/****************************************************************************
 *   Socket.cpp                                                             *
 *    JRogers, Jan 2003                                                     *
 ****************************************************************************/
#include "Socket.h"
#include "SocketException.h"

#include <errno.h>
#include <fcntl.h>
#include <ctime>
#include <sys/time.h>


/****************************************************************************
 *  Socket class                                                            *
 *      Class invariant:  m_sock >= 0 iff m_sock is fd of an open socket    *
 ****************************************************************************/
// Constructor --- this neither creates nor connects the socket
Socket::Socket()
{
  m_sock =  -1;
  memset ( &m_addr, 0, sizeof ( m_addr ) );
}

// Destructor --- closes socket if it is still open
Socket::~Socket()
{
  cerr << "closing socket " << m_sock << endl;
  if ( is_valid() )
    ::close( m_sock );
}

// string addr() returns dotted octet address associated with socket
string Socket::addr() const
{
  char name[NAMELEN];

  inet_ntop( AF_INET, &m_addr.sin_addr, name, NAMELEN );
  return string(name);
}

// Ready_t ready( Ready_t ) test if ready for transaction type
Socket::Ready_t Socket::ready( const Socket::Ready_t type ) const
{
  fd_set rfds;
  fd_set wfds;
  fd_set xfds;

  if (!is_valid() ) 
    return 0;

  FD_ZERO(&rfds);
  FD_ZERO(&wfds);
  FD_ZERO(&xfds);
  if ( (type & RDY_RD) != 0 ) FD_SET(m_sock, &rfds);
  if ( (type & RDY_WR) != 0 ) FD_SET(m_sock, &wfds);
  if ( (type & RDY_EX) != 0 ) FD_SET(m_sock, &xfds);

  timeval tv;
  tv.tv_sec = 0;
  tv.tv_usec = 0;

  select( m_sock+1, &rfds, &wfds, &xfds, &tv );

  Ready_t status = 0;
  if ( FD_ISSET( m_sock, &rfds ) ) status |= RDY_RD;
  if ( FD_ISSET( m_sock, &wfds ) ) status |= RDY_WR;
  if ( FD_ISSET( m_sock, &xfds ) ) status |= RDY_EX;
  
  return status;
}


/****************************************************************************
 *  TCPSocket                                                               *
 ****************************************************************************/
bool TCPSocket::create()
{
  // create socket in IPv4 domain, with connection-oriented type & tcp protocol
  // getprotobyname returns a pointer to a protoent struct 
  //  the actual protocol number is in its p_proto field 
  //  (see getprotobyname(3))
  m_sock = socket ( PF_INET,
		    SOCK_STREAM,
		    getprotobyname("tcp")->p_proto );
  
  if ( ! is_valid() )
    return false;
  
  // this call to setsockopt sets the SO_REUSEADDR option at the socket level
  // this option allows reuse of local addresses to speed rebinding
  // (see socket(7)).
  int on = 1;  // option value
  if ( setsockopt( m_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof( on ) ) 
       == -1 )
    return false;
  
  return true;
}


//  bind() --- binds socket to port at any INADDR via bind(2)
//  returns true iff no error
bool TCPSocket::bind ( const int port )
{
  if ( ! is_valid() )
    return false;
  
  // Build socket address
  m_addr.sin_family = AF_INET;         // IPv4 Address Family
  m_addr.sin_addr.s_addr = INADDR_ANY; // bind to all local interfaces
  m_addr.sin_port = htons( port );     // must be in network byte order
  
  // null scope resolution operator prevents resolving to Socket::bind
  int bind_return = ::bind( m_sock,
                            ( struct sockaddr * ) &m_addr,
                            sizeof( m_addr ) );
  return( bind_return != -1 );
}


/****************************************************************************
 *  TCPDataSocket                                                           *
 *   Class invariant: isbufp != NULL iff *isbufp is istringstream in heap   *
 ****************************************************************************/

// Constructors
//   Allocate but do not create
TCPDataSocket::TCPDataSocket()
{
  isbufp = NULL;
}

//   Encapsulate existing socket
TCPDataSocket::TCPDataSocket( int sd, const sockaddr_in& addr )
{
  m_sock = sd;
  m_addr = addr;
  isbufp = NULL;
}


// Destructor
TCPDataSocket::~TCPDataSocket()
{
  if ( isbufp != NULL )
    delete isbufp;
}

//  bool send ( data_string ) --- send from C++ string
//  returns true iff no error
bool TCPDataSocket::send( const string& msg ) const
{
  // null scope resolution operator prevents resolving to Socket::send
  int status = ::send( m_sock, msg.c_str(), msg.size(), MSG_NOSIGNAL );
  return(status != -1 );
}

//  bool send( ostringstream ) --- send from ostringstream
  bool TCPDataSocket::send( const ostringstream& msg ) const
{
  // null scope resolution operator prevents resolving to Socket::send
  int status = ::send( m_sock, msg.str().c_str(), msg.str().size(), 
                       MSG_NOSIGNAL );
  return(status != -1 );
}

// bool send( msg, len )  --- send from C string
bool TCPDataSocket::send( const char* msg, size_t len ) const
{
  // null scope resolution operator prevents resolving to Socket::send
  int status = ::send( m_sock, msg, len, MSG_NOSIGNAL );
  return(status != -1 );
}


//  int recv ( data_string ) --- recieve into C++ string
//  returns number of characters received
int TCPDataSocket::recv( string& s ) const
{
  s.erase();
  return recvAppend(s);
}
//  int recvAppend ( data_string ) --- recieve and append to C++ string
//  returns number of new characters received
int TCPDataSocket::recvAppend( string& s) const
{
  char cbuf [ MAXRECV + 1 ];

  int status = recv( cbuf, MAXRECV );

  if ( status > 0 )
    s += string(cbuf);

  return status;
}

// istringstream& recv( ) --- receive data into istringstream
//  returns _reference_ to istringstream
istringstream& TCPDataSocket::recv( )
{
  if ( recv( sbuf ) < 0 )
    throw SocketException( "Error reading TCPDataSocket" );
  if (isbufp != NULL)
    delete isbufp;
  isbufp = new istringstream(sbuf);
  return *isbufp;
}

//  int recv( buf, len ) --- receives into C string
int TCPDataSocket::recv( char* buf, size_t len ) const
{
  for ( size_t i = 0; i < len; buf[i++] = 0);

  // null scope resolution operator prevents resolving to Socket::recv
  int status = ::recv( m_sock, buf, len, 0 );
  
  if ( status == -1 )
    {
      if ( errno == EAGAIN )
        status =  0;
      else
        {
          std::cerr << "status == -1   errno == " << errno 
                    << "  in Socket::recv\n";
        }
    }
  return status;
}
  

/****************************************************************************
 *  TCPserver                                                               *
 ****************************************************************************/
// Constructors
//  Create, bind and listen
TCPServerSocket::TCPServerSocket( int port )
{
  if ( ! TCPSocket::create() )
    throw SocketException ( "Could not create server socket" );

  if ( ! TCPSocket::bind ( port ) )
    throw SocketException ( "Could not bind to port" );

  if ( ! TCPServerSocket::listen() )
    throw SocketException ( "Could not listen to socket" );
}

//  Create but don't bind
TCPServerSocket::TCPServerSocket( )
{ 
  if ( ! TCPSocket::create() )
      throw SocketException ( "Could not create server socket" );
}

/// Socket initialization
//  listen() --- starts queueing connections 
//               number of pending connections is limited to MAXCONNECTIONS
//  returns true iff no error
bool TCPServerSocket::listen() const
{
  if ( ! is_valid() )
    return false;
  
  // null scope resolution operator prevents resolving to Socket::listen
  int listen_return = ::listen( m_sock, MAXCONNECTIONS );

  return( listen_return != -1 );
}


//  accept( ) --- waits for next connection on socket
//  returns pointer to connection socket, NULL if error
TCPDataSocket* TCPServerSocket::accept () const
{
  sockaddr_in addr;
  socklen_t addr_length = sizeof( addr );
  // null scope resolution operator prevents resolving to Socket::accept
  int sd = 
    ::accept( m_sock, ( sockaddr * ) &addr, &addr_length );

  TCPDataSocket* new_sock = new TCPDataSocket(sd, addr);
  if( new_sock->is_valid() )
    return new_sock;
  else
    return NULL;
}

/****************************************************************************
 *  TCPClientSocket                                                         *
 ****************************************************************************/
// Constructors
//  Create and bind
TCPClientSocket::TCPClientSocket( const string& host, int port )
{
  if ( ! TCPSocket::create() )
    throw SocketException ( "Could not create client socket" );

  if ( ! TCPClientSocket::connect( host, port ) )
    throw SocketException ( "Could not bind to port" );
}


//  Create but don't bind
TCPClientSocket::TCPClientSocket( )
{
  if ( ! TCPSocket::create() )
    throw SocketException ( "Could not create client socket" );
}

//// Client initialization
//  connect( host, port) --- connects socket to host/port
//  returns true iff no error
bool TCPClientSocket::connect( const string& host, int port )
{
  if ( ! is_valid() ) return false;
  
  // Build socket address
  m_addr.sin_family = AF_INET;      // Address Family IPv4
  m_addr.sin_port = htons( port );  // Port in network byte order

  hostent* hostp = gethostbyname( host.c_str() );
  if ( hostp == NULL )
    return false;
  memcpy((void *) &m_addr.sin_addr, hostp->h_addr_list[0], hostp->h_length );
  // null scope resolution operator prevents resolving to Socket::connect
  int status = ::connect ( m_sock, ( sockaddr * ) &m_addr, sizeof ( m_addr ) );

  return ( status >= 0 );
}


