#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <sysexits.h>
#include <getopt.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

void usage(const char *pname) {
	fprintf(stderr, "Usage: %s [-h|--help] --type-cat # --type-type # --type-devsw # --type-devhw # [-d|--drvrrsrc filename] [-n|--name name] [-o|--output outfile]\n", pname);
	fprintf(stderr, "-h | --help:                 This usage statement\n");
	fprintf(stderr, "--type-cat:                  A hexadecimal number representing the resource category\n");
	fprintf(stderr, "--type-type:                 A hexadecimal number representing the resource type\n");
	fprintf(stderr, "--type-devsw:                A hexadecimal number representing the DevSW component\n");
	fprintf(stderr, "--type-devhw:                A hexadecimal number representing the DevHW component\n");
	fprintf(stderr, "-d | --drvrrsrc:             The filename of the driver resource file\n");
	fprintf(stderr, "-n | --name:                 The name of the board\n");
	fprintf(stderr, "-o | --output:               The filename to write the resource to\n");
	return;
}

int main(int argc, char *argv[]) {
	char c = 0;
	int loptind = 0;
	char *name = NULL;
	char *driverfile = NULL;
	char *bootfile = NULL;
	char *outfile = NULL;
	uint16_t tcat = 0;
	uint16_t ttype = 0;
	uint16_t tdevsw = 0;
	uint16_t tdevhw = 0;
	int ofd;
	struct option o[] = {
		{"help", 0, 0, 'h'},
		{"name", 1, 0, 'n'},
		{"output", 1, 0, 'o'},
		{"drvrrsrc", 1, 0, 'd'},
		{"bootrec", 1, 0, 'b'},
		{"type-cat", 1, 0, 1},
		{"type-type", 1, 0, 2},
		{"type-devsw", 1, 0, 3},
		{"type-devhw", 1, 0, 4},
		{0,0,0,0}
	};

	while( (c = getopt_long(argc, argv, "b:d:hn:o:", o, &loptind)) != -1 ) {
		switch(c) {
		case 1:
			if(!optarg) {
				usage(argv[0]);
				exit(EX_USAGE);
			}
			sscanf(optarg, "%hx", &tcat);
			break;
		case 2:
			if(!optarg) {
				usage(argv[0]);
				exit(EX_USAGE);
			}
			sscanf(optarg, "%hx", &ttype);
			break;
		case 3:
			if(!optarg) {
				usage(argv[0]);
				exit(EX_USAGE);
			}
			sscanf(optarg, "%hx", &tdevsw);
			break;
		case 4:
			if(!optarg) {
				usage(argv[0]);
				exit(EX_USAGE);
			}
			sscanf(optarg, "%hx", &tdevhw);
			break;
		case 'b':
			if(!optarg) {
				usage(argv[0]);
				exit(EX_USAGE);
			}
			bootfile = optarg;
			break;
		case 'd':
			if(!optarg) {
				usage(argv[0]);
				exit(EX_USAGE);
			}
			driverfile = optarg;
			break;
		case 'n':
			if(!optarg) {
				usage(argv[0]);
				exit(EX_USAGE);
			}
			name = optarg;
			break;
		case 'o':
			if(!optarg) {
				usage(argv[0]);
				exit(EX_USAGE);
			}
			outfile = optarg;
			break;
		case 'h':
			usage(argv[0]);
			exit(EX_USAGE);
			break;
		}
	}

	if(!outfile) {
		usage(argv[0]);
		exit(EX_USAGE);
	}
	errno = 0;

	ofd = open(outfile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
	if(ofd < 0) {
		fprintf(stderr, "Could not open output file %s: %d %s\n", outfile, errno, strerror(errno));
		exit(EX_CONFIG);
	}

	struct stat bootstat = {0};
	char *bootrec = NULL;
	if(bootfile) {
		int ifd = open(bootfile, O_RDONLY);
		if(ifd < 0) {
			fprintf(stderr, "Could not open boot file %s: %d %s\n", driverfile, errno, strerror(errno));
			exit(EX_CONFIG);
		}

		if(fstat(ifd, &bootstat) < 0) {
			fprintf(stderr, "Could not fstat boot file %s: %d %s\n", driverfile, errno, strerror(errno));
			exit(EX_CONFIG);
		}

		bootrec = calloc(1, bootstat.st_size);
		if(!bootrec) exit(1);
		if(read(ifd, bootrec, bootstat.st_size) != bootstat.st_size) {
			fprintf(stderr, "Could not read boot file %s: %d %s\n", driverfile, errno, strerror(errno));
			exit(EX_CONFIG);
		}
		close(ifd);
	}

	struct stat driverstat = {0};
	char *driverrsrc = NULL;
	if(driverfile) {
		int ifd = open(driverfile, O_RDONLY);
		if(ifd < 0) {
			fprintf(stderr, "Could not open driver file %s: %d %s\n", driverfile, errno, strerror(errno));
			exit(EX_CONFIG);
		}

		if(fstat(ifd, &driverstat) < 0) {
			fprintf(stderr, "Could not fstat driver file %s: %d %s\n", driverfile, errno, strerror(errno));
			exit(EX_CONFIG);
		}

		driverrsrc = calloc(1, driverstat.st_size);
		if(!driverrsrc) exit(1);
		if(read(ifd, driverrsrc, driverstat.st_size) != driverstat.st_size) {
			fprintf(stderr, "Could not read driver file %s: %d %s\n", driverfile, errno, strerror(errno));
			exit(EX_CONFIG);
		}
		close(ifd);
	}
	
	uint32_t tmpint;
	uint32_t rsrctypeoff = 0;
	rsrctypeoff = 4 /* rsrc offset */
	            + 4 /* name offset */
	            + 4 /* sRsrcFlags */
	            + 4 /* hwid */
	            + 4 /* MinorBaseOS */
	            + 4 /* MinorLength */
	            + 4 /* EOL */;
	if(bootfile) rsrctypeoff+=4;
	if(driverfile) rsrctypeoff+=4;
	
	tmpint = 0x01000000 | rsrctypeoff;
	tmpint = htonl(tmpint);
	if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
		fprintf(stderr, "Could not write sRsrcType offset: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}

	rsrctypeoff -= 4; /* decrement for next field */
	uint32_t rsrcnameoff = rsrctypeoff + 8 /* size of rsrctype */;
	tmpint = 0x02000000 | rsrcnameoff;
	tmpint = htonl(tmpint);
	if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
		fprintf(stderr, "Could not write sRsrcName offset: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}

	if(driverfile) {
		rsrcnameoff -= 4; /* decrement for next field */
		uint32_t drvroff = rsrcnameoff + strlen(name) + 1;
		tmpint = 0x04000000 | drvroff;
		tmpint = htonl(tmpint);
		if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
			fprintf(stderr, "Could not write sRsrcDrvrDir offset: %d %s\n", errno, strerror(errno));
			exit(EX_CONFIG);
		}
	}

	if(bootfile) {
		rsrcnameoff -= 4; /* decrement for next field */
		uint32_t bootoff = rsrcnameoff + strlen(name) + 1 + driverstat.st_size;
		tmpint = 0x06000000 | bootoff;
		tmpint = htonl(tmpint);
		if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
			fprintf(stderr, "Could not write sRsrcBootRec offset: %d %s\n", errno, strerror(errno));
			exit(EX_CONFIG);
		}
	}

	rsrcnameoff -= 4; /* decrement for next field */
	tmpint = 0x07000002;
	tmpint = htonl(tmpint);
	if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
		fprintf(stderr, "Could not write sRsrcHwDevId: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}

	rsrcnameoff -= 4; /* decrement for next field */
	tmpint = 0x08000001;
	tmpint = htonl(tmpint);
	if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
		fprintf(stderr, "Could not write sRsrcHwDevId: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}

	rsrcnameoff -= 4;
	tmpint = 0x0A000000 | (rsrcnameoff + strlen(name) + 1 + driverstat.st_size + bootstat.st_size);
	tmpint = htonl(tmpint);
	if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
		fprintf(stderr, "Could not write MinorBaseOS offset: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}

	rsrcnameoff -= 4;
	tmpint = 0x0B000000 | (rsrcnameoff + strlen(name) + 1 + driverstat.st_size + bootstat.st_size + 4);
	tmpint = htonl(tmpint);
	if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
		fprintf(stderr, "Could not write MinorLength offset: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}

	tmpint = 0xFF000000;
	tmpint = htonl(tmpint);
	if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
		fprintf(stderr, "Could not write EOL: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}

	/* sRsrcType */
	tmpint = (tcat << 16) /* Category */ | ttype /* cType */;
	tmpint = htonl(tmpint);
	if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
		fprintf(stderr, "Could not write sRsrcType 1: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}
	tmpint = (tdevsw << 16) /* DrSW */ | tdevhw /* DrHW */;
	tmpint = htonl(tmpint);
	if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
		fprintf(stderr, "Could not write sRsrcType 2: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}
	
	/* sRsrcName */
	if(write(ofd, name, strlen(name)+1) < strlen(name)) {
		fprintf(stderr, "Could not write sRsrcName: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}

	if(driverfile) {
		if(write(ofd, driverrsrc, driverstat.st_size) != driverstat.st_size) {
			fprintf(stderr, "Could not write sRsrcDrvrDir resource: %d %s\n", errno, strerror(errno));
			exit(EX_CONFIG);
		}
	}

	if(bootfile) {
		if(write(ofd, bootrec, bootstat.st_size) != bootstat.st_size) {
			fprintf(stderr, "Could not write sRsrcBootRec resource: %d %s\n", errno, strerror(errno));
			exit(EX_CONFIG);
		}
	}

	tmpint = 0;
	if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
		fprintf(stderr, "Could not write MinorBaseOS: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}

	tmpint = 0xFFC0;
	tmpint = htonl(tmpint);
	if(write(ofd, &tmpint, sizeof(tmpint)) != sizeof(tmpint)) {
		fprintf(stderr, "Could not write MinorLength: %d %s\n", errno, strerror(errno));
		exit(EX_CONFIG);
	}

	close(ofd);
	exit(EX_OK);
}
