#include "oslib/types.h"
#include "oslib/resolver.h"
#include "oslib/macros.h"
#include "oslib/os.h"
#include "oslib/osbyte.h"
#include "stdio.h"
#include "sys/byteorder.h"
#include "sys/select.h"

#include "string.h"

#include <clib/kernel.h>


#define _DEBUG

#define IYONIX_SOCKET_ERR     (0x20e00)
#define COMMAND_LEN           (1024)
#if defined _DEBUG
#define REPORT                Report
#define REPORTVAR             ReportVar
void    Report                (char * szMessage);
void    ReportVar             (char * szFormat, int nVariable);
#else
#define REPORT(szMessage)     /* szMessage */
#define REPORTVAR(szMessage, nVariable)    /* szMessage, nVariable */
#endif
#define Resolver_GetHost      (0x46001)
#define BUFFERSIZE            (1024*5)
#define COMMAND_LEN_QUEUE     (1032)
#define LISTEN_BACKLOG        (2)
#define DEFAULT_PORT          (6435)


typedef enum
{
  STATE_INVALID = -1,

  STATE_DISCONNECTED,

  STATE_CREATESOCKET,
  STATE_RESOLVING,
  STATE_CONNECT,
  STATE_CONNECTING,
  STATE_CONNECTED,

  STATE_CANCEL,
  STATE_FINISHED,
  STATE_QUIT,

  STATE_NUM
} STATE;

typedef enum
{
  SOCK_INVALID = -1,

  SOCK_OK,
  SOCK_ERR,
  SOCK_FAIL,
  SOCK_WAIT,
  SOCK_RECEIVE,
  SOCK_MULTIDONE,
  SOCK_CLOSED,

  SOCK_NUM
} SOCK;

static char                  gszServer[COMMAND_LEN - 5] = "127.0.0.1";
static int                   gnPort = DEFAULT_PORT;
static int                   gnAddress;
static socket_s              gsSocket;
static STATE                 geState = STATE_INVALID;
static STATE                 gePrevState = STATE_INVALID;
static char                  gpcBuffer[BUFFERSIZE];
static int                   gnSendCommandLen;
static int                   gnSendCommandSent;
static socket_s              gsSendSocket;
static char                  gszSendCommand[COMMAND_LEN_QUEUE];
static socket_s              gsSendSocket;


void NetworkLoop (void);
SOCK CreateSocket (socket_s * psSocket);
STATE Connect (int nAddress, socket_s * psSocket, int nPort);
STATE CheckConnected (socket_s sSocket);
void PrintState (STATE eState);
STATE Resolve (char * szHostName, int * pnAddress);
SOCK SendCommandStart (char * szCommand, socket_s * psSocket);
SOCK SendCommandUpdate (void);
void Cancel (void);
SOCK SocketRead (socket_s * psSocket);
STATE ImmediateUpdate (socket_s * psSocket);
STATE Connected (socket_s * psSocket);

//////////////////////////////////////////////////////////////////
// Report a debug message
void Report (char * szMessage)
{
  printf (szMessage);
  printf ("\n");
}

//////////////////////////////////////////////////////////////////
// Report a debug message with variable
void ReportVar (char * szFormat, int nVariable)
{
  printf (szFormat, nVariable);
  printf ("\n");
}

// Respond to swi errors
inline void err (os_error * psError)
{
  if (psError)
  {
    printf (psError->errmess);
    printf ("\n");
  }
}

//////////////////////////////////////////////////////////////////
// Main program
int main (int argc, char * argv[])
{
  argc = argc;
  argv = argv;

  gnSendCommandLen = 0;
  gnSendCommandSent = 0;

  geState = STATE_DISCONNECTED;
  gePrevState = STATE_INVALID;

  while (geState != STATE_QUIT)
  {
    NetworkLoop ();
  }

  return 0;
}

//////////////////////////////////////////////////////////////////
// Main network program
void NetworkLoop (void)
{
  SOCK                        eSockError;
  int                         nKey;

  switch (geState)
  {
    case STATE_DISCONNECTED:
        geState = STATE_CREATESOCKET;
      break;
    case STATE_CREATESOCKET:
      eSockError = CreateSocket (& gsSocket);
      switch (eSockError)
      {
        case SOCK_FAIL:
          Cancel ();
          geState = STATE_CANCEL;
          break;
        case SOCK_ERR:
          Cancel ();
          geState = STATE_CANCEL;
          break;
        default:
          geState = STATE_RESOLVING;
          break;
      }
      break;
    case STATE_RESOLVING:
      geState = Resolve (gszServer, & gnAddress);
      break;
    case STATE_CONNECT:
      geState = Connect (gnAddress, & gsSocket, gnPort);
      break;
    case STATE_CONNECTING:
      geState = CheckConnected (gsSocket);
      err (xosbyte1 (osbyte_IN_KEY, 0xcc , 0xff, & nKey)); // R
      if (nKey == 0xff)
      {
        geState = STATE_FINISHED;
      }
      break;
    case STATE_CONNECTED:
      geState = Connected (& gsSocket);
      err (xosbyte1 (osbyte_IN_KEY, 0xcc , 0xff, & nKey)); // R
      if (nKey == 0xff)
      {
        geState = STATE_FINISHED;
      }
      err (xosbyte1 (osbyte_IN_KEY, 0xdc , 0xff, & nKey)); // T
      if (nKey == 0xff)
      {
        if (gnSendCommandLen == 0)
        {
          SendCommandStart ("Hello world! (Send)", & gsSocket);
        }
      }
      SendCommandUpdate ();
      break;
    case STATE_CANCEL:
      geState = ImmediateUpdate (& gsSocket);
      break;
    case STATE_FINISHED:
      // Close socket
      err (xsocket_close (gsSocket));
      geState = STATE_QUIT;
      break;
    case STATE_QUIT:
      break;
    default:
      break;
  }

  if (geState != gePrevState)
  {
    PrintState (geState);
    gePrevState = geState;
  }
}

//////////////////////////////////////////////////////////////////
// Create Socket
SOCK CreateSocket (socket_s * psSocket)
{
  socket_sockaddr            sAddress;
  char                       cArg;
  os_error                   *psError;
  SOCK                       eReturn;

  eReturn = SOCK_OK;

  if (psSocket)
  {
    psError = xsocket_creat (socket_AF_INET, socket_SOCK_STREAM,
      socket_IPPROTO_TCP, psSocket);
//      socket_IPPROTO_IP, psSocket);

    if (psError)
    {
      err (psError);
      eReturn = SOCK_FAIL;
    }
    else
    {
      memset (& sAddress, 0, sizeof (sAddress));
      sAddress.sockaddr.af = socket_AF_INET;
      sAddress.sockaddr_in.af = socket_AF_INET;
      sAddress.sockaddr_in.port = 0;
      sAddress.sockaddr_in.addr = 0;

      psError = xsocket_bind (*psSocket, & sAddress, 16);

      if (psError)
      {
        err (psError);
        eReturn = SOCK_ERR;
      }
      else
      {
        cArg = 1;
        psError = xsocket_ioctl (*psSocket, socket_FIONBIO, & cArg);

        if (psError)
        {
          err (psError);
          eReturn = SOCK_ERR;
        }
      }
    }
  }
  return eReturn;
}

//////////////////////////////////////////////////////////////////
// Connect to the mail server
STATE Connect (int nAddress, socket_s * psSocket, int nPort)
{
  socket_sockaddr            sAddress;
  os_error                   *psError;
  STATE                      eNextState;

  memset (& sAddress, 0, sizeof (sAddress));
  sAddress.sockaddr.af = socket_AF_INET;
  sAddress.sockaddr_in.af = socket_AF_INET;

  sAddress.sockaddr_in.port = htons (nPort);
//  ((nPort & 0xff) << 8)
//    + ((nPort & 0xff00) >> 8);

  sAddress.sockaddr_in.addr = nAddress;

  psError = xsocket_connect (* psSocket, & sAddress, sizeof (sAddress));

  eNextState = STATE_CONNECTING;
  if (psError)
  {
    if ((psError->errnum == socket_EISCONN)
      || (psError->errnum == (socket_EISCONN + IYONIX_SOCKET_ERR)))
    {
      eNextState = STATE_CONNECTED;
    }
    else
    {
      if ((psError->errnum != socket_EWOULDBLOCK)
        && (psError->errnum != (socket_EWOULDBLOCK + IYONIX_SOCKET_ERR))
        && (psError->errnum != socket_EALREADY)
        && (psError->errnum != (socket_EALREADY + IYONIX_SOCKET_ERR))
        && (psError->errnum != socket_EINPROGRESS)
        && (psError->errnum != (socket_EINPROGRESS + IYONIX_SOCKET_ERR)))
      {
        REPORTVAR ("Error %d", psError->errnum);
        err (psError);
        Cancel ();
//        eNextState = STATE_FINISHED;
        eNextState = STATE_CANCEL;
      }
    }
  }
  else
  {
    eNextState = STATE_CONNECTED;
  }

  return eNextState;
}

//////////////////////////////////////////////////////////////////
// Connect to the mail server
STATE CheckConnected (socket_s sSocket)
{
  os_error                   *psError;
  STATE                      eNextState;
  socket_fdset               sRead;
  socket_fdset               sWrite;
  socket_fdset               sExcept;
  int                        nFound;
  socket_timeval             sTimeVal;

  FD_ZERO (& sRead);
  FD_ZERO (& sWrite);
  FD_ZERO (& sExcept);
//  memset (& sRead, 0, sizeof (sRead));
//  memset (& sWrite, 0, sizeof (sWrite));
//  memset (& sExcept, 0, sizeof (sExcept));

  FD_SET ((unsigned int)(sSocket), (fd_set*)(& sRead));
  FD_SET ((unsigned int)(sSocket), (fd_set*)(& sWrite));
  FD_SET ((unsigned int)(sSocket), (fd_set*)(& sExcept));
//  sRead |= (1 << (unsigned int)sSocket);
//  sWrite |= (1 << (unsigned int)sSocket);
//  sExcept |= (1 << (unsigned int)sSocket);

  sTimeVal.sec = 0;
  sTimeVal.usec = 0;

  psError = xsocket_select (((unsigned int)sSocket) + 1, & sRead, & sWrite,
    & sExcept, & sTimeVal, & nFound);

  eNextState = STATE_CONNECTING;
  if (psError)
  {
    REPORTVAR ("Error %d", psError->errnum);
    err (psError);
    Cancel ();
    eNextState = STATE_CANCEL;
  }
  else
  {
    if (FD_ISSET ((unsigned int)(sSocket), (fd_set*)(& sRead)))
    {
      REPORT ("Found on Read");
    }
    if (FD_ISSET ((unsigned int)(sSocket), (fd_set*)(& sWrite)))
    {
      REPORT ("Found on Write");
    }
    if (FD_ISSET ((unsigned int)(sSocket), (fd_set*)(& sExcept)))
    {
//      REPORT ("Found on Except");
//      eNextState = STATE_FINISHED;
    }

    if (nFound > 0)
    {
      if ((FD_ISSET ((unsigned int)(sSocket), (fd_set*)(& sRead)))
        || (FD_ISSET ((unsigned int)(sSocket), (fd_set*)(& sWrite))))
      {
        REPORTVAR ("Found %d", nFound);
        eNextState = STATE_CONNECTED;
      }
    }
  }

  return eNextState;
}


//////////////////////////////////////////////////////////////////
// Print the given state
void PrintState (STATE eState)
{
  switch (eState)
  {
    case STATE_DISCONNECTED:
      REPORT ("Disconnected");
      break;
    case STATE_CREATESOCKET:
      REPORT ("Creating socket");
      break;
    case STATE_RESOLVING:
      REPORT ("Resolving");
      break;
    case STATE_CONNECT:
      REPORT ("Connect");
      break;
    case STATE_CONNECTING:
      REPORT ("Connecting");
      break;
    case STATE_CONNECTED:
      REPORT ("Connected");
      break;

    case STATE_CANCEL:
      REPORT ("Cancelled");
      break;
    case STATE_FINISHED:
      REPORT ("Finished");
      break;
    case STATE_QUIT:
      REPORT ("Quit");
      break;
    default:
      REPORT ("Invalid");
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Resolve a host name
STATE Resolve (char * szHostName, int * pnAddress)
{
  _kernel_swi_regs           sRegs;
  STATE                      eNextState;
  os_error                   sError;

  sRegs.r[0] = (int)szHostName;
  _kernel_swi (Resolver_GetHost, & sRegs, & sRegs);

  if (sRegs.r[0] == 0)
  {
    REPORT ("HOST: ");
    REPORT (szHostName);
    eNextState = STATE_CONNECT;
    if (pnAddress)
    {
      *pnAddress = ***(int***)((char*)(sRegs.r[1] + 16));
    }
  }
  else
  {
    if (((int)(sRegs.r[0])) == 36)
    {
      eNextState = STATE_RESOLVING;
    }
    else
    {
      REPORT ("HOST: ");
      REPORT (szHostName);
      sError.errnum = 0;
      strcpy (sError.errmess, "Server lookup failed");
      err (& sError);
      Cancel ();
//      eNextState = STATE_FINISHED;
      eNextState = STATE_CANCEL;
    }
  }

  return eNextState;
}

//////////////////////////////////////////////////////////////////
// Read any incoming data from a socket
SOCK SocketRead (socket_s * psSocket)
{
  int                        nRead;
  os_error                   *psError = NULL;
  int                        nBufferSize;
  SOCK                       eReturn = SOCK_WAIT;

  nBufferSize = BUFFERSIZE;
  psError = xsocket_read (*psSocket, gpcBuffer, nBufferSize, & nRead);

  if (!psError)
  {
    // Do stuff with the data
    gpcBuffer[nRead] = 0;
    REPORT (gpcBuffer);
    if (nRead == 0)
    {
      eReturn = SOCK_CLOSED;
    }
    else
    {
      eReturn = SOCK_RECEIVE;
    }
  }

  if (psError)
  {
    if ((psError->errnum != socket_EWOULDBLOCK)
      && (psError->errnum != (socket_EWOULDBLOCK + IYONIX_SOCKET_ERR))
      && (psError->errnum != socket_EINPROGRESS)
      && (psError->errnum != (socket_EINPROGRESS + IYONIX_SOCKET_ERR)))
    {
      REPORTVAR ("Read Error %d", psError->errnum);
      err (psError);
      eReturn = SOCK_FAIL;
    }
  }

  return eReturn;
}

//////////////////////////////////////////////////////////////////
// Start sending a message
SOCK SendCommandStart (char * szCommand, socket_s * psSocket)
{
  os_error                   *psError;
  SOCK                       eReturn;

  eReturn = SOCK_OK;

  if (gnSendCommandLen != 0)
  {
    // We're already sending something!
    REPORT ("Error sending data: cannot send multiple commands\n");
    eReturn = SOCK_FAIL;
  }
  else
  {
    // Set up the command to be sent
    gsSendSocket = * psSocket;
    strncpy(gszSendCommand, szCommand, COMMAND_LEN_QUEUE - 3);
    gszSendCommand[COMMAND_LEN_QUEUE - 3] = 0;

    gnSendCommandLen = strlen(gszSendCommand);
    gszSendCommand[gnSendCommandLen] = 13;
    gszSendCommand[gnSendCommandLen + 1] = 10;
    gszSendCommand[gnSendCommandLen + 2] = 0;
    gnSendCommandLen += 2;

    gnSendCommandSent = 0;
//    SetConnectStatus (CONNECTED_SENDING);
    psError = xsocket_send (gsSendSocket, gszSendCommand, gnSendCommandLen,
      0, & gnSendCommandSent);

    if (gnSendCommandSent < gnSendCommandLen)
    {
      eReturn = SOCK_WAIT;
    }
    else
    {
      gnSendCommandLen = 0;
    }

    if (psError)
    {
      if ((psError->errnum != socket_EWOULDBLOCK)
        && (psError->errnum != (socket_EWOULDBLOCK + IYONIX_SOCKET_ERR))
        && (psError->errnum != socket_EINPROGRESS)
        && (psError->errnum != (socket_EINPROGRESS + IYONIX_SOCKET_ERR)))
      {
        REPORTVAR ("Send error number %d", psError->errnum);
        err (psError);
        gnSendCommandLen = 0;
        eReturn = SOCK_FAIL;
      }
    }
  }

  return eReturn;
}

//////////////////////////////////////////////////////////////////
// Continue sending a message if there is one
SOCK SendCommandUpdate (void)
{
  int                        nCc;
  os_error                   *psError;
  SOCK                       eReturn;

  eReturn = SOCK_OK;

  if (gnSendCommandSent < gnSendCommandLen)
  {
    // Continue sending the command
//    SetConnectStatus (CONNECTED_SENDING);
    nCc = 0;
    psError = xsocket_send (gsSendSocket, gszSendCommand + gnSendCommandSent,
      gnSendCommandLen - gnSendCommandSent, 0, & nCc);
    gnSendCommandSent += nCc;

    if (gnSendCommandSent < gnSendCommandLen)
    {
      eReturn = SOCK_WAIT;
    }
    else
    {
      gnSendCommandLen = 0;
    }

    if (psError)
    {
      if ((psError->errnum != socket_EWOULDBLOCK)
        && (psError->errnum != (socket_EWOULDBLOCK + IYONIX_SOCKET_ERR))
        && (psError->errnum != socket_EINPROGRESS)
        && (psError->errnum != (socket_EINPROGRESS + IYONIX_SOCKET_ERR)))
      {
        REPORTVAR ("Update error number %d", psError->errnum);
        err (psError);
        gnSendCommandLen = 0;
        eReturn = SOCK_FAIL;
      }
    }
  }

  return eReturn;
}

//////////////////////////////////////////////////////////////////
// Cancels operation
void Cancel (void)
{
  geState = STATE_CANCEL;
}

//////////////////////////////////////////////////////////////////
// Handles update state for an error or cancel
STATE ImmediateUpdate (socket_s * psSocket)
{
  STATE                      eNextState = STATE_FINISHED;

  psSocket = psSocket;

  return eNextState;
}

//////////////////////////////////////////////////////////////////
// Read data and deal with it
STATE Connected (socket_s * psSocket)
{
  STATE                      eNextState = STATE_CONNECTED;
  SOCK                       eReturn;
  eReturn = SocketRead (psSocket);

  if (eReturn == SOCK_FAIL)
  {
    eNextState = STATE_CANCEL;
  }

  if (eReturn == SOCK_CLOSED)
  {
    eNextState = STATE_FINISHED;
  }

  return eNextState;
}

