#include "serial.h"
#include "time.h"



/***************************************************************************//**
   Открытие com-порта по его имени 
   @param[in] filename   Имя порта (в Windows - COM1, COM2 и т.д.)
   @return               Описатель открытого порта (>0), если 0 или <0 - ошибка
   ****************************************************************************/
t_com_hnd com_open(const char* filename)
{
#ifdef _WIN32
    return CreateFile(filename, GENERIC_READ | GENERIC_WRITE,
                      FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
                      FILE_FLAG_OVERLAPPED, NULL);
#endif
}

/***************************************************************************//**
    Закрытие ранее открытого порта
    @param[in] port  Описатель порта
    @return          Код ошибки
  *****************************************************************************/
int com_close(t_com_hnd port)
{
#ifdef _WIN32
    return (CloseHandle((HANDLE)port)) ? 0 : COM_ERR_CLOSE;
#endif
}



int f_com_read_ll(t_com_hnd port, unsigned char* buff, int size, int timeout)
{
    int send_size = size;
    int res = 0;
    int bytes_read = 0;

#ifdef _WIN32
    OVERLAPPED Ov;
    // инициализируем OVERLAPPED структуру
    memset(&Ov, 0, sizeof(OVERLAPPED));
    Ov.hEvent = CreateEvent(NULL, FALSE , FALSE, NULL);
    if(!Ov.hEvent)
    {
        res = COM_ERR_ALLOC_RESOURCE;
    }
    // посылаем асинхронный запрос на сбор необходимого кол-ва данных
    else if(!ReadFile(port, buff, send_size, NULL, &Ov))
    {
        if(GetLastError() != ERROR_IO_PENDING)
        {
            CancelIo(port);
            CloseHandle(Ov.hEvent);
            res = COM_ERR_READ;
        }
    }

    if (!res)
    {
        // ждём окончания выполнения запроса
        if (WaitForSingleObject(Ov.hEvent, timeout) == WAIT_TIMEOUT)
        {
            CancelIo(port);
            CloseHandle(Ov.hEvent);
            res = COM_ERR_TIMEOUT;
        }
            // попробуем получить кол-во реально переданных байт данных
        else if(!GetOverlappedResult(port, &Ov, &bytes_read, TRUE))
        {
            CancelIo(port);
            CloseHandle(Ov.hEvent);
            res = COM_ERR_READ_RESULT;
        }
        else
        // закроем событие
        {
            CloseHandle(Ov.hEvent);
        }
    }
#endif
    return (res) ? res : bytes_read;
}



/***************************************************************************//**
 Чтение данных из порта
 @param[in] port    Описатель порта
 @param[out] buff   Указатель на буфер, куда складываются принятые данные
 @param[in] size    Количество запрашиваемых данных на прием (байт)
 @param[in] timeout Таймаут на прием в мс
 @return            < 0 - код ошибки, >=0 - кол-во переданных данных
  *****************************************************************************/
int com_read(int port, unsigned char* buff, int size, int timeout)
{
    int recv = 0;
    int res = 0;
    clock_t start_time, cur_time, clk_cnt;
    start_time = cur_time = clock();
    clk_cnt = (int)timeout * CLOCKS_PER_SEC/1000;
    while (((cur_time - start_time) < clk_cnt) && (recv < size) && !res)
    {
        res = f_com_read_ll(port, &buff[recv], size - recv, timeout - (int)(cur_time-start_time)*1000/CLOCKS_PER_SEC);
        if (res > 0)
        {
            recv+=res;
            res = 0;
        }
        cur_time = clock();
    }
    return res ? res : recv;
}




int f_com_write_ll(t_com_hnd port, unsigned char* buff, int size, int timeout)
{
    int res = 0;
    int write_bytes = 0;

#ifdef _WIN32
    OVERLAPPED Ov;
    // инициализируем OVERLAPPED структуру
    memset(&Ov, 0, sizeof(OVERLAPPED));
    // создаём событие
    Ov.hEvent = CreateEvent(NULL, FALSE , FALSE, NULL);
    if(!Ov.hEvent)
    {
        res = COM_ERR_ALLOC_RESOURCE;
    }
    // посылаем асинхронный запрос на сбор необходимого кол-ва данных
    else if(!WriteFile(port, buff, size, &write_bytes, &Ov))
    {
        if (GetLastError() != ERROR_IO_PENDING)
        {
            CancelIo(port);
            CloseHandle(Ov.hEvent);
            res = COM_ERR_WRITE_RESULT;
        }
    }

    if (!res)
    {
        // ждём окончания выполнения запроса
        if(WaitForSingleObject(Ov.hEvent, timeout) == WAIT_TIMEOUT)
        {
            CancelIo(port);
            CloseHandle(Ov.hEvent);
            res = COM_ERR_TIMEOUT;
        }
        // попробуем получить кол-во реально переданных байт данных
        else if(!GetOverlappedResult(port, &Ov, &write_bytes, TRUE))
        {
            CancelIo(port);
            CloseHandle(Ov.hEvent);
            res = COM_ERR_WRITE_RESULT;
        }
        // закроем событие
        else
        {
            CloseHandle(Ov.hEvent);
        }
    }
#endif
    return (res) ? res : write_bytes;
}


/***************************************************************************//**
  Запись данных в порта
  @param[in] port       Описатель порта
  @param[in] buff       Указатель на буфер с данными для передачи
  @param[in] size       Количество данных для передачи
  @param[in] timeout    Таймаут на передачу в мс
  @return               < 0 - код ошибки, >=0 - кол-во переданных данных
  *****************************************************************************/
int com_write(int port, unsigned char* buff, int size, int timeout)
{
    int sended = 0;
    int res = 0;
    clock_t start_time, cur_time, clk_cnt;
    start_time = cur_time = clock();
    clk_cnt = (int)timeout * CLOCKS_PER_SEC/1000;
    while (((cur_time - start_time) < clk_cnt) && (sended < size) && !res)
    {
        res = f_com_write_ll(port, &buff[sended], size - sended, timeout - (int)(cur_time-start_time)*1000/CLOCKS_PER_SEC);
        if (res > 0)
        {
            sended+=res;
            res = 0;
        }
        cur_time = clock();
    }

    return res ? res : sended;
}


int com_reset(t_com_hnd port)
{
#ifdef _WIN32
    int err = PurgeComm(port, PURGE_RXABORT | PURGE_RXCLEAR
                     | PURGE_TXABORT | PURGE_TXCLEAR) ? 0 : COM_ERR_RESET;
    if (!err)
        FlushFileBuffers( port );

    return err;
#endif
}

int com_set_br(t_com_hnd port, int br, int parity, int flags)
{
    int res = 0;
#ifdef _WIN32
    DCB settings;
    if (!GetCommState((HANDLE)port, &settings))
        res = COM_ERR_GET_SETTINGS;
    else
    {

        settings.fBinary = 1;
        settings.fOutxCtsFlow = 0;
        settings.fOutxDsrFlow = 0;
        settings.fDtrControl = (flags & COM_SET_FLAGS_DTR_CTL_EN) ?
                    DTR_CONTROL_ENABLE : DTR_CONTROL_DISABLE;
        settings.fDsrSensitivity = 0;
        settings.fTXContinueOnXoff = 0;
        settings.fOutX = 0;
        settings.fInX = 0;
        settings.fErrorChar = 0;
        settings.fNull = 0;
        settings.fRtsControl = (flags & COM_SET_FLAGS_RTS_CTL_EN) ?
                    RTS_CONTROL_ENABLE : RTS_CONTROL_DISABLE;
        settings.fAbortOnError = 0;
        settings.fDummy2 = 0;
        settings.wReserved = 0;
        settings.XonLim = 0x800;
        settings.XoffLim = 0x0200;

        settings.StopBits = (flags & COM_SET_FLAGS_TWO_STOP_BITS) ? TWOSTOPBITS : ONESTOPBIT;
        settings.XonChar = 0x11;
        settings.XoffChar = 0x13;
        settings.ErrorChar = 0;
        settings.EofChar = 0;
        settings.EvtChar = 0;
        settings.wReserved1 = 0;

        settings.BaudRate = br;
        settings.ByteSize = 8;
        settings.fParity = !(parity == COM_PARITY_NONE);
        if (parity == COM_PARITY_ODD)
            settings.Parity = ODDPARITY;
        else if (parity == COM_PARITY_EVEN)
            settings.Parity = EVENPARITY;

        if (!SetCommState(port, &settings))
            res = COM_ERR_SET_SETTINGS;


    }
#endif
    return res;
}
