//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org> 
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
#ifndef cwTcpPort_H
#define cwTcpPort_H


namespace cw
{
  namespace net
  {
    namespace socket
    {
      typedef handle<struct socket_str> handle_t;

      typedef unsigned short portNumber_t;
      
      enum
      {
       kNonBlockingFl   = 0x000,  // create a non-blocking socket  
       kBlockingFl      = 0x001,  // create a blocking socket  
       kTcpFl           = 0x002,  // create a TCP socket rather than a UDP socket
       kBroadcastFl     = 0x004,
       kReuseAddrFl     = 0x008,
       kReusePortFl     = 0x010,
       kMultiCastTtlFl  = 0x020,
       kMultiCastLoopFl = 0x040,
       kListenFl        = 0x080,
       kStreamFl        = 0x100,  // connected stream (not Datagram)
      };

      enum
      {
       // port 0 is reserved by and is therefore a convenient invalid port number
       kInvalidPortNumber = 0 
      };
      
      
      rc_t create( handle_t& hRef,
        portNumber_t         port,
        unsigned             flags,
        unsigned             timeOutMs  = 100, // time out to use with recv() on blocking sockets
        const char*          remoteAddr = NULL,
        portNumber_t         remotePort = socket::kInvalidPortNumber,
        const char*          localAddr  = NULL );
      
      rc_t destroy( handle_t& hRef );

      unsigned flags( handle_t h );

      rc_t set_multicast_time_to_live( handle_t h, unsigned seconds );

      rc_t join_multicast_group( handle_t h, const char* addr );

      rc_t setTimeOutMs( handle_t h, unsigned timeOutMs );

      // Listen for a connections
      rc_t accept( handle_t h );

      // Set a destination address for this socket. Once a destination address is set
      // the caller may use send() to communicate with the specified remote socket
      // without having to specify a destination address on each call.
      rc_t connect( handle_t h, const char* remoteAddr, portNumber_t port );

      // Return true if this socket is connected to a remote endpoint.
      bool isConnected( handle_t h );
      
      // Send a message to a remote UDP socket over a previously connected socket
      rc_t send( handle_t h, const void* data, unsigned dataByteCnt );
      
      // Send a message to a specific remote node over an unconnected socket.
      // Use the function initAddr() to setup the 'sockaddr_in';
      rc_t send( handle_t h, const void* data, unsigned dataByteCnt, const struct sockaddr_in* remoteAddr );
      rc_t send( handle_t h, const void* data, unsigned dataByteCnt, const char* remoteAddr, portNumber_t port );

      // Receive incoming messages by directly checking the internal
      // socket for waiting data.  This function is used to receive
      // incoming data when the internal listening thread is not used.
      // Note that if kBlockingFl was set in create() this call will
      // block for available data or for 'timeOutMs' milliseconds,
      // whichever comes first (as set in create()).  If
      // kNonBlockingFl was set in create() then the function will
      // return immediately if no incoming messages are waiting.  If
      // recvByteCntRef is valid (non-NULL) then it is set to the
      // length of the received message or 0 if no msg was received.
      rc_t receive(handle_t h, char* data, unsigned dataByteCnt, unsigned* recvByteCntRef=nullptr, struct sockaddr_in* fromAddr=nullptr );

      // 
      rc_t select_receive(handle_t h, char* buf, unsigned bufByteCnt, unsigned timeOutMs, unsigned* recvByteCntRef=nullptr, struct sockaddr_in* fromAddr=nullptr );

      //
      rc_t recv_from(handle_t h, char* buf, unsigned bufByteCnt, unsigned* recvByteCntRef=nullptr, struct sockaddr_in* fromAddr=nullptr );

      // Note that 
      rc_t get_mac( handle_t h, unsigned char buf[6], struct sockaddr_in* addr=nullptr, const char* netInterfaceName=nullptr );

      // Prepare a struct sockadddr_in for use with send()
      rc_t        initAddr( const char* addrStr, portNumber_t portNumber, struct sockaddr_in* retAddrPtr );
      
      rc_t        addrToString( const struct sockaddr_in* addr, char* buf, unsigned bufN=INET_ADDRSTRLEN );
      
      bool        addrIsEqual( const struct sockaddr_in* addr0, const struct sockaddr_in* addr1 );
      
      const char*   hostName( handle_t h );
      const char*   ipAddress( handle_t h );
      unsigned      inetAddress( handle_t h );
      portNumber_t  port( handle_t h );
      rc_t          peername( handle_t h, struct sockaddr_in* addr );

      rc_t get_info( const char* netInterfaceName, unsigned char mac[6], char* hostBuf=nullptr, unsigned hostBufN=_POSIX_HOST_NAME_MAX, struct sockaddr_in* addr=nullptr );

      
      
    }
  }    
}


#endif
