#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"

#include "cwThread.h"

#include "cwTcpSocket.h"
#include "cwTcpSocketSrv.h"
#include "cwTcpSocketTest.h"




namespace cw
{
  namespace net
  {
    namespace socket
    {
      typedef struct app_str
      {
        const char*      remoteAddr;
        unsigned         remotePort;
        unsigned         recvBufByteN;
        handle_t         sockH;
        thread::handle_t threadH;
        unsigned         cbN;
        bool             serverFl;
      } app_t;

      bool _dgramThreadFunc( void* arg )
      {
        rc_t               rc;
        app_t*             app  = static_cast<app_t*>(arg);
        struct sockaddr_in fromAddr;
        char               addrBuf[ INET_ADDRSTRLEN ];
        char               buf[ app->recvBufByteN ];
        unsigned           recvBufByteN = 0;
        
        if((rc = receive( app->sockH, buf, app->recvBufByteN, &recvBufByteN, &fromAddr )) == kOkRC )
        {
          addrToString( &fromAddr, addrBuf );
          printf("%i %s from %s\n", recvBufByteN, buf, addrBuf );
        }

        app->cbN += 1;
        if( app->cbN % 10 == 0)
        {
          printf(".");
          fflush(stdout);
        }
          
        return true;
      }      
      
      bool _tcpStreamThreadFunc( void* arg )
      {
        rc_t               rc;
        app_t*             app  = static_cast<app_t*>(arg);
        char               buf[ app->recvBufByteN ];
        unsigned           recvBufByteN = 0;

        if( isConnected(app->sockH) == false )
        {
          if( app->serverFl )
          {
            if((rc = accept( app->sockH )) == kOkRC )
            {
              printf("Server connected.\n");
            }
          }
          else
          {
            sleepMs(50);
          }
        }
        else
        {              
          if((rc = receive( app->sockH, buf, app->recvBufByteN, &recvBufByteN, nullptr )) == kOkRC )
          {
            // if the server disconnects then recvBufByteN 
            if( !isConnected( app->sockH) )
            {
              printf("Disconnected.");
            }
            else
            {
              printf("%i %s\n", recvBufByteN, buf );
            }
          }
        }

        // count the number of callbacks
        app->cbN += 1;
        if( app->cbN % 10 == 0)
        {
          // print '+' when the server is not connected.
          printf("%s", isConnected(app->sockH) == false ? "+" : ".");
          fflush(stdout);
        }
          
        return true;
      }
    }
  }
}

cw::rc_t cw::net::socket::test( portNumber_t localPort, const char* remoteAddr, portNumber_t remotePort )
{
  rc_t           rc;
  unsigned       timeOutMs = 100;
  const unsigned sbufN     = 31;
  char           sbuf[ sbufN+1 ];
  app_t          app;

  app.cbN          = 0;
  app.recvBufByteN = sbufN+1;
  
  if((rc = create(app.sockH,localPort, kBlockingFl,timeOutMs, NULL, kInvalidPortNumber )) != kOkRC )
    return rc;

  if((rc = thread::create( app.threadH, _dgramThreadFunc, &app, "tcp_sock_test_tcp" )) != kOkRC )
    goto errLabel;

  if((rc = thread::unpause( app.threadH )) != kOkRC )
    goto errLabel;

  while( true )
  {
    printf("? ");
    if( std::fgets(sbuf,sbufN,stdin) == sbuf )
    {
      printf("Sending:%s",sbuf);
      send(app.sockH, sbuf, strlen(sbuf)+1, remoteAddr, remotePort );
      
      if( strcmp(sbuf,"quit\n") == 0)
        break;
    }
  }

 errLabel:
  rc_t rc0 = thread::destroy(app.threadH);

  rc_t rc1 = destroy(app.sockH);

  return rcSelect(rc,rc0,rc1);
}


cw::rc_t cw::net::socket::test_tcp( portNumber_t localPort, const char* remoteAddr, portNumber_t remotePort, bool dgramFl, bool serverFl )
{
  rc_t           rc;
  unsigned       timeOutMs = 100;
  const unsigned sbufN     = 31;
  char           sbuf[ sbufN+1 ];
  app_t          app;
  bool           streamFl = !dgramFl;
  bool           clientFl = !serverFl;
  unsigned       flags = kTcpFl | kBlockingFl;

  app.remoteAddr   = remoteAddr;
  app.remotePort   = remotePort;
  app.cbN          = 0;
  app.recvBufByteN = sbufN+1;
  app.serverFl     = serverFl;

  if( serverFl && streamFl )
    flags |= kListenFl;
  
  if( streamFl )
    flags |= kStreamFl;

  // create the socket
  if((rc = create(app.sockH,localPort, flags,timeOutMs, NULL, kInvalidPortNumber )) != kOkRC )
    return rc;

  // create the listening thread (which is really only used by the server)
  if((rc = thread::create( app.threadH, streamFl ? _tcpStreamThreadFunc : _dgramThreadFunc, &app, "tcp_sock_test" )) != kOkRC )
    goto errLabel;

  // if this is a streaming client then connect to the server (which must have already been started)
  if( streamFl && clientFl )
  {
    // note that this creates a bi-directional stream
    if((rc = connect(app.sockH,remoteAddr,remotePort)) != kOkRC )
      goto errLabel;    
  }

  printf("Starting node ....\n");

  // start the thread
  if((rc = thread::unpause( app.threadH )) != kOkRC )
    goto errLabel;

  
  while( true )
  {
    printf("? ");
    if( std::fgets(sbuf,sbufN,stdin) == sbuf )
    {
      if( strcmp(sbuf,"quit\n") == 0)
        break;

      if( streamFl )
      {  
        // when using streams no remote address is necessary
        printf("Sending:%s",sbuf);      
        send(app.sockH, sbuf, strlen(sbuf)+1 );
      }
      else
      {
        // when using dgrams the dest. address is required
        printf("Sending:%s",sbuf);      
        send(app.sockH, sbuf, strlen(sbuf)+1, remoteAddr, remotePort);
      }
    }
  }

 errLabel:
  rc_t rc0 = thread::destroy(app.threadH);

  rc_t rc1 = destroy(app.sockH);

  return rcSelect(rc,rc0,rc1);
}


namespace cw
{
  namespace net
  {
    namespace srv
    {
      typedef struct app_str
      {
        handle_t srvH;
        unsigned cbN;
        struct sockaddr_in remoteAddr;
      } app_t;
      
      void srvReceiveCallback( void* arg, const void* data, unsigned dataByteCnt, const struct sockaddr_in* fromAddr )
      {
        app_t* p = static_cast<app_t*>(arg);
        
        send(p->srvH, data, dataByteCnt, &p->remoteAddr );

        
        char addrBuf[ INET_ADDRSTRLEN ];
        socket::addrToString( fromAddr, addrBuf, INET_ADDRSTRLEN );
        p->cbN += 1;
        printf("%i %s %s\n", p->cbN, addrBuf, (const char*)data );


      }
    }      
  }
}

cw::rc_t cw::net::srv::test_udp_srv( socket::portNumber_t localPort, const char* remoteAddr, socket::portNumber_t remotePort )
{
  rc_t           rc;
  unsigned       recvBufByteCnt = 1024;
  unsigned       timeOutMs      = 100;
  const unsigned sbufN          = 31;
  char           sbuf[ sbufN+1 ];
  app_t          app;
  app.cbN = 0;
  
  if((rc = srv::create(app.srvH,
        localPort,
        socket::kBlockingFl,
        0,
        srvReceiveCallback,
        &app,
        recvBufByteCnt,
        timeOutMs,
        nullptr,
        socket::kInvalidPortNumber )) != kOkRC )
  {
    return rc;
  }

  if((rc = socket::initAddr( remoteAddr, remotePort, &app.remoteAddr )) != kOkRC )
  {
    cwLogError(rc,"Address initialization failed.");
    goto errLabel;
  }
  
  if((rc = srv::start( app.srvH )) != kOkRC )
    goto errLabel;

  while( true )
  {
    printf("? ");
    if( std::fgets(sbuf,sbufN,stdin) == sbuf )
    {
      printf("Sending:%s",sbuf);
      send(app.srvH, sbuf, strlen(sbuf)+1, remoteAddr, remotePort );
      
      if( strcmp(sbuf,"quit\n") == 0)
        break;
    }
  }

 errLabel:
  rc_t rc0 = destroy(app.srvH);

  return rcSelect(rc,rc0);  
}

cw::rc_t cw::net::srv::test_tcp_srv( socket::portNumber_t localPort, const char* remoteAddr, socket::portNumber_t remotePort )
{
  rc_t           rc;
  unsigned       recvBufByteCnt = 1024;
  unsigned       timeOutMs      = 100;
  const unsigned sbufN          = 31;
  char           sbuf[ sbufN+1 ];
  app_t          app;
  app.cbN = 0;
  
  if((rc = srv::create(app.srvH,
        localPort,
        socket::kBlockingFl | socket::kTcpFl | socket::kStreamFl,
        0,
        srvReceiveCallback,
        &app,
        recvBufByteCnt,
        timeOutMs,
        remoteAddr,
        remotePort )) != kOkRC )
    
    return rc;

  if((rc = srv::start( app.srvH )) != kOkRC )
    goto errLabel;

  while( true )
  {
    printf("? ");
    if( std::fgets(sbuf,sbufN,stdin) == sbuf )
    {
      printf("Sending:%s",sbuf);
      send(app.srvH, sbuf, strlen(sbuf)+1 );
      
      if( strcmp(sbuf,"quit\n") == 0)
        break;
    }
  }

 errLabel:
  rc_t rc0 = destroy(app.srvH);

  return rcSelect(rc,rc0);  
}