#include <windows.h>
#include <assert.h>
#include "L4SERIAL.h"

HANDLE InitSerialPort(WORD rate, WORD format, const char* port, bool overlapped)
{
	HANDLE hComm = INVALID_HANDLE_VALUE;

	hComm = CreateFileA(port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, overlapped ? FILE_FLAG_OVERLAPPED : NULL, NULL);
	if(hComm == INVALID_HANDLE_VALUE)
   {
		DWORD err = GetLastError();
		DEBUG_STREAM << "ERROR: CreateFileA() failed [GetLastError() = " << GetLastError() << "]!\n" << std::flush;
		return INVALID_HANDLE_VALUE;
   }

	DCB control;
	memset(&control, 0, sizeof(control));
	control.DCBlength = sizeof(control);
	control.EvtChar = (char)0xFF;

	if(rate == PCS_300)
		control.BaudRate = 300;
	else if(rate == PCS_1200)
		control.BaudRate = 1200;
	else if(rate == PCS_2400)
		control.BaudRate = 2400;
	else if(rate == PCS_9600)
		control.BaudRate = 9600;
	else if(rate == PCS_19P2)
		control.BaudRate = 19200;
	else if(rate == PCS_38P4)
		control.BaudRate = 38400;
	else if(rate == PCS_115K)
		control.BaudRate = 115200;
	else
	{
		DEBUG_STREAM << "ERROR: Invalid Baud Rate!\n" << std::flush;
		CloseHandle(hComm);
		return INVALID_HANDLE_VALUE;
	}

	if((format & PCSMAP) == PCSNP)
		control.Parity = NOPARITY;
	else if((format & PCSMAP) == PCSOP)
		control.Parity = ODDPARITY;
	else if((format & PCSMAP) == PCSEP)
		control.Parity = EVENPARITY;
	else if((format & PCSMAP) == PCSMP)
		control.Parity = MARKPARITY;
	else if((format & PCSMAP) == PCSSP)
		control.Parity = SPACEPARITY;
	else
	{
		DEBUG_STREAM << "ERROR: Invalid Parity!\n" << std::flush;
		CloseHandle(hComm);
		return INVALID_HANDLE_VALUE;
	}

	if((format & PCSMD) == PCS8D)
		control.ByteSize = 8;
	else if((format & PCSMD) == PCS6D)
		control.ByteSize = 6;
	else if((format & PCSMD) == PCS7D)
		control.ByteSize = 7;
	else if((format & PCSMD) == PCS8D)
		control.ByteSize = 8;
	else
	{
		DEBUG_STREAM << "ERROR: Invalid Byte Size!\n" << std::flush;
		CloseHandle(hComm);
		return INVALID_HANDLE_VALUE;
	}

	if((format & PCSMS) == PCS1S)
		control.StopBits = ONESTOPBIT;
	else if((format & PCSMS) == PCS2S)
		control.StopBits = TWOSTOPBITS;
	else
	{
		DEBUG_STREAM << "ERROR: Invalid Stop Bits!\n" << std::flush;
		CloseHandle(hComm);
		return INVALID_HANDLE_VALUE;
	}

	if(!SetCommState(hComm, &control))
	{
		DEBUG_STREAM << "ERROR: SetCommState() failed [rate = " << control.BaudRate;
		DEBUG_STREAM << ", dataBits = " << control.ByteSize;
		DEBUG_STREAM << ", stopBits = " << control.StopBits;
		DEBUG_STREAM << ", parity = " << control.Parity << "!\n";
		CloseHandle(hComm);
		return INVALID_HANDLE_VALUE;
	}

	//Non-blocking
	COMMTIMEOUTS timeout;
	timeout.ReadIntervalTimeout = MAXDWORD;
	timeout.ReadTotalTimeoutConstant = 0;
	timeout.ReadTotalTimeoutMultiplier = 0;
	timeout.WriteTotalTimeoutConstant = 0;
	timeout.WriteTotalTimeoutMultiplier = 0;
	if(!SetCommTimeouts(hComm, &timeout))
	{
		DEBUG_STREAM << "ERROR: SetCommTimeouts() failed!\n";
		CloseHandle(hComm);
		return INVALID_HANDLE_VALUE;
	}

	PurgeComm(hComm, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);

	return hComm;
}

//Win32 Serial support: ADB 01/11/07
PCSerial::PCSerial(WORD rate, WORD format, const char* port)
//PCSerial(DWORD rate, BYTE dataBits, BYTE stopBits, BYTE parity, const WCHAR* port)
{
	enabled = 0;

   mOverlappedOb.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
   mOverlappedOb.Internal = 0;
   mOverlappedOb.InternalHigh = 0;
   mOverlappedOb.Offset = 0;
   mOverlappedOb.OffsetHigh = 0;

	//PCSerialInit(this, rate, format, port, int_num);
	hComm = InitSerialPort(rate, format, port, true);

	if(hComm != INVALID_HANDLE_VALUE)
		enabled = 1;
}

PCSerial::~PCSerial()
{
	if (this == NULL)
	{
		std::cout << "~PCSerial has been passed a NULL pointer\n" << std::flush;
	}
	else
	{
      WaitForSingleObject(mOverlappedOb.hEvent,INFINITE);
      CloseHandle(mOverlappedOb.hEvent);
		CloseHandle(hComm);
		hComm = INVALID_HANDLE_VALUE;
		enabled = 0;
	}
}

//Win32 Serial support: ADB 01/15/07
Logical PCSerial::TestInstance()
{
	//return PCSerialTestInstance(this);
	return 1;
}

//Win32 Serial support: ADB 01/15/07
int PCSerial::ReceiveString(char *destPtr, int maxLen)	// returns length
{
   //TODO:  Do overlapped reads if we ever need this function again
   assert(FALSE);
	//return PCSerialReceive(this, destPtr, maxLen)
	DWORD bytesRead;
	ReadFile(hComm, (void *)destPtr, maxLen, &bytesRead, NULL);
	return (int)bytesRead;
}

//Win32 Serial support: ADB 01/15/07
void PCSerial::FlowControlEnable(Logical onOff)
{
	//STUBBED: Serial ADB 01/15/07
	//VERIFY: Probably not used
	//PCSerialFlowControl(this, (int) onOff);DCB control;
}

Logical PCSerial::CanDraw()
{
   return (WaitForSingleObject(mOverlappedOb.hEvent, 0) == WAIT_OBJECT_0);
}

//Win32 Serial support: ADB 01/15/07
void PCSerial::SendString(char *srcPtr, int length)
{
	//PCSerialSend(this, srcPtr, length);
   WaitForSingleObject(mOverlappedOb.hEvent, INFINITE);
	DWORD bytesWritten;
	WriteFile(hComm, (void*)srcPtr, length, &bytesWritten, &mOverlappedOb);
}

//Win32 Serial support: ADB 01/15/07
int PCSerial::ReceiveCharCount()
{
	//return PCSerialReceiveCount(this);
	COMMPROP props;
	if(!GetCommProperties(hComm, &props))
	{
		DEBUG_STREAM << "ERROR: GetCommProperties() failed in TransmitCharCount()!  GetLastError() = " << GetLastError() << std::endl;
		return 0;
	}

	return props.dwCurrentRxQueue;
}

//Win32 Serial support: ADB 01/15/07
int PCSerial::TransmitCharCount()
{
	//return PCSerialTransmitCount(this);
	COMMPROP props;
	if(!GetCommProperties(hComm, &props))
	{
      DEBUG_STREAM << "ERROR: GetCommProperties() failed in TransmitCharCount()!  GetLastError() = " << GetLastError() << std::endl;
		return 0;
	}

	return props.dwCurrentTxQueue;
}

//Win32 Serial support: ADB 01/15/07
int PCSerial::Errors()							// returns error value, clears it
{
	//return PCSerialErrors(this);
	DWORD errors;
	if(!ClearCommError(hComm, &errors, NULL))
	{
		DEBUG_STREAM << "ERROR: GetCommError() failed!\n";
		return PCS_ERR_OTHER;
	}

	int outError = 0;
	if(errors & CE_OVERRUN)
	{
		outError |= PCS_ERR_OVERRUN;
		errors ^= CE_OVERRUN;
	}
	if(errors & CE_RXPARITY)
	{
		outError |= PCS_ERR_PARITY;
		errors ^= CE_RXPARITY;
	}
	if(errors & CE_FRAME)
	{
		outError |= PCS_ERR_FRAMING;
		errors ^= CE_FRAME;
	}
	if(errors & CE_BREAK)
	{
		outError |= PCS_ERR_BREAK;
		errors ^= CE_BREAK;
	}
	if(errors & CE_RXOVER)
	{
		outError |= PCS_ERR_RXOVER;
		errors ^= CE_RXOVER;
	}
	if(errors & CE_TXFULL)
	{
		outError |= PCS_ERR_TXOVER;
		errors ^= CE_TXFULL;
	}
	//other error
	if(errors)
		outError |= PCS_ERR_OTHER;

	return outError;
}

//Win32 Serial support: ADB 01/15/07
int PCSerial::IsActive()
{
	//return PCSerialActive(this);
	return enabled;
}


//Win32 Serial support: ADB 01/15/07
int PCSerial::ModemStatus()
{
	//return PCSerialModemStatus(this);
	DWORD modem;
	if(!GetCommModemStatus(hComm, &modem))
	{
		DEBUG_STREAM << "ERROR: GetCommModemStatus() failed!\n";
		return 0;
	}

	return modem;
}
