/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CSocket.cpp  -  description
                             -------------------
    begin                : July 10 2002
    copyright            : (C) 2002-2005 by Alexey Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it 
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at 
   your option) any later version.

   This library is distributed in the hope that it will be useful, but 
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library
   General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; if not, write to the Free Software Foundation, 
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

#ifndef __CSOCKET_H__
#define __CSOCKET_H__

/// @addtogroup utility Utility Classes
/// @{

#ifndef _WIN32
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

/// A socket handle is an integer
typedef int SOCKET;           

/// A value to indicate an invalid handle
#define INVALID_SOCKET -1     

#else
   //#define _WINSOCKAPI_
#include <windows.h>
#include <winsock.h>
#endif

#include <sptk3/CException.h>
#include <sptk3/CStrings.h>
#include <sptk3/CBuffer.h>

/// Buffered Socket reader.
class CSocketReader : protected CBuffer {
   int     m_socket;       ///< Socket handle
   int     m_readOffset;   ///< Current offset in the read buffer

   /// Performs buffered read from the opened socket
   /// into a character buffer of limited size
   /// @param dest char *, destination buffer
   /// @param sz int, size of the destination buffer
   /// @param readLine bool, true if we want to read one line (ended with CRLF) only
   int bufferedRead(char *dest,int sz,bool readLine);

public:

   /// Constructor
   /// @param bufferSize int, the desirable size of the internal buffer
   CSocketReader(int bufferSize=4096);

   /// Connects the reader to the socket handle
   /// @param socket int, socket handle
   void open(int socket);

   /// Disconnects the reader from the socket handle
   void close() { m_socket = 0; }

   /// Performs the buffered read
   /// @param dest char *, destination buffer
   /// @param sz int, size of the destination buffer
   /// @param readLine bool, true if we want to read one line (ended with CRLF) only
   int  read(char *dest,int sz,bool readLine=false);

   /// Performs the buffered read of '\n'-terminated string
   /// @param dest CBuffer&, destination buffer
   int  readLine(CBuffer& dest);
};

/// @brief Generic socket.
///
/// Allows to establish a network connection
/// to the host by name and port address
class CSocket {
protected:
   SOCKET              m_sockfd;       ///< Socket internal (OS) handle
   int                 m_domain;       ///< Socket domain type
   int                 m_type;         ///< Socket type
   int                 m_protocol;     ///< Socket protocol
   std::string         m_host;         ///< Host name
   int                 m_port;         ///< Port number
   fd_set              m_inputs;       ///< The set of socket descriptors for reading
   fd_set              m_outputs;      ///< The set of socket descriptors for writing
   CSocketReader       m_reader;       ///< Socket buffered reader
   CBuffer             m_stringBuffer; ///< Buffer to read a line

protected:

#ifdef _WIN32
   static int  m_socketCount;          ///< Total number of the objects of this class
   static bool m_inited;               ///< Were the sockets inited?
   static void init();                 ///< WinSock initialization
   static void cleanup();              ///< WinSock cleanup
#endif

   /// Reads a single char from the socket
   char getChar();                 

public:
   /// Constructor
   CSocket(int domain=AF_INET, int type=SOCK_STREAM, int protocol=0);

   /// Destructor
   virtual ~CSocket();

   /// Returns socket handle
   int  handle() const { return m_sockfd; }

   /// Attaches socket handle
   void attach(int socketHandle);

   /// Sets the host name
   void host(std::string hostName);

   /// Returns the host name
   std::string host() const { return m_host; }

   /// Sets the port number
   void port(int portNumber);

   /// Returns the current port number
   /// @returns port number
   int port() const { return m_port; }

   /// Opens the socket connection by address.
   /// @param addr struct sockaddr_in&, defines socket address/port information
   /// @param forBind bool, true for the server socket and false for the client socket
   void open_addr(struct sockaddr_in& addr,bool forBind=false);

   /// Opens the client socket connection by host and port
   virtual void open(std::string hostName="", int port=0);

   /// Opens the server socket connection on port (binds/listens)
   void listen(int portNumber=0);

   /// In server mode, waits for the incoming connection.
   /// When incoming connection is made, exits returning the connection info
   /// @param clientSocketFD int&, connected client socket FD
   /// @param clientInfo sockaddr_in&, connected client info
   void accept(int& clientSocketFD,struct sockaddr_in& clientInfo);

   /// Closes the socket connection 
   virtual void close();

   /// Returns the current socket state
   /// @returns true if socket is opened
   bool active() const { return m_sockfd != INVALID_SOCKET; }

   /// Calls Unix fcntl() or Windows ioctlsocket()
   int  control(int flag, unsigned long *check);

   /// Sets socket option value
   int  setOption(int level,int option,int  value);

   /// Gets socket option value
   int  getOption(int level,int option,int& value);

   /// Reads one line (terminated with CRLF) from the socket
   /// into existing memory buffer. The output string should fit
   /// the buffer or returned incomplete.
   /// @param buffer char *, the destination buffer
   /// @param size int, the destination buffer size
   /// @returns the number of bytes read from the socket
   int readLine(char *buffer,int size);

   /// Reads one line (terminated with CRLF) from the socket
   /// into existing memory buffer. The memory buffer is 
   /// extended to fit the string.
   /// @param buffer CBuffer&, the destination buffer
   /// @returns the number of bytes read from the socket
   int readLine(CBuffer& buffer);

   /// Reads one line (terminated with CRLF) from the socket
   /// into string
   /// @param s std::string&, the destination string
   /// @returns the number of bytes read from the socket
   int readLine(std::string& s);

   /// Reads data from the socket
   /// @param buffer char *, the memory buffer
   /// @param size int, the memory buffer size
   /// @returns the number of bytes read from the socket
   int read(char *buffer,int size);

   /// Reads data from the socket into memory buffer
   /// @param buffer const CBuffer&, the memory buffer
   /// @returns the number of bytes read from the socket
   int read(CBuffer& buffer);

   /// Writes data to the socket
   /// @param buffer const char *, the memory buffer
   /// @param size int, the memory buffer size
   /// @returns the number of bytes written to the socket
   int write(const char *buffer,int size);

   /// Writes data to the socket
   /// @param buffer const CBuffer&, the memory buffer
   /// @returns the number of bytes written to the socket
   int write(const CBuffer& buffer);

   /// Reports true if socket is ready for reading from it
   bool readyToRead(int waitmsec);

   /// Reports true if socket is ready for writing to it
   bool readyToWrite();

   /// Stream std::string input
   const CSocket& operator << ( const std::string& );

   /// Stream std::string output
   const CSocket& operator >> ( std::string& );
};

/// @}

#endif
