/* ---------------------------------------------------------------------
   (c) ED 2005-2006
   Project      : CLIB
   Function     : Portable sockets for Internet Protocol (IP)
   Module       : inet
   File         : inet.c
   Created      : 23-09-2005
   Modified     : 11-08-2008
   --------------------------------------------------------------------- */

   /* ---------------------------------------------------------------------
      Log

      0.2 11-08-2008 inet_client_receive() fixed.
      .              INET_ERR_SOCKET_DISCONNECTED added.
      0.1 11-05-2006 Becomes inet (was psk)
      0.0 23-09-2005 Created

      --------------------------------------------------------------------- */
#ifdef __cplusplus
#error This is not C++. Please use a C compiler.
#endif

#define DBG 1

#include "psock/inc/inet.h"
#include "psock/inc/psock.h"
#include "ed/inc/sysalloc.h"
#include "ed/inc/sys.h"

#if DBG
#include "ed/inc/prt.h"
#else
#define PRT_I(a)
#endif

#if defined (WIN32)
#include "win/inc/wsatools.h"
#endif

/* macros ============================================================== */

#define ID "INET"
#define VER "0.2"

#define MODULE ID"."

/* constants =========================================================== */
/* types =============================================================== */

typedef int inet_out_f (void *p_usr, int data);

/* structures ========================================================== */

struct inet
{
   SOCKET sock;

   SOCKADDR_IN sin;
   int sock_err;
   inet_out_f *pf;
   void *p_usr;

   /* client */
   SOCKET csock;
   SOCKADDR_IN csin;

#if defined (WIN32)
   WSADATA WSAData;
#endif

};

/* private variables =================================================== */

#if defined (WIN32)
static int G_count = 0;
#endif

/* private functions =================================================== */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static void init (inet_s * this)
{
   this->sock = INVALID_SOCKET;

#if defined (WIN32)

   if (G_count == 0)
   {
      WORD wVersionRequested = MAKEWORD (2, 2);
      int err = WSAStartup (wVersionRequested, &this->WSAData);
      printf (MODULE "WSAStartup()\n");
      if (!err)
      {
         /*
            Confirm that the WinSock DLL supports 2.2.
            Note that if the DLL supports versions greater
            than 2.2 in addition to 2.2, it will still return
            2.2 in wVersion since that is the version we
            requested.                                        */
         uint const maj = LOBYTE (this->WSAData.wVersion);
         uint const min = HIBYTE (this->WSAData.wVersion);
         if (maj == 2 && min == 2)
         {
            /* The WinSock DLL is acceptable. Proceed. */
         }
         else
         {
            printf (MODULE "Windows socket version error (%u.%u)\n", maj,
                    min);
            WSACleanup ();
            FREE (this);
         }
      }
      else
      {
         printf (MODULE "Windows socket error %d\n", err);
         wsa_print_error ();
      }
   }

   if (this != NULL)
   {
      G_count++;
   }
   printf (MODULE "inet_create.G_count=%d\n", G_count);
#endif
}

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static void end (void)
{
#if defined (WIN32)
   if (G_count != 0)
   {
      G_count--;
      if (!G_count)
      {
         printf (MODULE "WSACleanup()\n");
         WSACleanup ();
      }
   }
   printf (MODULE "inet_delete.G_count=%d\n", G_count);
#endif
}

/* ---------------------------------------------------------------------
   update_err ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
static inet_err_e update_err (inet_s * this, int sock_err)
{
   inet_err_e err;

   if (sock_err == 0)
   {
      err = INET_ERR_SOCKET_DISCONNECTED;
      this->sock_err = sock_err;
   }
   else
   {
#if defined (linux)

      this->sock_err = sock_err;
#elif defined (WIN32)

      ASSERT (sock_err == (int) INVALID_SOCKET);
      this->sock_err = WSAGetLastError ();
#endif
      err = INET_ERR_SOCKET;
   }

   return err;
}

/* ---------------------------------------------------------------------
   close_socket ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
static inet_err_e close_socket (inet_s * this)
{
   inet_err_e err = INET_OK;

   if (this->sock != INVALID_SOCKET)
   {
      int sock_err;

      printf (MODULE "disconnecting socket %d ...\n", this->sock);

      /* -ed- 2006-05-11 ajout... */
      sock_err = shutdown (this->sock, 2);
      if (!sock_err)
      {
         /* OK */
      }
      else
      {
         err = update_err (this, sock_err);
      }
      {
         int sock = this->sock;
         sock_err = closesocket (this->sock), this->sock = INVALID_SOCKET;

         printf (MODULE "... socket %d disconnected\n", sock);
      }

      if (!sock_err)
      {
         /* OK */
      }
      else
      {
         err = update_err (this, sock_err);
      }
   }
   return err;
}

/* ---------------------------------------------------------------------
   is_received ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I: context
   I: timeout in ms
   O: received
   --------------------------------------------------------------------- */
static int is_received (SOCKET sock, uint ms)
{
   int received = 0;

   fd_set rfds;
   struct timeval tv;

   tv.tv_sec = (ms * 1000) / 1000000;
   tv.tv_usec = (ms * 1000) % 1000000;

   PRT_I (tv.tv_sec);
   PRT_I (tv.tv_usec);

   FD_ZERO (&rfds);
   FD_SET (sock, &rfds);

   {
      int ret = select (sock + 1u, &rfds, NULL, NULL, &tv);
      PRT_I (ret);
      if (ret == 0)
      {
         printf (MODULE "select() timeout\n");
      }
      else if (ret > 0)
      {
         if (FD_ISSET (sock, &rfds))
         {
            received = 1;
         }
         else
         {
            printf (MODULE "nothing was received\n");
         }
      }
      else
      {
         printf (MODULE "select() error\n");
      }
   }
   return received;
}

/* internal public functions =========================================== */
/* entry points ======================================================== */

/* ---------------------------------------------------------------------
   inet_sid ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
char const *inet_sid (void)
{
   return ID;
}

/* ---------------------------------------------------------------------
   inet_sver ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
char const *inet_sver (void)
{
   return VER;
}

/* ---------------------------------------------------------------------
   inet_sver ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
void inet_panic (void)
{
#if defined (WIN32)
   if (G_count != 0)
   {
      printf (MODULE "WSACleanup() on emergency\n");
      WSACleanup ();
      G_count = 0;
   }
#endif
}

/* ---------------------------------------------------------------------
   inet_sver ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
char const *inet_sversock (inet_s * this)
{
#if defined (linux)
   static char sver[] = "BSD sockets";
#elif defined (WIN32)

   static char sver[] = "Winsock ???.???";

   if (this != NULL)
   {
      char *p = strchr (sver, '?');

      if (p != NULL)
      {
#if 1
         sprintf (p, "%u.%u", (unsigned) LOBYTE (this->WSAData.wVersion),
                  (unsigned) HIBYTE (this->WSAData.wVersion));
#endif

         printf (MODULE "%u.%u\n", (unsigned) LOBYTE (this->WSAData.wVersion),
                 (unsigned) HIBYTE (this->WSAData.wVersion));
      }
#endif
   }

   return sver;
}

/* ---------------------------------------------------------------------
   inet_serr ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
char const *inet_serr (inet_err_e err)
{
   char const *s = "INET_ERR_???";

   if (err < INET_ERR_NB)
   {
      static char const *as[] = {
         "INET_OK",
#define ITEM(n_, s_)\
         #s_,

#include "psock/inc/inet_err.itm"

#undef ITEM

      };
      s = as[err];
   }
   return s;
}

/* ---------------------------------------------------------------------
   inet_serr_sock ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
char const *inet_serr_sock (int err)
{
   char const *s = NULL;

   switch (err)
   {
#define ITEM(n_, s_, c_)\
   case n_:             \
      s= s_;            \
      break;

#include "psock/inc/sock_err.itm"

#undef ITEM

   default:
      s = "SOCK_ERR_???";
   }

   ASSERT (s != NULL);
   return s;
}

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
inet_s *inet_create (void)
{
   inet_s *this = malloc (sizeof *this);

   if (this != NULL)
   {
      CLR (this, inet_s);
      init (this);
   }

   return this;
}

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
void inet_delete (inet_s * this)
{
   end ();

   FREE (this);
}

/* ---------------------------------------------------------------------
   inet_last_sock_err ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
inet_err_e inet_last_sock_err (inet_s * this, int *p_err)
{
   inet_err_e err = INET_OK;

   if (this != NULL)
   {
      if (p_err)
      {
         *p_err = this->sock_err;
      }
   }
   else
   {
      err = INET_ERR_CONTEXT;
   }
   return err;
}

/* ---------------------------------------------------------------------
   inet_server_open ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
inet_err_e inet_server_open (inet_s * this, char const *sip, uint port)
{
   inet_err_e err = INET_OK;

   if (this != NULL)
   {
      if (this->sock == INVALID_SOCKET)
      {
         /* create the socket */
         SOCKET sock = socket (AF_INET, SOCK_STREAM, 0);

         if (sock != INVALID_SOCKET)
         {
            this->sin.sin_addr.s_addr = sip == INET_SIP_ANY
               ? htonl (INADDR_ANY) : inet_addr (sip);

            this->sin.sin_family = AF_INET;
            this->sin.sin_port = htons (port);

            {
               /* attribute an address and port number */
               int sock_err =
                  bind (sock, (SOCKADDR *) & this->sin, sizeof (this->sin));

               if (sock_err != SOCKET_ERROR)
               {
                  /* initiate the server mode (listen to new clients incomers)  */
                  sock_err = listen (sock, 5);

                  if (sock_err != SOCKET_ERROR)
                  {
                     /* OK, save the socket  */
                     this->sock = sock;
                  }
                  else
                  {
                     err = update_err (this, sock_err);
                  }
               }
               else
               {
                  err = update_err (this, sock_err);
               }
            }
         }
         else
         {
            err = update_err (this, (int) sock);
         }
      }
      else
      {
         err = INET_ERR_SOCKET_OPENED;
      }
   }
   else
   {
      err = INET_ERR_CONTEXT;
   }
   return err;
}

/* ---------------------------------------------------------------------
   inet_server_poll ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
inet_err_e inet_server_poll (inet_s * this, int *p_accepted)
{
   inet_err_e err = INET_OK;

   if (this != NULL)
   {
      if (this->sock != INVALID_SOCKET)
      {
         int accepted = 0;
         SOCKADDR_IN csin = {
            0
         };
         int recsize = (int) sizeof csin;
         SOCKET csock;

         printf (MODULE "accept\n");

         csock = accept (this->sock, (SOCKADDR *) & csin, &recsize);

         printf (MODULE "accepted\n");

         if (csock != INVALID_SOCKET)
         {
            /* OK */
            accepted = 1;
            printf (MODULE "accepted connection from %s:%d\n",
                    inet_ntoa (csin.sin_addr), htons (csin.sin_port));

            this->csock = csock;
            this->csin = csin;
         }
         else
         {
            err = update_err (this, (int) csock);
         }
#if 0
         int nfds = this->sock + 1;
         fd_set readfds;
         fd_set *writefds = NULL;
         fd_set *exceptfds = NULL;
         const struct timeval *timeout = NULL;

         FD_ZERO (&readfds);
         FD_SET (this->sock, &readfds);

         printf (MODULE "select\n");
         int ret_select =
            select (nfds, &readfds, writefds, exceptfds, timeout);
         printf (MODULE "selected\n");

         if (ret_select != 0)
         {
            if (FD_ISSET (this->sock, &readfds))
            {
            }
         }
#endif
         if (p_accepted != NULL)
         {
            *p_accepted = 1;
         }
      }
      else
      {
         err = INET_ERR_SOCKET_CLOSED;
      }
   }
   else
   {
      err = INET_ERR_CONTEXT;
   }
   return err;
}

/* ---------------------------------------------------------------------
   inet_server_send ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I: context
   I: data address
   I: bytes to send
   I: address of variable for number of received bytes
   O: error
   --------------------------------------------------------------------- */
inet_err_e inet_server_send (inet_s * this, void const *addr, size_t size,
                             size_t * p_sent)
{
   inet_err_e err = INET_OK;

   if (this != NULL)
   {
      if (this->sock != INVALID_SOCKET)
      {
         size_t sent = 0;
         size_t to_send = size;
         char const *p_data = addr;

         printf (MODULE "%u bytes to send\n", (uint) size);

         do
         {
            int const sock_err =
               send (this->csock, p_data + sent, (int) size, 0);

#if 1

            printf (MODULE "inet_send() sock_err=%d\n", sock_err);
#endif

            if (sock_err != -1)
            {
               size_t n = (size_t) sock_err;

               to_send -= n;
               sent += n;
               printf (MODULE "%u bytes sent\n", (uint) sent);
            }
            else
            {
               err = update_err (this, sock_err);
               break;
            }
         }
         while (to_send != 0)
         ;

         if (p_sent != NULL)
         {
            *p_sent = sent;
         }
      }
      else
      {
         err = INET_ERR_SOCKET_CLOSED;
      }
   }
   else
   {
      err = INET_ERR_CONTEXT;
   }
   return err;
}

/* ---------------------------------------------------------------------
   inet_server_close ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
inet_err_e inet_server_close (inet_s * this)
{
   inet_err_e err = INET_OK;

   if (this != NULL)
   {
      err = close_socket (this);
   }
   else
   {
      err = INET_ERR_CONTEXT;
   }
   return err;
}

/* ---------------------------------------------------------------------
   inet_client_open ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
inet_err_e inet_client_open (inet_s * this)
{
   inet_err_e err = INET_OK;

   if (this != NULL)
   {
      if (this->sock == INVALID_SOCKET)
      {
         /* create the socket */
         SOCKET sock = socket (AF_INET, SOCK_STREAM, 0);

         if (sock != INVALID_SOCKET)
         {
            /* OK, save the socket  */
            this->sock = sock;
         }
         else
         {
            err = update_err (this, (int) sock);
         }
      }
      else
      {
         err = INET_ERR_SOCKET_OPENED;
      }
   }
   else
   {
      err = INET_ERR_CONTEXT;
   }
   return err;
}

/* ---------------------------------------------------------------------
   inet_client_connect ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
inet_err_e inet_client_connect (inet_s * this, char const *sip, uint port)
{
   inet_err_e err = INET_OK;

   if (this != NULL)
   {
      if (this->sock != INVALID_SOCKET)
      {
         int sock_err;

         this->sin.sin_addr.s_addr = inet_addr (sip);
         this->sin.sin_family = AF_INET;
         this->sin.sin_port = htons (port);

         printf (MODULE "connecting socket %d to %s:%u ...\n", this->sock,
                 sip, port);

         /* ask a connection to the server */
         sock_err =
            connect (this->sock, (SOCKADDR *) & this->sin, sizeof this->sin);

         if (!sock_err)
         {
            /* OK */
            printf (MODULE "... socket %d connected\n", this->sock);
         }
         else
         {
            err = update_err (this, sock_err);
         }
      }
      else
      {
         err = INET_ERR_SOCKET_CLOSED;
      }
   }
   else
   {
      err = INET_ERR_CONTEXT;
   }
   return err;
}

/* ---------------------------------------------------------------------
   inet_server_received ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I: context
   I: timeout in ms
   O: received
   --------------------------------------------------------------------- */
int inet_server_received (inet_s * this, uint ms)
{
   return is_received (this->csock, ms);
}

/* ---------------------------------------------------------------------
   inet_client_received ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I: context
   I: timeout in ms
   O: received
   --------------------------------------------------------------------- */
int inet_client_received (inet_s * this, uint ms)
{
   return is_received (this->sock, ms);
}

/* ---------------------------------------------------------------------
   receive ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I: socket
   I: destination data address
   I: data size
   O: number of received bytes
   R: error status
   --------------------------------------------------------------------- */
static inet_err_e receive (SOCKET sock, void *p_data, size_t size,
                           size_t * p_received, inet_s * ctx)
{
   inet_err_e err = INET_OK;

   if (sock != INVALID_SOCKET)
   {
#if DBG
      printf (MODULE "inet_receive() receiving on socket %d...\n", (int)sock);
#endif
      int sock_err = recv (sock, p_data, (int) size, 0);

      PRT_I (sock_err);

      if (sock_err > 0)
      {
         size_t nb_rec = (size_t) sock_err;
#if DBG
         printf (MODULE "inet_receive() nb_rec=%lu\n", (ulong) nb_rec);
#endif

         if (p_received != NULL)
         {
            *p_received = nb_rec;
         }
      }
      else
      {
         err = update_err (ctx, sock_err);
      }
   }
   else
   {
      err = INET_ERR_SOCKET_CLOSED;
   }
   PRT_I(err);
   return err;
}

/* ---------------------------------------------------------------------
   inet_server_receive ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I: context
   I: destination data address
   I: data size
   O: number of received bytes
   R: error status
   --------------------------------------------------------------------- */
inet_err_e inet_server_receive (inet_s * this, void *p_data, size_t size,
                                size_t * p_received)
{
   inet_err_e err = INET_OK;
   if (this != NULL)
   {
      err = receive (this->csock, p_data, size, p_received, this);
   }
   else
   {
      err = INET_ERR_CONTEXT;
   }
   return err;
}

/* ---------------------------------------------------------------------
   inet_client_receive ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I: context
   I: destination data address
   I: data size
   O: number of received bytes
   R: error status
   --------------------------------------------------------------------- */
inet_err_e inet_client_receive (inet_s * this, void *p_data, size_t size,
                                size_t * p_received)
{
   inet_err_e err = INET_OK;
   if (this != NULL)
   {
      err = receive (this->sock, p_data, size, p_received, this);
   }
   else
   {
      err = INET_ERR_CONTEXT;
   }
   return err;
}

/* ---------------------------------------------------------------------
   inet_client_send ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I: context
   I: data address
   I: bytes to send
   I: address of variable for number of received bytes
   O: error
   --------------------------------------------------------------------- */
inet_err_e inet_client_send (inet_s * this, void const *p_data, size_t size,
                             size_t * p_sent)
{
   inet_err_e err = INET_OK;

   if (this != NULL)
   {
      if (this->sock != INVALID_SOCKET)
      {
         size_t sent = 0;
         size_t to_send = size;

         printf (MODULE "CLI : %u bytes to send\n", (uint) size);

         do
         {
            int const sock_err = send (this->sock, p_data, (int) size, 0);

#if DBG

            printf (MODULE "inet_client_send() sock_err=%d\n", sock_err);
#endif

            if (sock_err != -1)
            {
               size_t n = (size_t) sock_err;

               to_send -= n;
               sent += n;
               printf (MODULE "CLI : %u bytes sent\n", (uint) sent);
            }
            else
            {
               err = update_err (this, sock_err);
               break;
            }
         }
         while (to_send != 0);

         if (p_sent != NULL)
         {
            *p_sent = sent;
         }
      }
      else
      {
         err = INET_ERR_SOCKET_CLOSED;
      }
   }
   else
   {
      err = INET_ERR_CONTEXT;
   }
   return err;
}

/* ---------------------------------------------------------------------
   inet_client_close ()
   ---------------------------------------------------------------------

   ---------------------------------------------------------------------
   I:
   O:
   --------------------------------------------------------------------- */
inet_err_e inet_client_close (inet_s * this)
{
   inet_err_e err = INET_OK;

   if (this != NULL)
   {
      err = close_socket (this);
   }
   else
   {
      err = INET_ERR_CONTEXT;
   }
   return err;
}

/* public variables ==================================================== */
