#include "arc.h"
#include "ioc.h"
#include "serial.h"
#include <Windows.h>

// MOS 6551

uint8_t serial_control = 0b00000000;
uint8_t serial_command = 0b00000010;
uint8_t serial_status = 0b00010000;
uint8_t serial_transmit = 0;
uint8_t serial_receive = 0;

char serial_host_name[16] = "";
HANDLE serial_host_handle = INVALID_HANDLE_VALUE;

uint8_t serial_control_old = 0b11111111;
uint8_t serial_command_old = 0b11111111;

const uint8_t serial_control_host_control_mask = 0b11111111;
const uint8_t serial_command_host_control_mask = 0b11100001;

void serial_host_handle_init(void) {
	
	// Open the serial port if not already open
	if (serial_host_handle == INVALID_HANDLE_VALUE && strlen(serial_host_name) > 0) {
		serial_host_handle = CreateFile(serial_host_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	}
	
	// Is the port open?
	if (serial_host_handle == INVALID_HANDLE_VALUE) {
		return;
	}
	
	if ((serial_control & serial_control_host_control_mask) != serial_control_old || (serial_command & serial_command_host_control_mask) != serial_command_old) {
		
		// Fetch old comm state
		DCB serial_host_handle_config;
		GetCommState(serial_host_handle, &serial_host_handle_config);
		
		// Update the comm state variable
		
		// Control register fields
		{
			int baud_rates[16] = { -1, 50, 75, 110, 135, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 19200 };
			int stop_bits, word_length, baud_rate;
			stop_bits = (serial_control >> 7) + 1;
			word_length = 8 - ((serial_control >> 5) & 3);
			baud_rate = baud_rates[serial_control & 15];
			debug_out("serial_host_handle: stop bits = %d, word length = %d, baud rate = %d\n", stop_bits, word_length, baud_rate);
			
			// Populate DCB with the new settings
			serial_host_handle_config.StopBits = (stop_bits - 1) * 2;
			if (baud_rate > 0) serial_host_handle_config.BaudRate = baud_rate;
			serial_host_handle_config.ByteSize = word_length;
		}
		
		// Command register fields
		{
			int parity, echo, transmitter_interrupt, rts, receiver_interrupt, dtr;
			parity = (serial_command >> 5) & 3;
			echo = (serial_command >> 4) & 1;
			transmitter_interrupt = ((serial_command >> 2) & 3) == 0b01;
			rts = ((serial_command >> 2) & 3) == 0b00;
			receiver_interrupt = ((serial_command >> 1) & 1) == 0;
			dtr = ((serial_command >> 0) & 1) == 0;
			debug_out("serial_host_handle: parity = %d, echo = %d, transmitter_interrupt = %d, rts = %d, receiver_interrupt = %d, dtr = %d\n", parity, echo, transmitter_interrupt, rts, receiver_interrupt, dtr);
			
			// Populate DCB with the new settings
			switch (parity) {
				case 0b001:
					serial_host_handle_config.Parity = ODDPARITY;
					serial_host_handle_config.fParity = 1;
					break;
				case 0b011:
					serial_host_handle_config.Parity = EVENPARITY;
					serial_host_handle_config.fParity = 1;
					break;
				case 0b101:
					serial_host_handle_config.Parity = MARKPARITY;
					serial_host_handle_config.fParity = 1;
					break;
				case 0b111:
					serial_host_handle_config.Parity = SPACEPARITY;
					serial_host_handle_config.fParity = 1;
					break;
				default:
					serial_host_handle_config.Parity = NOPARITY;
					serial_host_handle_config.fParity = 0;
					break;
			}
			
			serial_host_handle_config.fDtrControl = dtr;
		}
		
		// General settings
		serial_host_handle_config.fRtsControl = RTS_CONTROL_HANDSHAKE;
		serial_host_handle_config.fOutxCtsFlow = 1;
		
		// Set the new comm state
		if (SetCommState(serial_host_handle, &serial_host_handle_config)) {
			debug_out("serial_host_handle: updated comm state\n");
			serial_control_old = serial_control & serial_control_host_control_mask;
			serial_command_old = serial_command & serial_command_host_control_mask;
		} else {
			debug_out("serial_host_handle: update comm state failed\n");
		}
	}
}

void serial_irq(void) {
	
	if ((serial_status & (1 << 3)) && !(serial_command & (1 << 1))) {
		serial_status |= (1 << 7);
	}
	
	if ((serial_status & (1 << 4)) && (((serial_command >> 2) & 3) == 1)) {
		serial_status |= (1 << 7);
	}
	
	if (serial_status & (1 << 7)) {
		ioc_irqb(IOC_IRQB_SERIAL_CONTROL);
	} else {
		ioc_irqbc(IOC_IRQB_SERIAL_CONTROL);
	}
}

void serial_poll_host(void) {
	
	if (serial_host_handle == INVALID_HANDLE_VALUE) {
		serial_status |= (1 << 4); // flag transmitter data is empty
	} else {
		
		DWORD serial_host_handle_errors;
		COMSTAT serial_host_handle_status;
		if (ClearCommError(serial_host_handle, &serial_host_handle_errors, &serial_host_handle_status)) {
			// Is there data to transmit?
			if (serial_host_handle_status.cbOutQue == 0 && (serial_status & (1 << 4)) == 0) {
				uint8_t buf[1] = { serial_transmit };
				if (WriteFile(serial_host_handle, buf, 1, NULL, NULL)) {
					serial_status |= (1 << 4); // flag transmitter data is empty
				}
			}
			// Is there data to receive?
			if (serial_host_handle_status.cbInQue > 0 && (serial_status & (1 << 3)) == 0) {
				uint8_t buf[1] = { 0 };
				if (ReadFile(serial_host_handle, buf, 1, NULL, NULL)) {
					serial_receive = buf[0];
					serial_status |= (1 << 3); // flag receiver data is full
				}
			}
		} else {
			serial_status |= (1 << 4); // flag transmitter data is empty
		}
	}
	
	serial_irq();
}

void serial_reset(void) {
	serial_control = 0b00000000;
	serial_command = 0b00000010;
	serial_status = (serial_status & 0b01100000) | 0b00010000;
	serial_host_handle_init();
	serial_irq();
}

void serial_write(uint16_t addr, uint8_t val) {
	switch ((addr >> 2) & 3) {
		case 0: // Transmit data register
			//debug_out("serial_write transmit = %02x\n", val);
			serial_transmit = val;
			serial_status &= ~(1 << 4); // flag transmitter data as full
			serial_poll_host();
			break;
		case 1: // Programmed reset
			//debug_out("serial_write reset = %02x\n", val);
			serial_control = 0b00000000;
			serial_command = (serial_command & 0b11100000) | 0b00000010;
			serial_status = serial_status & 0b11111011;
			serial_host_handle_init(); // re-init host
			break;
		case 2: // Command register
			//debug_out("serial_write command = %02x\n", val);
			serial_command = val;
			serial_host_handle_init(); // re-init host
			break;
		case 3: // Control register
			//debug_out("serial_write control = %02x\n", val);
			serial_control = val;
			serial_host_handle_init(); // re-init host
			break;
	}
	serial_irq();
}

uint8_t serial_read(uint16_t addr) {
	uint8_t val = 0xFF;
	switch ((addr >> 2) & 3) {
		case 0: // Receiver data register
			val = serial_receive;
			//debug_out("serial_read receiver = %02x\n", val);
			serial_status &= ~(1 << 3); // flag receiver data is not full
			break;
		case 1: // Status register
			val = serial_status;
			//debug_out("serial_read status = %02x\n", val);
			serial_status &= ~(1 << 7); // Clear IRQ
			serial_poll_host();
			break;
		case 2: // Command register
			val = serial_command;
			//debug_out("serial_read command = %02x\n", val);
			break;
		case 3: // Control register
			val = serial_control;
			//debug_out("serial_read control = %02x\n", val);
			break;
	}
	serial_irq();
	return val;
}
