/**
 * \brief iqInterface common communication functions
 * \copyright 2013-2014, IQ� Development GmbH, Neckartenzlingen. All rights reserved.
 */

#include "iqi_define.h"
#include "iqi_rs232.h"
#include "iqi_ethernet.h"
#include "iqi_ethernet_internal.h"
#include "iqi_comm.h"

#define PRODUCT_INFO_SIZE 24

#define MAX_RETRY_NUM 3

enum                                                                           
{
  IQI_CMD_WRITE_PARAM   = 0x11,
  IQI_CMD_READ_PARAM    = 0x12
};

typedef int16 (*SendRecvFuncT)(uint16 uiPortNo,
                               uchar08 *ucpBuf,
                               ulong32 ulBytesCount,
                               char08 *cpErrMsg);

/** \brief Default connect action for _TrySearchPortNo
 *
 * \param[in]   uiPortNo      IQI's com-port number
 * \param[out]  cpErrMsg            an error message (minimum 256 symbols!)
 *
 */
static int16 _DefaultConnectAction(uint16 uiPortNo, char08 *cpErrMsg);

/**
 * \brief Search iqInterface on ports range using fpTryConnectAction
 *
 * \param[in]   ucFromPortNo        com port number search from
 * \param[in]   ucUntilPortNo       com port number until search
 * \param[out]  cpErrMsg            an error message (minimum 256 symbols!)
 *
 * \return the founded IQI'c com-port number
 */
static int16 _TrySearchPortNo(iqi_TryConnectFuncT fpTryConnectAction,
                              uchar08 ucFromPortNo,
                              uchar08 ucUntilPortNo,
                              char08 *cpErrMsg);

/**
 * \brief Calculate 6-bit compressed checksum according to the IO-Link specification
 *
 * \param[in]       ucChannel         a communication channel
 * \param[in]       ucCommand         a required command
 * \param[in]       ucpBuf            pointer on a buffer
 * \param[in]       uiBufSize         count of buffer bytes
 *
 * \return 6-bit checksum
 */
static uchar08 _ucCalc6bitChecksum(uchar08 ucChannel,
                                   uchar08 ucCommand,
                                   uchar08 *ucpBuf,
                                   uint16 uiBufSize);

static 
void _SetSendRecv(uint16 uiPortNo, SendRecvFuncT * send, SendRecvFuncT * recv)
{
  if (uiPortNo < MAX_COMPORT_NUM)
  {
    *send = rs232_Write;
    *recv = rs232_Read;
  }
  else
  {
    *send = enet_Send;
    *recv = enet_Recv;
  }
}

static int16 _SendRecvWithRetry(uint16 uiPortNo,
                                uchar08 ucChannel,
                                uchar08 ucCommand,
                                uchar08 *ucpSendBuf,
                                uint16 uiBytesCount,
                                uchar08 *ucpRecvBuf,
                                uint16 uiRecvBufSize,
                                uchar08 retryNum,
                                char08 *cpErrMsg)
{
  int16 error = ERR_NONE;
  uchar08 actSize = 0;
  uchar08 recv_buffer[256] = { 0 };
  uchar08 send_buffer[256] = { 0 };
  SendRecvFuncT send;
  SendRecvFuncT recv;
  int16 retry = retryNum;

  _SetSendRecv(uiPortNo, &send, &recv);

  if (uiBytesCount > 254)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "IQI communication send data size is too big ('%d', more than 254 bytes).", uiBytesCount);
    return ERR_PARAMETER;
  }

  send_buffer[0] = uiBytesCount + 2;
  send_buffer[1] = ucChannel | _ucCalc6bitChecksum(ucChannel, ucCommand, ucpSendBuf, uiBytesCount);
  send_buffer[2] = ucCommand;
  memcpy(send_buffer + 3, ucpSendBuf, uiBytesCount);
  uchar08 send_data_length = uiBytesCount + 3;

  while (retry > 0)
  {
    error = send(uiPortNo, send_buffer, send_data_length, cpErrMsg);
    if (error < ERR_NONE)
    {
        continue;
    }

    //get message size
    error = recv(uiPortNo, recv_buffer, 1, cpErrMsg);
    actSize = recv_buffer[0];
    if (actSize < 2)
    {
        snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Invalid message size.");
        error = ERR_DATA;
    }

    error = recv(uiPortNo, recv_buffer + 1, actSize, cpErrMsg);

    uchar08 cks = recv_buffer[1] & 0x3F;
    if (error >= ERR_NONE && 
        cks != _ucCalc6bitChecksum(recv_buffer[1] & 0xC0, recv_buffer[2], recv_buffer + 3, actSize))
    {
      snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "IQI telegram response checksum error.");
      error = ERR_DATA;
    }
    else
    {
      break;
    }

    retry--;
  }

  if ((error < ERR_NONE) || (actSize == 0))
  {
      return error;
  }

  if ((recv_buffer[1] & 0xC0) == CH_ERR)
  {
    if (recv_buffer[3] == 0x56)
    {
        snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
            "ISDU %s index: %u, subindex: %u cached operation failed. Error code: ('0x%04X')",
            recv_buffer[4] == 1 ? "read" : "write",
            GET_BIGEND_UINT16(recv_buffer + 5),
            recv_buffer[7],
            GET_BIGEND_UINT16(recv_buffer + 8));
    }
    else
    {
        uint16 errCode;
        errCode = GET_BIGEND_UINT16(recv_buffer + 2);
        snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
            "IQI communication error code ('0x%04X').", errCode);
    }
    return ERR_DATA;
  }
  else if ((recv_buffer[1] & 0xC0) != ucChannel)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Wrong IQI response communication channel.");
    return ERR_DATA;
  }
  
  if (recv_buffer[2] != (0x80 | ucCommand))
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "IQI command confirmation is absent.");
    return ERR_DATA;
  }

  actSize -= 2; //remove header size

  memcpy(ucpRecvBuf, recv_buffer + 3, actSize);
        
  return actSize;
}

int16 comm_SendRecv(uint16 uiPortNo,
                    uchar08 ucChannel,
                    uchar08 ucCommand,
                    uchar08 *ucpSendBuf,
                    uint16 uiBytesCount,
                    uchar08 *ucpRecvBuf,
                    uint16 uiRecvBufSize,
                    char08 *cpErrMsg)
{
  return _SendRecvWithRetry(uiPortNo, ucChannel, ucCommand, ucpSendBuf, uiBytesCount, 
    ucpRecvBuf, uiRecvBufSize, MAX_RETRY_NUM, cpErrMsg);
}

static uchar08 _ucCalc6bitChecksum(uchar08 ucChannel,
                                   uchar08 ucCommand,
                                   uchar08 *ucpBuf,
                                   uint16 uiBufSize)
{
  uchar08 i1, i2, hash;
  int i;

  hash = 0x52 ^ ucChannel ^ ucCommand;
  for (i = 0; i < uiBufSize; i++)
  {
    hash ^= ucpBuf[i];
  }

  // compress 8bit result to 6 bit
  i1 = hash ^ (hash << 4), i1 ^= (i1 >> 2);
  i2 = hash ^ (hash >> 1);
  return (i1 & 0x30)
    | ((i2 & 0x40) >> 3) | ((i2 & 0x10) >> 2) | ((i2 & 0x04) >> 1) | (i2 & 0x01);
}

int16 comm_SearchPortNo(uchar08 ucFromPortNo,
                        uchar08 ucUntilPortNo,
                        char08 *cpErrMsg)
{
  return _TrySearchPortNo(&_DefaultConnectAction, ucFromPortNo, ucUntilPortNo, cpErrMsg);
}

DLL_API
int16 comm_ReadSerialNumber(uint16 uiPortNo, ulong64 * ul64pSerialNumber, char08 *cpErrMsg)
{
  int16 size;
  uchar08 i;
  uchar08 ucpProductInfoBuf[PRODUCT_INFO_SIZE];
  size = comm_ReadParameter(uiPortNo, IQParSerialNumber, ucpProductInfoBuf, PRODUCT_INFO_SIZE, cpErrMsg);
  if (size != PRODUCT_INFO_SIZE)
    return ERR_INTERNAL;
  *ul64pSerialNumber = 0;
  for (i = 0; i < 8; ++i)
    *ul64pSerialNumber += ((ulong64)(ucpProductInfoBuf[i + 2])) << (8*(7 - i));
  return ERR_NONE;
}

DLL_API
int16 enet_Search(enet_ConnectionT * pFoundConnections, 
                  uchar08 ucMaxConnectionsNumber, 
                  uchar08 * ucpFoundConnectionsNumber,
                  uint16 uiSendRecvTimoutMs, 
                  char08 *cpErrMsg)
{
  CHK_RETURN(enet_findIQInterfaces(pFoundConnections, ucMaxConnectionsNumber, ucpFoundConnectionsNumber, 
                                   comm_ReadSerialNumber, uiSendRecvTimoutMs, cpErrMsg));
  if (*ucpFoundConnectionsNumber == 0)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "No iqInterface network connections are found");
    return ERR_INTERNAL;
  }
  return ERR_NONE;
}

int16 comm_ReadParameter(uint16 uiPortNo,
                         uchar08 ucParNo,
                         uchar08 *ucpBuf,
                         uint16 uiBufSize,
                         char08 *cpErrMsg)
{
  return comm_SendRecv(uiPortNo,
    CH_IOL, IQI_CMD_READ_PARAM, &ucParNo, 1, ucpBuf, uiBufSize, cpErrMsg);
}

int16 comm_WriteParameter(uint16 uiPortNo,
                          uchar08 ucParNo,
                          uchar08 *ucpBuf,
                          uint16 uiBytesCount,
                          char08 *cpErrMsg)
{
  uchar08 buf[33];
  
  if (uiBytesCount > 32)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
      "Too big number of communication parameter bytes ('%d').", uiBytesCount);
    return ERR_PARAMETER;
  }
  
  *buf = ucParNo, memcpy(buf + 1, ucpBuf, uiBytesCount);
  return comm_SendRecv(uiPortNo,
    CH_IOL, IQI_CMD_WRITE_PARAM, buf, uiBytesCount + 1, NULL, 0, cpErrMsg);
}

static
int16 _DefaultConnectAction(uint16 uiPortNo, char08 *cpErrMsg)
{
  uchar08 mode;
  uchar08 ucParNo = IQParIQIOperMode;
  return _SendRecvWithRetry(uiPortNo,
    CH_IOL, IQI_CMD_READ_PARAM, &ucParNo, 1, &mode, 1, 1, cpErrMsg);
}

static 
int16 _TrySearchPortNo(iqi_TryConnectFuncT fpTryConnectAction,
                       uchar08 ucFromPortNo,
                       uchar08 ucUntilPortNo,
                       char08 *cpErrMsg)
{
  uint16 i;
  
  for (i = ucFromPortNo; i <= ucUntilPortNo; i++)
  {
    if(rs232_OpenPortWithTimeout(i, 115200, 200, cpErrMsg))
      continue;
      
    if (fpTryConnectAction(i, cpErrMsg) < 0)
    {
      rs232_ClosePort(i, cpErrMsg);
      continue;
    }

    if (rs232_ClosePort(i, cpErrMsg))
      continue;
  
    return i;
  }
  
  snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
    "IQI hasn't been found inside [COM%d..COM%d].", ucFromPortNo, ucUntilPortNo);
  return ERR_NOTFOUND;
}
