/**
 * \brief RS232 communication
 * \copyright 2013-2014, IQ² Development GmbH, Neckartenzlingen. All rights reserved.
 */

#include "iqi_define.h"
#include "iqi_rs232.h"


static HANDLE CommPorts[MAX_COMPORT_NUM];

int16 rs232_ScanPorts(uchar08 ucFromPortNo,
                      uchar08 ucUntilPortNo,
                      uchar08 *ucpFoundPorts,
                      char08 *cpErrMsg)
{
  HANDLE hPort;
  char08 cpRealDevice[12];
  int i;

  if (ucFromPortNo > ucUntilPortNo)
  {
    ucFromPortNo += ucUntilPortNo;
    ucUntilPortNo = ucFromPortNo - ucUntilPortNo;
    ucFromPortNo -= ucUntilPortNo;
  }

  if (ucUntilPortNo > 127)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Required port number ('%d') is out of range [1..127].", ucUntilPortNo);
    return ERR_PARAMETER;
  }
  
  *ucpFoundPorts = 0;
  for (i = ucFromPortNo; i <= ucUntilPortNo; i++)
  {                                                                              // search for free com ports
    sprintf_s(cpRealDevice, sizeof(cpRealDevice), "\\\\.\\COM%i", i);

    hPort = CreateFileA(cpRealDevice,
                       GENERIC_READ | GENERIC_WRITE,
                       0,                                                       // must be opened with exclusive-access
                       NULL,                                                    // default security attributes
                       OPEN_EXISTING,                                           // must use OPEN_EXISTING
                       0/*FILE_FLAG_OVERLAPPED*/,                               // not overlapped I/O
                       NULL);                                                   // hTemplate must be NULL for comm devices
   
    if ((hPort != INVALID_HANDLE_VALUE) && CloseHandle(hPort))
    {
      CommPorts[i] = NULL;

      *(ucpFoundPorts++) = i, *ucpFoundPorts = 0;
    }
  }
  
  return ERR_NONE;
}

int16 rs232_GetPortHandle(uint16 uiPortNo,
                          HANDLE *hpPort,
                          char08 *cpErrMsg)
{
  if (uiPortNo > 127)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Required port number ('%d') is out of range [1..127].", uiPortNo);
    return ERR_PARAMETER;
  }

  *hpPort = CommPorts[uiPortNo];
  
  return ERR_NONE;
}

int16 rs232_SetPortHandle(uint16 uiPortNo,
                          HANDLE hPort,
                          char08 *cpErrMsg)
{
  if (uiPortNo > 127)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Required port number ('%d') is out of range [1..127].", uiPortNo);
    return ERR_PARAMETER;
  }

  CommPorts[uiPortNo] = hPort;

  return ERR_NONE;
}

int16 rs232_OpenPort(uint16 uiPortNo,
                     ulong32 ulBaudrate,
                     char08 *cpErrMsg)
{
  return rs232_OpenPortWithTimeout(uiPortNo, ulBaudrate, 1000, cpErrMsg);
}

int16 rs232_OpenPortWithTimeout(uint16 uiPortNo,
                                ulong32 ulBaudrate,
                                uint16 uiTimeoutMs,
                                char08 *cpErrMsg)
{
  int i;
  char08 cpPortName[7], cpRealDevice[12];
  const ulong32 ulSupportedBaudrateList[8] =
    { 9600, 14400, 19200, 28800, 38400, 57600, 115200, 230400 };

  HANDLE hPort;
  COMMTIMEOUTS CommTimeouts;
  DCB dcb;

  if (uiPortNo > 127)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Required port number ('%d') is out of range [1..127].", uiPortNo);
    return ERR_PARAMETER;
  }
  
  for (i = 0; ulSupportedBaudrateList[i] != ulBaudrate; i++)
  {
    if (i != 7) continue;
    
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Unsupported baudrate parameter ('%d').", ulBaudrate);
    return ERR_PARAMETER;
  }
  
  sprintf_s(cpPortName, sizeof(cpPortName), "COM%i", uiPortNo);
  strcpy_s(cpRealDevice, sizeof(cpRealDevice), "\\\\.\\");
  strcat_s(cpRealDevice, sizeof(cpRealDevice), cpPortName);

  hPort = CreateFileA(cpRealDevice,
                     GENERIC_READ | GENERIC_WRITE,
                     0,                                                         // must be opened with exclusive-access
                     NULL,                                                      // default security attributes
                     OPEN_EXISTING,                                             // must use OPEN_EXISTING
                     0/*FILE_FLAG_OVERLAPPED*/,                                 // not overlapped I/O
                     NULL);                                                     // hTemplate must be NULL for comm devices
   
  if (hPort == INVALID_HANDLE_VALUE)
  {
    rs232_ClosePort(uiPortNo, cpErrMsg);
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Open com port ('%s') operation failed.", cpPortName);
    return ERR_OPERATION;
  }
  
  GetCommTimeouts(hPort, &CommTimeouts);                                        // Set the communication timeouts
  
  // Change the COMMTIMEOUTS structure settings
  CommTimeouts.ReadIntervalTimeout          = uiTimeoutMs;
  CommTimeouts.ReadTotalTimeoutConstant     = uiTimeoutMs * 4;
  CommTimeouts.ReadTotalTimeoutMultiplier   = uiTimeoutMs / 5;
  CommTimeouts.WriteTotalTimeoutMultiplier  = uiTimeoutMs / 5;
  CommTimeouts.WriteTotalTimeoutConstant    = uiTimeoutMs;

  // Set the timeout parameters for all read and write operations
  if (!SetCommTimeouts(hPort, &CommTimeouts))
  {
    rs232_ClosePort(uiPortNo, cpErrMsg);
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Set com port ('%s') timeouts operation failed.", cpPortName);
    return ERR_OPERATION;
  }
  
  ZeroMemory(&dcb, sizeof(DCB));                                                // Initialize the DCB structure
  
  dcb.DCBlength = (unsigned long)sizeof(DCB);  
  dcb.BaudRate = ulBaudrate;                                                    // baud rate
  dcb.fBinary  = TRUE;                                                          // Microsoft® Win32® API does not support nonbinary
  dcb.ByteSize = 8;                                                             // data size, xmit and rcv
  dcb.Parity   = NOPARITY;                                                      // parity bit
  dcb.StopBits = ONESTOPBIT;                                                    // stop bit

  if (!SetCommState(hPort, &dcb))
  {
    rs232_ClosePort(uiPortNo, cpErrMsg);
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Set com port ('%s') state operation failed.", cpPortName);
    return ERR_OPERATION;
  }
 
  PurgeComm(hPort, PURGE_RXCLEAR);                                              // Clear Input Buffer.
  PurgeComm(hPort, PURGE_TXCLEAR);                                              // Clear Output Buffer.

  CommPorts[uiPortNo] = hPort;

  return ERR_NONE;
}

int16 rs232_ClosePort(uint16 uiPortNo,
                      char08 *cpErrMsg)
{
  if (!CommPorts[uiPortNo])
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Invalid com port ('COM%i') handle.", uiPortNo);
    return ERR_PARAMETER;
  }

  if (!CloseHandle(CommPorts[uiPortNo]))
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Close com port ('COM%i') operation failed.", uiPortNo);
    return ERR_OPERATION;
  }
  
  CommPorts[uiPortNo] = NULL;
  
  return ERR_NONE;
}

int16 rs232_ChangeBaudrate(uint16 uiPortNo,
                           ulong32 ulBaudrate,
                           char08 *cpErrMsg)
{
  CHK_RETURN(rs232_ClosePort(uiPortNo, cpErrMsg));
  return rs232_OpenPort(uiPortNo, ulBaudrate, cpErrMsg);
}

int16 rs232_Read(uint16 uiPortNo,
                 uchar08 *ucpBuf,
                 ulong32 ulBytesCount,
                 char08 *cpErrMsg)
{
  long64 l64BytesRead = 0;
  unsigned long *lpBytesRead = (unsigned long*)&l64BytesRead;
  
  if (!CommPorts[uiPortNo])
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Invalid com port ('COM%i') handle.", uiPortNo);
    return ERR_PARAMETER;
  }

  if (!ReadFile(CommPorts[uiPortNo],                                            // Port handle
                ucpBuf,                                                         // Pointer to the data to read 
                ulBytesCount,                                                   // Number of bytes to write
                lpBytesRead,                                                    // Pointer to the number of bytes read
                NULL))                                                          // Must be NULL for Windows CE
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Read com port ('COM%i') data operation failed.", uiPortNo);
    return ERR_OPERATION;
  }
  
  if (ulBytesCount != (*lpBytesRead))
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Read com port ('COM%i') data operation failed by timeout.", uiPortNo);
    return ERR_OPERATION;
  }

  return ERR_NONE;
}

int16 rs232_Write(uint16 uiPortNo,
                  uchar08 *ucpBuf,
                  ulong32 ulBytesCount,
                  char08 *cpErrMsg)
{ 
  long64 l64BytesWritten = 0;
  unsigned long *lpBytesWritten = (unsigned long*)&l64BytesWritten;
  
  if (!CommPorts[uiPortNo])
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Invalid com port ('COM%i') handle.", uiPortNo);
    return ERR_PARAMETER;
  }

  if (!WriteFile (CommPorts[uiPortNo],                                          // Port handle
                  ucpBuf,                                                       // Pointer to the data to write 
                  ulBytesCount,                                                 // Number of bytes to write
                  lpBytesWritten,                                               // Pointer to the number of bytes written
                  NULL))                                                        // Must be NULL for Windows CE
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Write com port ('COM%i') data operation failed.", uiPortNo);
    return ERR_OPERATION;
  }
  
  if (ulBytesCount != (*lpBytesWritten))
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Write com port ('COM%i') data operation failed by timeout.", uiPortNo);
    return ERR_OPERATION;
  }
  
  return ERR_NONE;
}

int16 rs232_DMWriteRead(uint16 uiPortNo,
                        uchar08 *ucpSendBuf,
                        uchar08 *ucpRecvBuf,
                        ulong32 ulBytesCount,
                        char08 *cpErrMsg)
{
  CHK_RETURN(rs232_Write(uiPortNo, ucpSendBuf, (*ucpSendBuf) + 1, cpErrMsg));
  CHK_RETURN(rs232_Read(uiPortNo, ucpRecvBuf, 1, cpErrMsg));
  
  if ((ulong32)(ucpRecvBuf[0] + 1) > ulBytesCount)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Too small data2 buffer depth ('%d', but '%d' is needed).", ulBytesCount, (*ucpRecvBuf) + 1);
    return ERR_BUFFER_OVERFLOW;
  }

  CHK_RETURN(rs232_Read(uiPortNo, ucpRecvBuf + 1, *ucpRecvBuf, cpErrMsg));
  
  return (*ucpRecvBuf) + 1;
}
