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

#define ROMSIZE 32*1024

uint16_t compute_checksum(const unsigned char *buffer) {
	int i;
	uint16_t sum = 0;
	for(i = 8; i < ROMSIZE; i++) {
		sum += (unsigned char)*(buffer+i);
	}
	return sum;
}

int verify_file(const unsigned char *buffer) {
	uint16_t computed, actual;

	computed = compute_checksum(buffer);
	actual = ntohs(*(((uint16_t*)buffer)+3));
	if(computed != actual) {
		printf("Warning: Checksum appears invalid: %x vs %x\n", computed, actual);
		return 1;
	}
}

float convertf(float temp) {
	return ((temp * 9.0) / 5.0) + 32.0;
}

float convertc(float temp) {
	return (temp - 32.0) * 5.0/9.0;
}

void print_file(const unsigned char *buffer) {
	unsigned char tmpchar;
	float tmpfloat;
	int cylinders = 0;
	printf("PromID: %x\n", ntohs(*(((uint16_t*)buffer))));
	printf("Checksum: %x\n", ntohs(*(((uint16_t*)buffer)+3)));
	printf("Chip Code: %x\n", *(buffer+8));

	switch(*(buffer+9)) {
	case 0: cylinders = 8; break;
	case 192: cylinders = 6; break;
	case 128: cylinders = 4; break;
	default: break;
	}
	printf("Cylinders: %d (bin value: %x)\n", cylinders, *(buffer+9));
	printf("Cylinder displacement: %f (bin value: %x)\n", *(buffer+0x6de) / 27.894625 / 8, *(buffer+0x6de));
	printf("XXX VSS Gear Ratio: %x\n", *(buffer+0x15));
	printf("VATS Enabled: %s (bin value: %x)\n", (*(buffer+0x16) & 0x10) ? "YES" : "NO", *(buffer+0x16));
	printf("Air valve: %s (bin value: %x)\n", *(buffer+0x17) & 0x04 ? "dual" : "single", *(buffer+0x17));
	printf("Magnetic VSS: %s (bin value: %x)\n", (*(buffer+0x18) & 0x40) ? "NO" : "YES", *(buffer+0x18));
	printf("Initial spark advance: %f degrees (bin value: %x)\n", (float)*(buffer+0x25) * 0.35156, *(buffer+0x25));
	printf("Max spark advance: %f degrees (bin value: %x)\n", *(buffer+0x27) * 0.35156, *(buffer+0x27));

	printf("Highway Mode disable temp: %fF %dC (bin value: %x)\n", convertf((float)*(buffer+0x1a7)), *(buffer+0x1a7), *(buffer+0x1a7));

	printf("Enable Knock Sensor at RPM: %f rpm\n", *(buffer+0x20a) * 12.5);
	printf("Enable Knock Sensor at MPH: %d mph\n", *(buffer+0x20b));
	tmpfloat = *(buffer+0x20c) * 0.75 - 40.0;
	printf("Disable Knock Sensor below temp: %f F %f C\n", convertf(tmpfloat), tmpfloat);

	printf("Oil Temp low: %d F\n", *(buffer+0x2a8));
	printf("Oil Temp high: %d F\n", *(buffer+0x2ad));

	printf("Fan 1 enable speed: %d mph\n", *(buffer+0x374));
	printf("Fan 2 enable speed: %d mph\n", *(buffer+0x375));
	tmpfloat = *(buffer+0x377) * 0.75 - 40.0;
	printf("Fan 1 disable temp: %f F %f C\n", convertf(tmpfloat), tmpfloat);
	tmpfloat = *(buffer+0x378) * 0.75 - 40.0;
	printf("Fan 1 enable  temp: %f F %f C\n", convertf(tmpfloat), tmpfloat);
	tmpfloat = *(buffer+0x379) * 0.54;
	printf("Fan 2 disable temp: %f F %f C\n", convertf(tmpfloat), tmpfloat);
	tmpfloat = *(buffer+0x37a) * 0.54;
	printf("Fan 2 enable  temp: %f F %f C\n", convertf(tmpfloat), tmpfloat);

	printf("Max speed fuel cut: %d mph\n", *(buffer+0x3F4));
	printf("Max rpm fuel cut: %f rpm\n", (98 / *(buffer+0x3F6)) * 10036.7);

	tmpfloat = *(uint16_t*)(buffer+0x41c) / 5.0 / 256.0;
	printf("Injector flow rate: %f seconds/gram, %f lbs/hr\n", tmpfloat, (((1.0/tmpfloat)*60)*60)/454);
	
	printf("Max KPA for Highway mode: %f (bin value: %x)\n", *(buffer+0x480)*0.92308, *(buffer+0x480));
	tmpfloat = *(buffer+0x481) * 0.4928;
	printf("Minimum temp for Highway mode: %f F %f C\n", convertf(tmpfloat), tmpfloat);
	printf("Max time in Highway mode: %d sec\n", *(buffer+0x483));
	printf("Minimum speed for Highway mode: %d mph\n", *(buffer+0x484));

	tmpfloat = *(buffer+0x489) - 40.0;
	printf("Minimum temp for closed loop: %f F %f C\n", convertf(tmpfloat), tmpfloat);
}

void usage(char *name) {
	printf("Usage: %s [options] file.bin\n", name);
	printf("Options:\n");
	printf("-p                               prints known values\n");
	printf("--vats={1,0}                     enables or disables VATS\n");
	printf("--fan1-enable-temp=<degreesF>    set fan1 enable temp\n");
	printf("--fan1-disable-temp=<degreesF>   set fan1 disable temp\n");
	printf("--fan2-enable-temp=<degreesF>    set fan2 enable temp\n");
	printf("--fan2-disable-temp=<degreesF>   set fan2 disable temp\n");
	printf("--min-highway-speed=<mph>        set speed hw mode engages\n");
}

#define kcmd_print 1
int main(int argc, char *argv[]) {
	char c;
	int loptind = 0;
	struct option o[] = {
		{"vats", 1, 0, 1},
		{"fan1-enable-temp", 1, 0, 2},
		{"fan1-disable-temp", 1, 0, 3},
		{"fan2-enable-temp", 1, 0, 4},
		{"fan2-disable-temp", 1, 0, 5},
		{"min-highway-speed", 1, 0, 6},
		{ 0, 0, 0, 0 }
	};
	int cmd = 0;
	int fd;
	const unsigned char *file = NULL;
	char *buffer;
	ssize_t r;
	const char *outfile = NULL;

	int vats = -1;
	float fan1etemp = -1;
	float fan1dtemp = -1;
	float fan2etemp = -1;
	float fan2dtemp = -1;
	int minhwspeed = -1;

	while( (c = getopt_long(argc, argv, "o:p", o, &loptind)) != -1 ) {
		switch(c) {
		case 1:
			vats = atoi(optarg);
			break;
		case 2:
			fan1etemp = atof(optarg);
			break;
		case 3:
			fan1dtemp = atof(optarg);
			break;
		case 4:
			fan2etemp = atof(optarg);
			break;
		case 5:
			fan2dtemp = atof(optarg);
			break;
		case 6:
			minhwspeed = atoi(optarg);
			break;
		case 'o':
			outfile = optarg;
			break;
		case 'p':
			cmd = kcmd_print;
			break;
		default:
			usage(argv[0]);
			exit(1);
		}
	}
	argc -= optind;
	argv += optind;

	if( argc < 1 ) {
		fprintf(stderr, "Missing file name\n");
	}
	file = argv[0];

	if(!file) {
		fprintf(stderr, "No file specified.\n");
		exit(1);
	}

	
	fd = open(file, O_RDONLY);
	if(!fd) {
		fprintf(stderr, "Unable to open %s: %s\n", file, strerror(errno));
		exit(1);
	}
	buffer = calloc(1, ROMSIZE);
	if(!buffer) {
		fprintf(stderr, "Unable to allocate memory for file contents\n");
		exit(1);
	}

	r = read(fd, buffer, ROMSIZE);
	if(r != ROMSIZE) {
		fprintf(stderr, "Unable to read bin image (%zd) : %s\n", r, strerror(errno));
		exit(1);
	}

	close(fd);

	verify_file(buffer);

	if(cmd == kcmd_print) print_file(buffer);

	if(vats != -1) *(buffer+0x16) &= ~0x10;
	if(fan1etemp != -1) {
		float tempc = convertc(fan1etemp);
		*(buffer+0x378) = (unsigned char)((tempc+40)/0.75);
	}
	if(fan1dtemp != -1) {
		float tempc = convertc(fan1dtemp);
		*(buffer+0x377) = (unsigned char)((tempc+40)/0.75);
	}
	if(fan2etemp != -1) {
		float tempc = convertc(fan2etemp);
		*(buffer+0x37a) = (unsigned char)(tempc/0.54);
	}
	if(fan2dtemp != -1) {
		float tempc = convertc(fan2dtemp);
		*(buffer+0x379) = (unsigned char)(tempc/0.54);
	}
	if(minhwspeed != -1) {
		*(buffer+0x484) = minhwspeed;
	}

	if(outfile) {
		*(uint16_t*)(buffer+6) = htons(compute_checksum(buffer));
		fd = open(outfile, O_WRONLY|O_CREAT|O_TRUNC);
		if(!fd) {
			fprintf(stderr, "Could not open file %s: %s\n", outfile, strerror(errno));
			exit(1);
		}

		write(fd, buffer, ROMSIZE);
		close(fd);
	}
	exit(0);
}

