////////////////////////////////////////////////////////////////////////////////
//
// From David J. Kruglinski (Inside Visual C++).
//
////////////////////////////////////////////////////////////////////////////////

// removed baseclass CObject
// removed baseclass CException
// removed all LPCTSTR LPTSTR
// _select only defined to avoid compiler warning in WIN32
// make it run on linux

#ifndef INC_BLOCKINGSOCKET_H
#define INC_BLOCKINGSOCKET_H

#include <string>
#include <string.h>
#include <memory>
#include "Definements.h"

#ifdef WIN32
   #ifdef _WINSOCKAPI_
      #ifndef _WINSOCK2API_
      // NOTICE: In Microsoft Visual Studio you can add the header 'winsock2.h" to
      //         "Configuration --> Properties --> C/C++ --> Advanced --> Forced Include File"
      #pragma message("\nIt is recommended to include 'winsock2.h' instead of 'winsock.h'\n")
      #endif
   #else
      #include <winsock2.h>
   #endif
#elif defined(unix)
   #include <sys/socket.h>
   #include <arpa/inet.h>   // needed for inet_ntoa and inet_addr
   #include <netdb.h>       // needed for gethostbyname and gethostbyaddr
   #include <errno.h>
#endif

namespace nsSocket
{

#ifdef WIN32
   typedef int socklen_t;
   inline int _select(UINT_PTR /*nfds*/, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout)
   {
      // The nfds parameter is ignored in windows (included only for
      // compatibility with Berkeley sockets).
      return select(0, readfds, writefds, exceptfds, timeout);
   }
#elif defined(unix)
   typedef unsigned int SOCKET;
   typedef sockaddr SOCKADDR;
   typedef sockaddr* LPSOCKADDR;
   typedef sockaddr_in SOCKADDR_IN;
   typedef sockaddr_in* LPSOCKADDR_IN;
   typedef timeval TIMEVAL;
   #define SOCKET_ERROR   (-1)
   #define INVALID_SOCKET (SOCKET)(~0)
   #define closesocket close
   #define WSAGetLastError() errno
   #define WSAEINTR EINTR
   inline int _select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval *__restrict timeout)
   {
      return select(nfds, readfds, writefds, exceptfds, timeout);
   }
#endif

   typedef const struct sockaddr* LPCSOCKADDR;

///////////////////////////////////////////////////////////////////////////////////////
//***************************** CBlockingSocketException  ***************************//
///////////////////////////////////////////////////////////////////////////////////////
class CBlockingSocketException
{
public:
   // Constructor
   CBlockingSocketException(LPCTSTR pchMessage);
   virtual ~CBlockingSocketException() {}

   virtual bool GetErrorMessage(LPTSTR lpstrError, UINT nMaxError, PUINT pnHelpContext = NULL);
   virtual tstring GetErrorMessage(PUINT pnHelpContext = NULL);

private:
   int     m_nError;
   tstring m_strMessage;
};

///////////////////////////////////////////////////////////////////////////////////////
//*********************************** CSockAddr  ************************************//
///////////////////////////////////////////////////////////////////////////////////////
class CSockAddr : public sockaddr_in
{
public:
   // constructors
   CSockAddr()
   {
      sin_family        = AF_INET; // specifies address family
      sin_port          = 0;       // ip port
      sin_addr.s_addr   = 0;       // ip address
   }

   CSockAddr(const SOCKADDR& sa)
            { memcpy(this, &sa, sizeof(SOCKADDR)); }

   CSockAddr(const SOCKADDR_IN& sin)
            { memcpy(this, &sin, sizeof(SOCKADDR_IN)); }

   CSockAddr(ULONG ulAddr, USHORT ushPort = 0) // parms are host byte ordered
            {
               sin_family      = AF_INET;          // specifies address family
               sin_port        = htons(ushPort);   // ip port
               sin_addr.s_addr = htonl(ulAddr);    // ip address
            }

   CSockAddr(const char* pchIP, USHORT ushPort = 0) // dotted IP addr string
            {
               sin_family = AF_INET;               // specifies address family
               sin_port = htons(ushPort);          // ip port
               sin_addr.s_addr = inet_addr(pchIP); // ip address
            } // already network byte ordered

   // Return the address in dotted-decimal format
   tstring DottedDecimal()
      { return nsHelper::CCnv::ConvertToTString(inet_ntoa(sin_addr)); }

   // Get port and address (even though they're public)
   USHORT Port() const
            { return ntohs(sin_port); }

   ULONG IPAddr() const
            { return ntohl(sin_addr.s_addr); }

   // operators added for efficiency
   CSockAddr& operator=(const SOCKADDR& sa)
            {
               memcpy(this, &sa, sizeof(SOCKADDR));
               return *this;
            }

   CSockAddr& operator=(const SOCKADDR_IN& sin)
            {
               memcpy(this, &sin, sizeof(SOCKADDR_IN));
               return *this;
            }

   operator SOCKADDR()
            { return *((LPSOCKADDR) this); }

   operator LPSOCKADDR()
            { return (LPSOCKADDR) this; }

   operator LPSOCKADDR_IN()
            { return (LPSOCKADDR_IN) this; }
};

///////////////////////////////////////////////////////////////////////////////////////
//********************************* CBlockingSocket  ********************************//
///////////////////////////////////////////////////////////////////////////////////////

class IBlockingSocket
{
public:
   virtual ~IBlockingSocket() {};
   virtual IBlockingSocket* CreateInstance() const = 0;
   virtual void Create(int nType = SOCK_STREAM) = 0;
   virtual void Connect(LPCSOCKADDR psa) const = 0;
   virtual void Bind(LPCSOCKADDR psa) const = 0;
   virtual void Listen() const = 0;
   virtual void Cleanup() = 0;
   virtual bool Accept(IBlockingSocket& s, LPSOCKADDR psa) const = 0;
   virtual void Close() = 0;
   virtual int  Write(const char* pch, int nSize, int nSecs) const = 0;
   virtual int  Receive(char* pch, int nSize, int nSecs) const = 0;
   virtual void GetPeerAddr(LPSOCKADDR psa) const = 0;
   virtual void GetSockAddr(LPSOCKADDR psa) const = 0;
   virtual operator SOCKET() const = 0;
   virtual bool CheckReadability() const = 0;
   virtual CSockAddr   GetHostByName(const char* pchName, USHORT ushPort = 0) = 0;
   virtual const char* GetHostByAddr(LPCSOCKADDR psa) = 0;
};

// member functions truly block and must not be used in UI threads
// use this class as an alternative to the MFC CSocket class
class CBlockingSocket : public IBlockingSocket
{
public:
   CBlockingSocket();
   virtual ~CBlockingSocket();

   virtual IBlockingSocket* CreateInstance() const;

   void Cleanup();
   void Create(int nType = SOCK_STREAM);
   void Close();

   void Bind(LPCSOCKADDR psa) const;
   void Listen() const;
   void Connect(LPCSOCKADDR psa) const;
   bool Accept(IBlockingSocket& s, LPSOCKADDR psa) const;
   int  Send(const char* pch, int nSize, int nSecs) const;
   int  Write(const char* pch, int nSize, int nSecs) const;
   int  Receive(char* pch, int nSize, int nSecs) const;
   int  SendDatagram(const char* pch, int nSize, LPCSOCKADDR psa, int nSecs) const;
   int  ReceiveDatagram(char* pch, int nSize, LPSOCKADDR psa, int nSecs) const;
   void GetPeerAddr(LPSOCKADDR psa) const;
   void GetSockAddr(LPSOCKADDR psa) const;
   bool CheckReadability() const;

   CSockAddr   GetHostByName(const char* pchName, USHORT ushPort = 0);
   const char* GetHostByAddr(LPCSOCKADDR psa);

   operator SOCKET() const { return m_hSocket; }

private:
   SOCKET m_hSocket;
};

std::unique_ptr<IBlockingSocket> CreateDefaultBlockingSocketInstance();

///////////////////////////////////////////////////////////////////////////////////////
//******************************** CHttpBlockingSocket ******************************//
///////////////////////////////////////////////////////////////////////////////////////
class CHttpBlockingSocket : public CBlockingSocket
{
public:
   enum {nSizeRecv = 1000}; // max receive buffer size (> hdr line length)

   CHttpBlockingSocket();
   ~CHttpBlockingSocket();

   int ReadHttpHeaderLine(char* pch, int nSize, int nSecs);
   int ReadHttpResponse(char* pch, int nSize, int nSecs);

private:
   char*  m_pReadBuf; // read buffer
   int    m_nReadBuf; // number of bytes in the read buffer
};

}

#endif // INC_BLOCKINGSOCKET_H
