/* 
 * This software is licensed under terms of the GNU GENERAL PUBLIC LICENSE
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software  is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Source code is available at http://ppcenter.free.fr
 */

#ifndef _SERIALCOMM_H_
#define _SERIALCOMM_H_

////////////////////////////////////////////////////////////////////////
// Serial port communication class.                                   //
// Windows API commands (CreateFile, WriteFile, etc) wrapper.         //
////////////////////////////////////////////////////////////////////////


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

/* Error codes are defined here. */
#include "../common/satcom.h"
/* Logging function. */
#include "../common/log.h"


/* Define if you want a dummy COM port. */
//#define COMM_DEBUG


class SerialComm {
public:

    /**
     * Constructor : define connection settings, don't open yet.
     * Example :
     *  BAUD      : 375000  (375Kbit/s)
     *  PARITY    : None
     *  DATA BITS : 8
     *  STOP BITS : 2
     *   -> "baud=375000 parity=N data=8 stop=2"
    **/
    SerialComm(char* port, char* option, int readtimeout, int writetimeout)
        : _is_opened(0)
        , _errorvalue(SC_NOERROR)
    {

        strncpy(_port  , port  , sizeof(_port)  ); _port  [sizeof(_port)   - 1] = '\0';
        strncpy(_option, option, sizeof(_option)); _option[sizeof(_option) - 1] = '\0';
        _readtimeout  = readtimeout;
        _writetimeout = writetimeout;
    }
    /* Destructor : if opened, close the serial port. */
    ~SerialComm()
    {
        /* Close port. */
        if(_is_opened) CloseHandle(_commport);
    }

    /* Get last error code. */
    int GetErrorCode(void) { return _errorvalue; }



    /* Open serial port and set-up communication options. */
    int Start(void)
    {
#ifdef COMM_DEBUG // Debug
        return SC_NOERROR;
#endif

        scl_log(SCLT_DLLPG, 5, "[COMM::Start]Start !");
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]port=0x%08X, %s", _port, _port);

        /* Open serial port. */
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]CreateFile ...");
        _commport = CreateFile(_port,
                        GENERIC_READ | GENERIC_WRITE,
                        0, NULL, OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL, NULL);
        /* Bad handle check (= open failure). */
        if (_commport == INVALID_HANDLE_VALUE)
        {
            return SetError(SC_ERROR_PINIT0, "CreateFile failure");
        }
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]_commport=0x%08X", _commport);

        /* Get COM port properties. */
        COMMPROP properties;
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]GetCommProperties ...");
        GetCommProperties(_commport, &properties);

        /* Establish communication with COM port. */
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]ClearCommBreak ...");
        ClearCommBreak(_commport);
        DWORD errors;
        COMSTAT stat;
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]ClearCommError ...");
        ClearCommError(_commport, &errors, &stat);

        /* Get COM port communication settings. */
        DCB dcb;
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]GetCommState ...");
        if(!GetCommState(_commport, &dcb))
        {
            return SetError(SC_ERROR_PINIT1, "GetCommState failure");
        }

        /* Set communication settings (syntax is "COMX: BAUD=..."). */
        char settings[sizeof(_port) + sizeof(_option) + 3];
        settings[0] = '\0';
        //strcat(settings, port);
        //strcat(settings, ": ");
        strcat(settings, _option);
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]settings string = %s", settings);

        /* Communication settings (String -> Structure). */
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]BuildCommDCB ...");
        if(!BuildCommDCB(settings, &dcb))
        {
            return SetError(SC_ERROR_PINIT2, "BuildCommDCB failure");
        }
        /* Communication settings (Structure -> COM port). */
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]SetCommState ...");
        if(!SetCommState(_commport, &dcb))
        {
            return SetError(SC_ERROR_PINIT3, "SetCommState failure 0x%X", GetLastError());
        }

        /* Clear all buffers in the COM port. */
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]PurgeComm ...");
        PurgeComm(_commport, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_TXABORT | PURGE_RXABORT);

        /* Set COM port timeouts. */
        COMMTIMEOUTS time_outs;
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]GetCommTimeouts ...");
        GetCommTimeouts(_commport, &time_outs);
        time_outs.ReadTotalTimeoutConstant = _readtimeout;
        time_outs.WriteTotalTimeoutConstant = _writetimeout;
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]SetCommTimeouts ...");
        SetCommTimeouts(_commport, &time_outs);

        _is_opened = 1;
        scl_log(SCLT_DLLPG, 5, "[COMM::Start]OK !");
        return SC_NOERROR;
    }

    /* Send data. */
    int Send(void* buffer, int sendsize, int* sendcount)
    {
#ifdef COMM_DEBUG // Debug
        return SC_NOERROR;
#endif
        if(!_is_opened) return SetError(SC_ERROR_PNOTOPEN, "Send failure : port is not opened");

        DWORD tmp = 0;
        scl_log(SCLT_DLLPG, 5, "[COMM::Send]Send sendsize = %d", sendsize);
        if (!WriteFile(_commport, buffer, sendsize, &tmp, NULL))
        {
            return SetError(SC_ERROR_PSND0, "WriteFile failure");
        }
        scl_log(SCLT_DLLPG, 5, "[COMM::Send]Send sendcount = %d", tmp);
        if(sendcount != NULL) {*sendcount = tmp;}

        /* Could send all the data ?. */
        if(sendsize != tmp)
        {
            return SetError(SC_ERROR_PSND0, "Could not send all the data (Wanted %d bytes, but sent %d bytes)", sendsize, tmp);
        }

        return SC_NOERROR;
    }


    /* Receive data. */
    int Receive(void* buffer, int receivesize, int* receivecount)
    {
#ifdef COMM_DEBUG // Debug
        return SC_NOERROR;
#endif
        if(!_is_opened) return SetError(SC_ERROR_PNOTOPEN, "Receive failure : port is not opened");

        DWORD tmp = 0;
        memset(buffer, 0, receivesize);
        scl_log(SCLT_DLLPG, 5, "[COMM::Receive]Receive receivesize = %d", receivesize);
        if(!ReadFile(_commport, buffer, receivesize, &tmp, NULL))
        {
            return SetError(SC_ERROR_PRCV0, "ReadFile failure");
        }
        scl_log(SCLT_DLLPG, 5, "[COMM::Receive]Receive receivecount = %d, test = %02X", tmp, ((unsigned char*)buffer)[1]);
        if(receivecount != NULL) {*receivecount = tmp;}

        /* Could receive all the data ? */
        if(receivesize != tmp)
        {
            return SetError(SC_ERROR_PSND0, "Could not receive all the data (wanted %d bytes, but received %d bytes)", receivesize, tmp);
        }

        return SC_NOERROR;
    }


private:
    char _port[1024];   /* Port name (ex : "COM1"). */
    char _option[1024]; /* Connection option (ex : "baud=375000 parity=N data=8 stop=2"). */
    int _readtimeout, _writetimeout; /* msec unit. */

    int SetError(int error, const char *string, ...)
    {
        va_list argptr;
        char buffer[8192];

        /* Display error. */
        va_start (argptr, string);
        vsnprintf (buffer, sizeof(buffer), string, argptr);
        va_end (argptr);
        scl_log(SCLT_DLLPG, 5, " === Serial port error : %s", buffer);

        _errorvalue = error;
        return error;
    }

    int _errorvalue;
    HANDLE _commport;
    int _is_opened;
};


#endif // _SERIALCOMM_H_


