#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/termios.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <linux/tty.h>
#include <sys/ioctl.h>
#include <errno.h>

typedef unsigned long u32;
typedef unsigned short u16;
typedef unsigned char u8;

#define min(a,b) ( (a<b) ? a : b )

int debug = 0;
int devfd = -1;
struct termios oldtio, newtio;
char queuebuf[256];
int queued=0;
int displayf=0;
int hddrate=0, hddsize=0, hddboot=0;

void queue_s(char *str) {
	int count = min(strlen(str),(255-queued));
	if(count<=0) return;

	memcpy(queuebuf+queued, str, count);
	queued+=count;
}

void queue_c(char c) {
	int count = (255-queued);
	if(count<=0) return;

	queuebuf[queued] = c;
	queued++;
}

void queue_chk(void) {
	int i, c=0;
	queue_c(0x9F);
	for(i=1; i<queued; i++)
		c+=queuebuf[i];
	
	c %= 256;
	queue_c(c);
	queue_c(0xFE);
}

void queue_parse_response(void) {
	int i;
	
	if(!memcmp(&queuebuf[1], "CD6", 3)) {
		printf("CoolDrive6 ID\n");
	} else {
		u16 *rpm = (u16*)&queuebuf[1];
		u8 *temp = (u8*)&queuebuf[9];
		u8 flags = *(u8*)&queuebuf[17];
		u16 idle = *(u16*)&queuebuf[18];
		u8 *speed = (u8*)&queuebuf[20];
		u8 *alarm = (u8*)&queuebuf[24];

		float ftemp[4], falarm[4];
		for(i=0; i<4; i++) {
			ftemp[i] = temp[i*2] + (temp[i*2]/10.0f);
			falarm[i] = alarm[i] * 1.0f;
		}

		if(displayf) {
			for(i=0; i<4; i++) {
				ftemp[i] *= (9.0f/5.0f);
				ftemp[i] += 32.0f;
				falarm[i] *= (9.0f/5.0f);
				falarm[i] += 32.0f;
			}
		}

		printf("         \t    1      2      3      4\n");
		printf("FAN RPM: \t%5d\t%5d\t%5d\t%5d\n",
			rpm[0], rpm[1], rpm[2], rpm[3]);
		printf("FAN Set: \t%5d\t%5d\t%5d\t%5d\n",
			speed[0], speed[1], speed[2], speed[3]);
		printf("Temp:    \t%3.2f\t%3.2f\t%3.2f\t%3.2f %c\n",
			ftemp[0], ftemp[1], ftemp[2], ftemp[3], (displayf ? 'F' : 'C'));
		printf("Alarm:   \t%3.2f\t%3.2f\t%3.2f\t%3.2f %c\n",
			falarm[0], falarm[1], falarm[2], falarm[3], (displayf ? 'F' : 'C'));
		printf("Flags:   ");
		if(flags & 32) printf("ALARM");
		if(flags & 64) printf("IDLE");
		printf("\n");

		idle = ((idle & 0xff) << 8) | ((idle & 0xff00) >> 8);
		printf("Idle:    %5d\n", idle);
	}
}

void queue_send(void) {
	int i;
	if(devfd<0) return;

	if(debug) printf("Sending... ");
	for(i=0; i<queued; i++) {
		write(devfd, &queuebuf[i], 1);
		usleep(16384);
	}
	queued=0;
	if(debug) printf("Done\n");
}

int queue_start(char *cmd) {
	char tbuf[256];

	// Prevent buffer overruns.
	hddrate %= 10000;
	hddsize %= 100000000;
	hddboot %= 100000000;
	
	queued=0;
	memset(queuebuf, 0, 256);
	
	queue_c(0xA0);

	sprintf(tbuf, "%3i%i", hddrate/10, hddrate%10);
	queue_s(tbuf);

	sprintf(tbuf, "%6i%02i", hddsize/100, hddsize%100);
	queue_s(tbuf);

	sprintf(tbuf, "%6i%02i", hddboot/100, hddboot%100);
	queue_s(tbuf);

	queue_s(cmd);
}

void queue_close(void) {
	if(devfd<0) return;

	tcflush(devfd, TCIFLUSH);
	tcsetattr(devfd, TCSANOW, &oldtio);
	
	close(devfd);
	devfd=-1;
}

int queue_open(char *devname) {
	int n;
	queue_close();
	
	queued=0;
	memset(queuebuf, 0, 256);
	
	devfd=open(devname, O_RDWR | O_NOCTTY | O_NDELAY);
	if(devfd<0) {
		printf("Unable to open %s\n", devname);
	}

	tcflush(devfd, TCIOFLUSH);

	n = fcntl(devfd, F_GETFL, 0);

	fcntl(devfd, F_SETFL, n & ~O_NDELAY);

	if(tcgetattr(devfd, &oldtio) !=0)
		fprintf(stderr, "tcgetattr() failed\n");

	if(tcgetattr(devfd, &newtio) !=0)
		fprintf(stderr, "tcgetattr() failed\n");
	
	cfsetispeed(&newtio, B9600);
	cfsetospeed(&newtio, B9600);

	newtio.c_cflag = (newtio.c_cflag & ~CSIZE) | CS8;
	newtio.c_cflag |= CLOCAL | CREAD;
	newtio.c_cflag &= ~CRTSCTS;
	newtio.c_cflag &= ~CSTOPB;
	newtio.c_iflag |= IGNBRK;
	newtio.c_iflag &= ~(IXON|IXOFF|IXANY);
	newtio.c_lflag=0;
	newtio.c_oflag=0;
	memset(newtio.c_cc, _POSIX_VDISABLE, sizeof(newtio.c_cc));

	if(tcsetattr(devfd, TCSANOW, &newtio) !=0)
		fprintf(stderr, "tcsetattr() failed\n");

	int mcs=0;
	ioctl(devfd, TIOCMGET, &mcs);
	mcs |= TIOCM_RTS;
	ioctl(devfd, TIOCMSET, &mcs);

	if(tcgetattr(devfd, &newtio) !=0)
		fprintf(stderr, "tcgetattr() failed\n");

	newtio.c_cflag &= ~CRTSCTS;

	if(tcsetattr(devfd, TCSANOW, &newtio) !=0)
		fprintf(stderr, "tcsetattr() failed\n");
}

void queue_string(char *str, int c) {
	int z;
	for(z=0; z<min(c, strlen(str)); z++)
		queue_c(str[z]);
	if(z<c)
		for(; z<c; z++)
			queue_c(' ');
}

void queue_u8(char *str, int max) {
	queue_c(atoi(str) % max);
}

void queue_u16(char *str, int max) {
	int z=atoi(str);

	if(max) z %= max;
	z &= 0xffff;

	queue_c(((u16)z) >> 8);
	queue_c(((u16)z) & 0xFF);
}

int main(int argc, char **argv) {
	int i,z;
	int reading;
	char tbuf[256];

	for(i=1; i<argc; i++) {
		if(!strcmp(argv[i], "-h") | !strcmp(argv[i], "--help")) {
			printf("Syntax: %s [options]\n", argv[0]);
			printf("\t-dev         <serial>       Serial port\n");
			printf("\t-id                         Request device ID\n");
			printf("\t-hdd-rate    <speed>        HDD Transfer Rate 10 = 1Mbit\n");
			printf("\t-hdd-life    <hhhhhhmm>     HDD runtime total\n");
			printf("\t-hdd-boot    <hhhhhhmm>     HDD runtime since last boot\n");
			printf("\t-hdd-size    <space>        HDD Size 10 = 1MByte\n");
			printf("\t-hdd-used    <space>        HDD Used 10 = 1MByte\n");
			printf("\t-hdd-brand   <string>       HDD Brand [16 bytes]\n");
			printf("\t-hdd-model   <string>       HDD Model [16 bytes]\n");
			printf("\t-fan?-name   <string>       Fan Label [9 bytes]\n");
			printf("\t-fan?-speed  <speed>        Fan Speed 0=off 18=full\n");
			printf("\t-temp?-name  <string>       Temp Label [9 bytes]\n");
			printf("\t-temp?-alarm <value>        Temp Alarm 0-90'C\n");
			printf("\t-c                          Set Cooldrive display in Celsius\n");
			printf("\t-f                          Set Cooldrive display in Fahrenheit\n");
			printf("\t-C                          Show responses in Celsius\n");
			printf("\t-F                          Show responses in Fahrenheit\n");
			printf("\t-v                          Verbose responses\n");
			exit(0);
		} else if(!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) {
			debug=1;
		} else if(!strcmp(argv[i], "-hdd-rate")) {
			i++;
			hddrate=atoi(argv[i]);
		} else if(!strcmp(argv[i], "-hdd-life")) {
			i++;
			hddsize=atoi(argv[i]);
		} else if(!strcmp(argv[i], "-hdd-boot")) {
			i++;
			hddboot=atoi(argv[i]);
		} else if(!strcmp(argv[i], "-dev")) {
			i++;
			queue_open(argv[i]);
		}
	}
	
	if(devfd == -1) {
		printf("No device specified.\n");
		return 0;
	}

	for(i=1; i<argc; i++) {
		if(!strcmp(argv[i], "-h") | !strcmp(argv[i], "--help")) {
		} else if(!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) {
		} else if(!strcmp(argv[i], "-hdd-rate")) {
			i++;
		} else if(!strcmp(argv[i], "-hdd-life")) {
			i++;
		} else if(!strcmp(argv[i], "-hdd-boot")) {
			i++;
		} else if(!strcmp(argv[i], "-dev")) {
			i++;
		} else if(!strcmp(argv[i], "-id")) {
			queue_start("DV");
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-hdd-model")) {
			i++;
			queue_start("MO");
			queue_string(argv[i],16);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-hdd-brand")) {
			i++;
			queue_start("BR");
			queue_string(argv[i],16);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-hdd-used")) {
			i++;
			z = atoi(argv[i]);
			queue_start("US");
			sprintf(tbuf, "%4i%i", z/10, z%10);
			queue_s(tbuf);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-hdd-size")) {
			i++;
			z = atoi(argv[i]);
			queue_start("TS");
			sprintf(tbuf, "%4i%i", z/10, z%10);
			queue_s(tbuf);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-fan1-name")) {
			i++;
			queue_start("FA");
			queue_string(argv[i],9);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-fan2-name")) {
			i++;
			queue_start("FB");
			queue_string(argv[i],9);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-fan3-name")) {
			i++;
			queue_start("FC");
			queue_string(argv[i], 9);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-fan4-name")) {
			i++;
			queue_start("FD");
			queue_string(argv[i],9);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-fan1-speed")) {
			i++;
			queue_start("FE");

			queue_u8(argv[i], 19);

			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-fan2-speed")) {
			i++;
			queue_start("FF");

			queue_u8(argv[i], 19);

			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-fan3-speed")) {
			i++;
			queue_start("FG");

			queue_u8(argv[i], 19);

			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-fan4-speed")) {
			i++;
			queue_start("FH");

			queue_u8(argv[i], 19);

			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-temp1-name")) {
			i++;
			queue_start("CA");
			queue_string(argv[i],9);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-temp2-name")) {
			i++;
			queue_start("CB");
			queue_string(argv[i],9);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-temp3-name")) {
			i++;
			queue_start("CC");
			queue_string(argv[i],9);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-temp4-name")) {
			i++;
			queue_start("CD");
			queue_string(argv[i],9);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-temp1-alert")) {
			i++;
			queue_start("CE");
			queue_u8(argv[i], 91);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-temp2-alert")) {
			i++;
			queue_start("CF");
			queue_u8(argv[i], 91);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-temp3-alert")) {
			i++;
			queue_start("CG");
			queue_u8(argv[i], 91);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-temp4-alert")) {
			i++;
			queue_start("CH");
			queue_u8(argv[i], 91);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-idle-time")) {
			i++;
			queue_start("IT");
			queue_u16(argv[i], 65536);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-idle-text")) {
			i++;
			queue_start("IC");
			queue_string(argv[i],27);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-idle-on")) {
			queue_start("ID");
			queue_c(0x20);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-idle-off")) {
			queue_start("ID");
			queue_c(0x00);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-alarm")) {
			queue_start("ID");
			queue_c(0x10);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-c")) {
			queue_start("ID");
			queue_c(0x21);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-f")) {
			queue_start("ID");
			queue_c(0x61);
			queue_chk();
			queue_send();
		} else if(!strcmp(argv[i], "-C")) {
			displayf=0;
		} else if(!strcmp(argv[i], "-F")) {
			displayf=1;
		} else if(!strcmp(argv[i], "-comm")) {
			i++;
			queue_start("CP");
			queue_u8(argv[i], 4);
			queue_chk();
			queue_send();
		} else {
			printf("unknown parameter %s\n", argv[i]);
		}
	}

//	z = fcntl(devfd, F_GETFL, 0);
//	fcntl(devfd, F_SETFL, z | O_NDELAY);

	printf("Reading...\n");

	if( devfd != -1 ) {
		int t, reading;
		int c=0;
		for(t=0; t<64; t++) {
			unsigned char b;
			int rv = read(devfd, &b, 1);
			if((rv<0) && (errno != EAGAIN)) {
				fprintf(stderr, "Read Error\n");
				reading = 0;
				break;
			} else if(rv==1) {
				if(debug) {
					printf(" %02x", b);
					if(c==7) printf(" ");
					if(c==16) {
						c=0;
						printf("\n");
					}
				}
				if(b==0xA0) {
					t=0;
					reading=1;
					queued=0;
					memset(queuebuf, 0, 256);
				}
				if(reading) {
					queue_c(b);
					if(b==0xFE) {
						if(debug) printf("\n");
						queue_parse_response();
						reading=0;
					}
				}
				t--;
			} else {
				usleep(16);
			}
		}
	}

	queue_close();

	return 0;
}

