#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "commdev.h"

DWORD CommDev::BaudTable[] = 
{
    CBR_110, CBR_300, CBR_600, CBR_1200, CBR_2400,
    CBR_4800, CBR_9600, CBR_14400, CBR_19200, CBR_38400,
    CBR_56000, CBR_128000, CBR_256000};

DWORD CommDev::ParityTable[] = 
{
    NOPARITY, EVENPARITY, ODDPARITY, MARKPARITY, SPACEPARITY
};

DWORD CommDev::StopBitsTable[] = 
{
    ONESTOPBIT, ONE5STOPBITS, TWOSTOPBITS
};

CommDev::CommDev(int port)
{
    setDefaults();
    bPort = (BYTE)port;
}

CommDev::CommDev(const char* portname)
{
    setDefaults();
    int port;
    sscanf(portname, "COM%d", &port);
    bPort = (BYTE)port;
}

CommDev::~CommDev()
{
    if (fConnected)
        CloseConnection();
}

bool CommDev::setDefaults()
{
    // initialize 
    // rate = 9600, 1 start bit, 8 data bits, 1 stop bit , even parity
    idComDev      = 0;
    fConnected    = false;
    bPort         = 1;
    dwBaudRate    = CBR_9600;
    bByteSize     = 8;
    bFlowCtrl     = 0; //FC_RTSCTS;
    bParity       = EVENPARITY;
    bStopBits     = ONESTOPBIT;
    fXonXoff      = false;
    
    CommWatchProc = NULL;
    dwThreadID    = 0;
    hWatchThread  = NULL;
    
    return true;
}

void CommDev::setParity(int parity)
{
    bParity = parity; //ParityTable[parity];
}

void CommDev::setRate(int rate)
{
    int bitRate;
    switch (rate) {
        case 600:
            bitRate = CBR_600;
            break;
        case 1200:
            bitRate = CBR_1200;
            break;
        case 2400:
            bitRate = CBR_2400;
            break;
        case 4800:
            bitRate = CBR_4800;
            break;
        case 9600:
            bitRate = CBR_9600;
            break;
        case 19200:
            bitRate = CBR_19200;
            break;
        case 38400:
            bitRate = CBR_38400;
            break;            
        case 56000:
            bitRate = CBR_56000;
            break;            
    }
 
    dwBaudRate = bitRate;
}

void CommDev::setStopBits(int stopbits)
{
    bStopBits = (unsigned char)StopBitsTable[stopbits];
}

void CommDev::SetCommWatchProc(LPTHREAD_START_ROUTINE proc)
{
    CommWatchProc = proc;
}

bool CommDev::OpenConnection()
{
    WCHAR szPort[15];
    bool fRetVal;
    HANDLE hCommWatchThread;
    DWORD    dwThreadID;
    COMMTIMEOUTS CommTimeOuts;

    // load the COM prefix string and append port number
    wsprintf(szPort, L"COM%d:", bPort);

    // open COMM device
    if ((idComDev = CreateFile(szPort, GENERIC_READ | GENERIC_WRITE, 0, // exclusive access
         NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == (HANDLE)-1)
        return false;
    else 
    {
        // get any early notifications
        SetCommMask(idComDev, EV_RXCHAR);

        // setup device buffers
        SetupComm(idComDev, RXQUEUE, TXQUEUE);

        // purge any information in the buffer
        PurgeComm(idComDev, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);

        // set up for overlapped I/O
        CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF;
        CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
        CommTimeOuts.ReadTotalTimeoutConstant = 1000;
        // CBR_9600 is approximately 1byte/ms. For our purposes, allow
        // double the expected time per character for a fudge factor.
        CommTimeOuts.WriteTotalTimeoutMultiplier = 2*CBR_9600/dwBaudRate;
        CommTimeOuts.WriteTotalTimeoutConstant = 0;
        SetCommTimeouts(idComDev, &CommTimeOuts);
    }

    fRetVal = SetupConnection();
    if (fRetVal) 
    {
        fConnected = true;

        if (CommWatchProc) 
        {
            // Create a secondary thread to watch for an event.
            if (NULL == (hCommWatchThread = CreateThread((LPSECURITY_ATTRIBUTES) NULL, 0, 
                CommWatchProc, (LPVOID)this, 0, &dwThreadID))) 
            {
                fConnected = false;
                CloseHandle(idComDev);
                fRetVal = false;
            } else {
                dwThreadID = dwThreadID;
                hWatchThread = hCommWatchThread;
            }
        }
        // assert DTR
        if (fConnected) 
            EscapeCommFunction(idComDev, SETDTR);
    } 
    else 
    {
        fConnected = false;
        CloseHandle(idComDev);
    }

    return fRetVal;
}

bool CommDev::SetupConnection()
{
    bool fRetVal;
    BYTE bSet;
    DCB    dcb;

    dcb.DCBlength = sizeof(DCB);

    GetCommState(idComDev, &dcb);

    dcb.BaudRate = dwBaudRate;
    dcb.ByteSize = bByteSize;
    dcb.Parity = bParity;
    dcb.StopBits = bStopBits;

    // setup hardware flow control
    bSet = (BYTE)((bFlowCtrl & FC_DTRDSR) != 0);
    dcb.fOutxDsrFlow = bSet;
    if (bSet)
        dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
    else
        dcb.fDtrControl = DTR_CONTROL_ENABLE;

    bSet = (BYTE) ((bFlowCtrl & FC_RTSCTS) != 0);
    dcb.fOutxCtsFlow = bSet;
    if (bSet)
        dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
    else
        dcb.fRtsControl = RTS_CONTROL_ENABLE;

    // setup software flow control
    bSet = (BYTE)((bFlowCtrl & FC_XONXOFF) != 0);
    dcb.fInX = dcb.fOutX = bSet;
    dcb.XonChar = (bSet) ? ASCII_XON : 0; 
    dcb.XoffChar = (bSet) ? ASCII_XOFF : 0;
    dcb.XonLim = 100;
    dcb.XoffLim = 100;

    // other various settings
    dcb.fBinary = TRUE;
    dcb.fParity = TRUE;

    fRetVal = (bool)SetCommState(idComDev, &dcb);

    return fRetVal;
}

bool CommDev::CloseConnection()
{
    fConnected = false;

    // disable event notification and wait for thread to halt
    SetCommMask(idComDev, 0);

    // block until thread has been halted
    while (dwThreadID != 0) {};

    // drop DTR
    EscapeCommFunction(idComDev, CLRDTR);

    // purge any outstanding reads/writes and close device handle
    PurgeComm(idComDev, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
    CloseHandle(idComDev);

    return true;
}

unsigned int CommDev::Read(LPSTR lpszBlock, DWORD nMaxLength)
{
    BOOL             fReadStat;
    COMSTAT        ComStat;
    DWORD            dwErrorFlags;
    DWORD            dwLength;
    //DWORD            dwError;

    // only try to read number of bytes in queue
    ClearCommError(idComDev, &dwErrorFlags, &ComStat);
    dwLength = (nMaxLength < ComStat.cbInQue) ? nMaxLength : ComStat.cbInQue;

    if (dwLength > 0) 
    {
        fReadStat = ReadFile(idComDev, lpszBlock, dwLength, &dwLength, NULL);
        if (!fReadStat) 
        {
            dwLength = 0;
            ClearCommError(idComDev, &dwErrorFlags, &ComStat);
        }
    }
    return dwLength;
}

bool CommDev::Write(LPSTR lpByte , DWORD dwBytesToWrite)
{
    BOOL                fWriteStat;
    DWORD             dwBytesWritten;
    DWORD             dwErrorFlags;
    //DWORD             dwError;
    DWORD             dwBytesSent=0;
    COMSTAT         ComStat;

    fWriteStat = WriteFile(idComDev, lpByte, dwBytesToWrite, &dwBytesWritten, NULL);
    
    /*#ifdef DEBUG
    LogData(0, lpByte, dwBytesToWrite, bPort);
    #endif*/

    if (!fWriteStat) {
        ClearCommError(idComDev, &dwErrorFlags, &ComStat);
        return false;
    }
    return true;
}

int CommDev::getCTS() const
{
    DWORD dwModemStatus;
    GetCommModemStatus(idComDev, &dwModemStatus);
    return dwModemStatus & MS_CTS_ON;
}

void CommDev::setRTS(int set)
{
    EscapeCommFunction(idComDev, (set) ? SETRTS : CLRRTS);
}

