/**
 * \brief TCP/IPv4/ethernet/LAN communication
 * \copyright 2013-2014, IQ² Development GmbH, Neckartenzlingen. All rights reserved.
 */

#define WIN32_LEAN_AND_MEAN

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

#include <winsock2.h>
#include <iphlpapi.h>

#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Mswsock.lib")
#pragma comment(lib, "AdvApi32.lib")
#pragma comment(lib, "IPHLPAPI.lib")

#define UDP_BROADCAST_PORT    30718
#define TCP_PORT              10001
#define MAX_CONNECTION_NUMBER 128

#define EN_CHKRET_ERR(res,err_mes)\
  if (res)\
  {\
      snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, err_mes, WSAGetLastError());\
      closesocket(s);\
      return ERR_INTERNAL;\
  }

#define CHECK_STARTUP \
  if (isStarted == FALSE)\
  {\
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Can not perform any network operation without enet_Startup");\
    return ERR_INTERNAL;\
  }

#define CHECK_CONNECTION_NUMBER(c)\
  if (c < 0 || c >= MAX_CONNECTION_NUMBER)\
  {\
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Wrong TCP connection number %d", c);\
    return ERR_INTERNAL;\
  }

static boolean isStarted = FALSE;
static SOCKET Connections[MAX_CONNECTION_NUMBER];

DLL_API
int16 enet_Startup(char08 * cpErrMsg)
{
  WSADATA wsaData;
  uchar08 c;
  if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) 
  {
      snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
        "WSAStartup failed with error: %d", WSAGetLastError());
      return ERR_INTERNAL;
  }
  if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
  {
      snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Could not find a usable version of Winsock.dll");
      WSACleanup();
      return ERR_INTERNAL;
  }
  for (c = 0; c < MAX_CONNECTION_NUMBER; ++c)
    Connections[c] = INVALID_SOCKET;
  isStarted = TRUE;
  return ERR_NONE;
}

DLL_API
int16 enet_Cleanup(char08 * cpErrMsg)
{
  uchar08 c;
  WSACleanup();    
  for (c = 0; c < MAX_CONNECTION_NUMBER; ++c)
    Connections[c] = INVALID_SOCKET;
  isStarted = FALSE;
  return ERR_NONE;
}

static void _MacAddrToStr(char08 * str, uchar08 * ucMacAddr)
{
  char08 buf[4], i;
  str[0] = 0;
  for (i = 0; i < 6; ++i)
  {
    sprintf_s(buf, sizeof(buf), "%02X:", ucMacAddr[i]);
    if (i == 5) buf[2] = 0;
    strcat_s(str, strlen(buf), buf);
  }
}

static int16 _sendUdpBroadcast(SOCKADDR_IN * sin_local_addr,
                               enet_ConnectionT * foundConnections, 
                               uchar08 ucMaxConnectionsNumber, 
                               uchar08 * ucpFoundConnectionsNumber,
                               enet_ReadSerialNumberT readSerialNumber,
                               uint16 uiBroadcastTimoutMs, char08 * cpErrMsg)
{  
  const char08 _cpUdpBroadcastMessage [] = { 0, 1, 0, 0xf6 };
  SOCKET s = INVALID_SOCKET;
  DWORD broadcastTimoutMs = (DWORD)uiBroadcastTimoutMs;  
  BOOL broadcastOption = 1;
  SOCKADDR_IN sin_to_addr, sin_from_addr;  
  char08 buf[256];
  int fromLen = sizeof(SOCKADDR_IN), recvLen = 0;
  int16 iPortNo, err;
  
  *ucpFoundConnectionsNumber = 0;
  
  CHECK_STARTUP;

  if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
             "UDP socket open failed with error code %d", WSAGetLastError());
    return ERR_INTERNAL;
  }
  EN_CHKRET_ERR(bind(s, (SOCKADDR *)sin_local_addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR,
                "Can not bind socket to local address for UDP broadcast, error code %d");
  EN_CHKRET_ERR(setsockopt(s, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcastOption, sizeof(BOOL)) == SOCKET_ERROR,
                "Can not set UDP socket to broadcast mode, error code %d");
  EN_CHKRET_ERR((setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char *)&broadcastTimoutMs, sizeof(DWORD)) == SOCKET_ERROR ||
                 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char *)&broadcastTimoutMs, sizeof(DWORD)) == SOCKET_ERROR),
                "Can not set UDP socket send/receive timeout, error code %d");
  
  ZeroMemory(&sin_to_addr, sizeof(sin_to_addr));
  sin_to_addr.sin_family = AF_INET;
  sin_to_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
  sin_to_addr.sin_port = htons(UDP_BROADCAST_PORT);
  EN_CHKRET_ERR(sendto(s, _cpUdpBroadcastMessage, sizeof(_cpUdpBroadcastMessage), 0, 
                       (SOCKADDR *)&sin_to_addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR,
                "Can not send UDP broadcast message, error code %d");

  for (;;)
  {
    strcpy_s(foundConnections[*ucpFoundConnectionsNumber].cpLocalIpAddr,
           sizeof(foundConnections[*ucpFoundConnectionsNumber].cpLocalIpAddr),
           inet_ntoa(sin_local_addr->sin_addr));
    if (*ucpFoundConnectionsNumber >= ucMaxConnectionsNumber)
        break;
    if ((recvLen = recvfrom(s, buf, sizeof(buf), 0, (SOCKADDR *)&sin_from_addr, &fromLen)) == SOCKET_ERROR
        || recvLen < 30)
    {
      //snprintf(cpErrMsg, MAX_ERR_STR_LEN,
      //       "error code %d", WSAGetLastError());
      break;
    }
    else
    { 
      strcpy_s(foundConnections[*ucpFoundConnectionsNumber].cpIqInterfaceIpAddr,
             sizeof(foundConnections[*ucpFoundConnectionsNumber].cpIqInterfaceIpAddr),
             inet_ntoa(sin_from_addr.sin_addr));
      foundConnections[*ucpFoundConnectionsNumber].uiPort = TCP_PORT;
      _MacAddrToStr(foundConnections[*ucpFoundConnectionsNumber].cpIqInterfaceMacAddr, (uchar08 *)&(buf[24]));
      iPortNo = enet_OpenConnection(&(foundConnections[*ucpFoundConnectionsNumber]), uiBroadcastTimoutMs, cpErrMsg);
      if (iPortNo <= 0)
        continue;
      err = readSerialNumber(iPortNo, &(foundConnections[*ucpFoundConnectionsNumber].ul64SerialNumber), cpErrMsg);
      if (enet_CloseConnection(iPortNo, cpErrMsg) != ERR_NONE ||
          err != ERR_NONE)
        continue;      
      ++(*ucpFoundConnectionsNumber);      
    }
  }

  closesocket(s);  
  return ERR_NONE;
}

int16 enet_findIQInterfaces(enet_ConnectionT * foundConnections, 
                            uchar08 ucMaxConnectionsNumber, 
                            uchar08 * ucpFoundConnectionsNumber,
                            enet_ReadSerialNumberT readSerialNumber,
                            uint16 uiBroadcastTimoutMs, char08 * cpErrMsg)
{
  int16 err, i = 0;
  ULONG outBufLen = 15000, retVal;
  PIP_ADAPTER_ADDRESSES pAddresses = NULL, pCurrAddresses = NULL;
  PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;
  SOCKADDR_IN * sin_local_addr;
  uchar08 foundConnectionsNumber = 0;
  
  *ucpFoundConnectionsNumber = 0;

  do
  {
    pAddresses = (IP_ADAPTER_ADDRESSES *) HeapAlloc(GetProcessHeap(), 0, outBufLen);
    if (pAddresses == NULL) 
    {
      snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Memory allocation failed for network adapters");
      return ERR_INTERNAL;
    }
    if ((retVal = GetAdaptersAddresses(AF_INET, 
                                       GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, 
                                       NULL, pAddresses, &outBufLen)) == ERROR_BUFFER_OVERFLOW) 
    {
        HeapFree(GetProcessHeap(), 0, pAddresses);
        pAddresses = NULL;
    } 
    else if (retVal == ERROR_NO_DATA)
    {
      HeapFree(GetProcessHeap(), 0, pAddresses);
      snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "No network adapters were found");
      return ERR_INTERNAL;
    }
    if (retVal != NO_ERROR)
    {
      HeapFree(GetProcessHeap(), 0, pAddresses);
      snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Get network adapters failed with error code %d", retVal);
      return ERR_INTERNAL;
    }
    else
      break;
    i++;
  } while (i < 3);

  pCurrAddresses = pAddresses;
  while (pCurrAddresses)
  {
    if (*ucpFoundConnectionsNumber >= ucMaxConnectionsNumber)
        break;
    if (pCurrAddresses->OperStatus != IfOperStatusUp ||
        pCurrAddresses->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
    {
      pCurrAddresses = pCurrAddresses->Next;
      continue;
    }
    pUnicast = pCurrAddresses->FirstUnicastAddress;
    while (pUnicast)
    {
      sin_local_addr = (SOCKADDR_IN *)(pUnicast->Address.lpSockaddr);
      if (sin_local_addr->sin_family != AF_INET || 
          strncmp("127.", inet_ntoa(sin_local_addr->sin_addr), 4) == 0) // loopback address
      {
        pCurrAddresses = pCurrAddresses->Next;
        continue;      
      }
      err = _sendUdpBroadcast(sin_local_addr,
                              &(foundConnections[*ucpFoundConnectionsNumber]), 
                              ucMaxConnectionsNumber - *ucpFoundConnectionsNumber, 
                              &foundConnectionsNumber, readSerialNumber, uiBroadcastTimoutMs, cpErrMsg);
      if (err == ERR_NONE)
        *ucpFoundConnectionsNumber += foundConnectionsNumber;
        
      pUnicast = pUnicast->Next;
    }
    pCurrAddresses = pCurrAddresses->Next;
  }

  HeapFree(GetProcessHeap(), 0, pAddresses);
  return ERR_NONE;
}

int16 enet_OpenConnection(enet_ConnectionT * connection, uint16 uiSendRecvTimoutMs, char08 * cpErrMsg)
{
  uint16 c;
  SOCKADDR_IN sin_to_addr;
  DWORD sendRecvTimoutMs = uiSendRecvTimoutMs;

  CHECK_STARTUP;

  for (c = 0; c < MAX_CONNECTION_NUMBER; ++c)
    if (Connections[c] == INVALID_SOCKET) break;
  if (c == MAX_CONNECTION_NUMBER)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Maximum connection number %d is exceeded", MAX_CONNECTION_NUMBER);
    return ERR_INTERNAL;
  }

  ZeroMemory(&sin_to_addr, sizeof(sin_to_addr));
  sin_to_addr.sin_family = AF_INET;
  sin_to_addr.sin_addr.S_un.S_addr = inet_addr(connection->cpIqInterfaceIpAddr);
  sin_to_addr.sin_port = htons(connection->uiPort);
  if (sin_to_addr.sin_addr.S_un.S_addr == INADDR_NONE)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Wrong ip address %s", connection->cpIqInterfaceIpAddr);
    return ERR_INTERNAL;
  }  

  if ((Connections[c] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
             "TCP socket open failed with error code %d", WSAGetLastError());
    return ERR_INTERNAL;
  }

  if (setsockopt(Connections[c], SOL_SOCKET, SO_RCVTIMEO, (const char *)&sendRecvTimoutMs, sizeof(DWORD)) == SOCKET_ERROR ||
      setsockopt(Connections[c], SOL_SOCKET, SO_SNDTIMEO, (const char *)&sendRecvTimoutMs, sizeof(DWORD)) == SOCKET_ERROR)
  {
    closesocket(Connections[c]);
    Connections[c] = INVALID_SOCKET;
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Can not set TCP send/receive timeout, error code %d", WSAGetLastError());
    return ERR_INTERNAL;
  }
    
  if (connect(Connections[c], (SOCKADDR *)&sin_to_addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) 
  {
    closesocket(Connections[c]);
    Connections[c] = INVALID_SOCKET;
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Can not open TCP connection to %s:%d, error code %d", 
             connection->cpIqInterfaceIpAddr, connection->uiPort, WSAGetLastError());
    return ERR_INTERNAL;
  }

  return c + MAX_COMPORT_NUM;
}

int16 enet_CloseConnection(uint16 uiPortNo, char08 * cpErrMsg)
{
  uint16 c = uiPortNo - MAX_COMPORT_NUM;
  int16 err = ERR_NONE;

  CHECK_STARTUP;
  CHECK_CONNECTION_NUMBER(c);

  if (Connections[c] == INVALID_SOCKET)
    return ERR_NONE;

  if (shutdown(Connections[c], SD_SEND) == SOCKET_ERROR) 
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, 
             "TCP connection close failed with error code %d", WSAGetLastError());
    err = ERR_INTERNAL;
  }

  closesocket(Connections[c]);
  Connections[c] = INVALID_SOCKET;
  return ERR_NONE;
}

int16 enet_Send(uint16 uiPortNo,
                 uchar08 *ucpBuf,
                 ulong32 ulBytesCount,
                 char08 *cpErrMsg)
{
  uint16 c = uiPortNo - MAX_COMPORT_NUM;
  ulong32 sendNum;
  CHECK_STARTUP;
  CHECK_CONNECTION_NUMBER(c);
  if ((sendNum = send(Connections[c], (const char *)ucpBuf, ulBytesCount, 0)) == SOCKET_ERROR) 
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "TCP send failed with error code %d", WSAGetLastError());
    return ERR_INTERNAL;
  }
  if (sendNum < ulBytesCount)
  {
    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "TCP sent timeout %d", WSAGetLastError());
    return ERR_INTERNAL;
  }
  return ERR_NONE;
}

int16 enet_Recv(uint16 uiPortNo,
                uchar08 *ucpBuf,
                ulong32 ulBytesCount,
                char08 *cpErrMsg)
{
  uint16 c = uiPortNo - MAX_COMPORT_NUM;  
  int recvNum = 0;
  CHECK_STARTUP;
  CHECK_CONNECTION_NUMBER(c);
  while (ulBytesCount > 0)
  {
    recvNum = recv(Connections[c], (char *)ucpBuf, ulBytesCount, 0);
    if (recvNum == SOCKET_ERROR)
    {
        snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "TCP receive failed with error code %d", WSAGetLastError());
        return ERR_INTERNAL;
    }
    else if (recvNum == 0)
    {
      snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "TCP receive timeout or connection lost");
      return ERR_INTERNAL;
    }
    ucpBuf += recvNum;
    ulBytesCount -= recvNum;
  }
  return ERR_NONE;
}
