#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/io.h>
#include <libx86.h>

#define READ 1
#define WRITE 0

int monitor; //monitor port number.

struct sci_caps {
	int sclwrite;
	int sdawrite;
	int sclread;
	int sdaread;
	int ver; //0xABCD: AB is pre-decimal, DC is post. Multiply by 10.
	int ports;
};

struct sci_caps sci_caps;

void handle(int signo) {
	//don't want an alarm clock to bug me...
}

void die(char *msg) {
	fputs(msg, stderr);
	exit(1);
}

int vbe_cmd(unsigned int b, unsigned int d, struct LRMI_regs *regs) {
	pause(); //100KHz...	
	int ret;
	regs->eax = 0x4f15;
	regs->ebx = b;
	regs->ecx = monitor;
	regs->edx = d;
#ifdef EBUG
	fprintf(stderr, "wakawaka - b = %u, d = %u\n", b, d);
#endif
	ret = !LRMI_int(0x10, regs);
#ifdef EBUG
	fprintf(stderr, "dodoyeah\n");
	fprintf(stderr, "Return: 0x%x. Func %ssupported, call %s.\n", regs->eax, ((regs->eax & 0xff) == 0x4f) ? "" : "not ", ((regs->eax & 0xff00) == 0x0000) ? "successful" : "failed");
#endif
	return ret;
}

int get_sci_caps(struct LRMI_regs *regs) {
	if (vbe_cmd(0x10, 0, regs))
		die("Report Capabilities failed\n");
	sci_caps.sclwrite = regs->ebx & 0x01;
	sci_caps.sdawrite = regs->ebx & 0x02;
	sci_caps.sclread = regs->ebx & 0x04;
	sci_caps.sdaread = regs->ebx & 0x08;
	sci_caps.ver = (((regs->ecx & 0xff00) >> 8) * 10) + (regs->ecx & 0xff);
	fprintf(stderr, "VBE/SCI version %.1f\n", (float)sci_caps.ver / 10);
	sci_caps.ports = regs->edx;
	return 0;
}

int getclock(struct LRMI_regs *regs) {
	vbe_cmd(0x15, 0, regs);
	return regs->edx;
}

int clock(struct LRMI_regs *regs) {
	//clock down, clock up. Fast way of not changing data bits.
	vbe_cmd(0x13, 0, regs);
	vbe_cmd(0x13, 1, regs);
	while(!getclock(regs)){
		fprintf(stderr, "clock being angry\n");
	}
	return 0;
}

int clocktest(struct LRMI_regs *regs) {
	//clock down - is it down? good. Back up.
	//This'll take many false positive capabilities out.
	int ret;
	vbe_cmd(0x13, 0, regs);
	ret = getclock(regs); //0 if good, 1 if bad.
	vbe_cmd(0x13, 1, regs);
	return ret;
}

int data(int bit, struct LRMI_regs *regs) {
	//clock down, change data, clock up.
	vbe_cmd(0x13, 0, regs);
	vbe_cmd(0x14, bit, regs);
	vbe_cmd(0x13, 1, regs);
	while(!getclock(regs)){
		fprintf(stderr, "clock being angry\n");
	}
	return 0;
}

int readbit(struct LRMI_regs *regs) {
	clock(regs);
	vbe_cmd(0x16, 0, regs);
	return regs->edx;
}

int dostart(struct LRMI_regs *regs) {
	//clock down, up - in case it is a repeated start. Safe if it's not.
	//Will always result in a high clock, though.
	clock(regs);

	
        vbe_cmd(0x16, 0, regs);
        if (!regs->edx);
                vbe_cmd(0x14, 1, regs); //if data is low, make it high.
	//NB: this might end up being a stop... I think this is good...
	//I'll  clock() just in case...
	clock(regs);
	
	//Start bit: SCL is high, SDA goes high->low
	vbe_cmd(0x14, 0, regs);
#ifdef EBUG
	fprintf(stderr, "Start\n");
#endif
	return 0;
}

int dostop(struct LRMI_regs *regs) {
	//Data must be low, clock must be up.
	data(0, regs);

	//now bring data up.
	vbe_cmd(0x14, 1, regs);
#ifdef EBUG
	fprintf(stderr, "Stop\n");
#endif
	return 0;
}

int doack(struct LRMI_regs *regs) {
	data(1, regs);
        vbe_cmd(0x16, 0, regs);
        if (regs->edx == 1) {
                fprintf(stderr, "an ACK was a no-ack... continuing\n");
		return 1;
	}
#ifdef EBUG
	fprintf(stderr, "ACK\n");
#endif
	return 0;
}

int writebyte(unsigned char byte, struct LRMI_regs *regs) {
	//not optimized - will re-do writes, but it's I2C, so it doesn't matter :P
	
	data((byte & 0x80) >> 7, regs);
	data((byte & 0x40) >> 6, regs);
	data((byte & 0x20) >> 5, regs);
	data((byte & 0x10) >> 4, regs);
	data((byte & 0x08) >> 3, regs);
	data((byte & 0x04) >> 2, regs);
	data((byte & 0x02) >> 1, regs);
	data((byte & 0x01), regs);

	//I'll let you do the ack. Just in case.
#ifdef EBUG
	fprintf(stderr, "Wrote 0x%x\n", byte);
#endif
	return 0;
}

unsigned char readbyte(struct LRMI_regs *regs) {
	unsigned char ret;
	
	ret = (readbit(regs) & 0x01) << 7;
	ret |= (readbit(regs) & 0x01) << 6;
	ret |= (readbit(regs) & 0x01) << 5;
	ret |= (readbit(regs) & 0x01) << 4;
	ret |= (readbit(regs) & 0x01) << 3;
	ret |= (readbit(regs) & 0x01) << 2;
	ret |= (readbit(regs) & 0x01) << 1;
	ret |= (readbit(regs) & 0x01);
#ifdef EBUG
	fprintf(stderr, "Read 0x%x\n", ret);
#endif
	return ret;

	//you do the ack - just in case.
}

int doaddr(int addr, int rw, struct LRMI_regs *regs) {
	//SCL should be high, SDA should be low - you just did a start, right?
	
	//7-bit address, 1 bit read/write.
	//I usually don't make an unnecessary variable, but casting can be a bitch.
	unsigned char byte = (addr << 1) | (rw & 0x01);

	writebyte(byte, regs); //addr + r/w is a byte, write? :D

	return 0;
}

int writesp(unsigned char val, struct LRMI_regs *regs) { //write val to the segment pointer.
	//start
	dostart(regs);

	//addr = 0x30, write. Spec sez 0x60, and lies.
	doaddr(0x30, WRITE, regs);
	
	//ack. Don't care about this, or any for that matter.
	doack(regs);

	writebyte(val, regs);

	doack(regs);
	return 0;
}

int writewo(unsigned char val, struct LRMI_regs *regs) { //write val as the word offset.
	dostart(regs);
	//addr = 0x50, write. Spec sez 0xA0, and lies.
	doaddr(0x50, WRITE, regs);
	doack(regs);
	writebyte(val, regs);
	doack(regs);
	return 0;
}

int readedid(unsigned char *buf, struct LRMI_regs *regs) {
	//read first 128-byte edid.
	
	int i;

	//Write 0 to the segment pointer - first 2 blocks.
	//writesp(0, regs);

	//Write 0 to the word offset - first byte of first block.
	writewo(0, regs);

	//Address 0x50, read. Spec sez 0xA1, and lies.
	doaddr(0x50, READ, regs);
	doack(regs);

	for (i = 0; i < 128; i++) {
		buf[i] = readbyte(regs);

		//This ack is special - first of all, it's me doing the ACKing,
		//and the last byte must end with a NO-ACK.
		data((i == 127), regs);

	}

	//Stop. If we don't do this, your monitor will catch fire
	//
	//It won't really, but that would be kinda cool...
	//The bus really will get confused, though.
	dostop(regs);
	return 0;
}

int main(int argc, char *argv[]) {
	int i;
	unsigned char edid[128];
	struct itimerval delay;

	signal(SIGALRM, handle);
	delay.it_interval.tv_sec = 0;
	delay.it_interval.tv_usec = 10; //100KHz...
	delay.it_value.tv_sec = 0;
	delay.it_value.tv_usec = 10; //just another value.
	if (setitimer(ITIMER_REAL, &delay, NULL))
		perror("setitimer");
	
	if (!LRMI_init())
		die("Real-mode Init failed\n");
	struct LRMI_regs *regs = malloc(sizeof(struct LRMI_regs));
	
	if (argc > 2)
		die("too many arguments\n");
	
	if (argc == 1)
		monitor = 0;
	else
		monitor = atoi(argv[1]);

	//following command fixes permissions
	iopl(3);
	
	fprintf(stderr, "debug1\n");

	get_sci_caps(regs);
	fprintf(stderr, "Trying monitor %i out of %i.\n", monitor, sci_caps.ports);
	if (!(sci_caps.sclwrite && sci_caps.sdawrite && sci_caps.sclread && sci_caps.sdaread)) {
		fprintf(stderr, "Email the following line to the author, poly-p man, at pyrophobicman@gmail.com\nsclwrite: %i, sdawrite: %i, sclread: %i, sdaread: %i\n", sci_caps.sclwrite, sci_caps.sdawrite, sci_caps.sclread, sci_caps.sdaread);
		//die("Not all capabilities present...\n");
		fprintf(stderr, "Warning - not all capabilities present - trying anyway\n");
	}
	if (vbe_cmd(0x11, 0, regs)) //Begin SCL/SDA control.
		die("Begin SCL/SDA control failed\n");
	/* YOU MAY BEGIN I2C COMMANDS HERE */
#ifndef ONTSTOP	
	if (clocktest(regs)) 
		die("Clock not responding - no I2C support on video card.\n");
#endif
	fprintf(stderr, "About to start the read... hold on tight\n");

	readedid(edid, regs);
	for (i = 0; i < 128; i++)
		printf("%c", edid[i]);


	/* NO MORE I2C COMMANDS! */
	if (vbe_cmd(0x12, 0, regs)) //End SCL/SDA control.
		die("End SCL/SDA control failed\n");
	free(regs);
	return 0;
}
