/***************************************************************************
                          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"
 ***************************************************************************/

#include <sptk3/CSocket.h>
#include <sptk3/CException.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifndef _WIN32
#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/time.h>

#ifdef __APPLE__
typedef int socklen_t;
#endif

#else
typedef int socklen_t;
#endif

#ifdef _WIN32
int   CSocket::m_socketCount;
bool  CSocket::m_inited = false;
#endif

using namespace std;

CSocketReader::CSocketReader(int buffer_size)
: CBuffer(buffer_size) {
   m_socket = 0;
   m_readOffset = 0;
   m_buffer[buffer_size-1] = 0;
}

void CSocketReader::open(int socket) {
   m_socket = socket;
   m_readOffset = 0;
}

int CSocketReader::bufferedRead(char *dest,int sz,bool read_line) {
   int availableBytes = m_bytes - m_readOffset;
   int bytesToRead = sz;
   bool eol = 0;
   if (!availableBytes) {
      m_readOffset = 0;
#ifdef _WIN32
      m_bytes = recv(m_socket, m_buffer, m_size-2, NULL);
#else
      m_bytes = recv(m_socket, m_buffer, m_size-2, 0);
      //m_bytes = ::read(m_socket, m_buffer, m_size-2);
#endif
      availableBytes = m_bytes;
      m_buffer[m_bytes] = 0;
      if (!m_bytes)
         return 0;
   }
   char *readPosition = m_buffer+m_readOffset;
   if (availableBytes < bytesToRead)
      bytesToRead = availableBytes;

   if (read_line) {
      char *cr = (char *)strchr(readPosition,'\n');
      if (cr) {
         eol = 1;
         bytesToRead = cr - readPosition + 1;
         *cr = 0;
         if (bytesToRead) {
            cr--;
            if (*cr == '\r')
               *cr = 0;
         }
      }
   }

   // copy data to dest, advance the read offset
   memcpy(dest,readPosition,bytesToRead);
   m_readOffset += bytesToRead;
   if (eol) // Indicate, that we have a complete string
      return -bytesToRead;

   return bytesToRead;
}

int CSocketReader::read(char *dest,int sz,bool read_line) {
   int total = 0;
   int eol = 0;

   if (m_socket <= 0)
      throw CException("Can't read from closed socket");

   while (!eol) {
      int bytesToRead = sz - total;
      if (bytesToRead <= 0) return sz;

      int bytes = bufferedRead(dest,bytesToRead,read_line);

      if (!bytes) // No more data
         break;

      if (bytes < 0) { // Received the complete string
         eol = 1;
         bytes = -bytes;
      }

      total += bytes;
      dest += bytes;
   }
   return total - eol;
}

int CSocketReader::readLine(CBuffer& destBuffer) {
   int total = 0;
   int eol = 0;

   if (m_socket <= 0)
      throw CException("Can't read from closed socket");

   while (!eol) {
      int bytesToRead = destBuffer.size() - total;
      if (bytesToRead <= 128) {
         destBuffer.checkSize(destBuffer.size() + 128);
         bytesToRead = destBuffer.size() - total - 1;
      }

      char *dest = destBuffer.data() + total;

      int bytes = bufferedRead(dest,bytesToRead,true);

      if (!bytes) // No more data
         break;

      if (bytes < 0) { // Received the complete string
         eol = 1;
         bytes = -bytes;
      }

      total += bytes;
   }
   destBuffer.data()[total] = 0;
   destBuffer.bytes(total);
   return destBuffer.bytes();
}

#ifdef _WIN32
void CSocket::init() {
   if (m_inited) return;
   m_inited =  true;
   WSADATA  wsaData;
   WORD     wVersionRequested = MAKEWORD( 2, 0 );
   WSAStartup( wVersionRequested, &wsaData );
}

void CSocket::cleanup() {
   m_inited =  false;
   WSACleanup();
}
#endif

// Constructor
CSocket::CSocket(int domain,int type,int protocol)
: m_reader(1024) {
#ifdef _WIN32
   init();
   m_socketCount++;
#endif

   m_sockfd   = INVALID_SOCKET;
   m_domain   = domain;
   m_type     = type;
   m_protocol = protocol;
   m_port     = 0;

   FD_ZERO(&m_inputs);
   FD_ZERO(&m_outputs);
}

// Destructor
CSocket::~CSocket() {
   close();
#ifdef _WIN32
   m_socketCount--;
   if (!m_socketCount)
      cleanup();
#endif
}

int CSocket::control(int flag, unsigned long *check) {
#ifdef _WIN32
   return ioctlsocket(m_sockfd, flag, check);
#else
   return fcntl(m_sockfd, flag, *check);
#endif
}

void CSocket::host(string hostName) {
   m_host = hostName;
}

void CSocket::port(int portNumber) {
   m_port = portNumber;
}

// Connect & disconnect
void CSocket::open_addr(struct sockaddr_in& addr,bool forBind) {
   if ( active() ) close();

   // Create a new socket
   m_sockfd = socket(m_domain, m_type, m_protocol);
   if (m_sockfd == INVALID_SOCKET)
      throw CException("Can't create a new socket");

   int rc;
   string currentOperation;

   if (!forBind) {
      rc = connect(m_sockfd, (struct sockaddr *)&addr,sizeof( addr));
      currentOperation = "connect";
   } else {
      rc = bind(m_sockfd, (struct sockaddr *)&addr,sizeof( addr));
      currentOperation = "bind";
      if (!rc) {
         rc = ::listen(m_sockfd,5);
         currentOperation = "listen";
      }
   }

   if (rc) {
      close();
      throw CException("Can't connect: "+currentOperation+"() failed.");
   }

   FD_SET(m_sockfd, &m_inputs);
   FD_SET(m_sockfd, &m_outputs);

   m_reader.open(m_sockfd);
}

void CSocket::open(string hostName,int portNumber) {
   if (hostName.length()) m_host = hostName;
   if (!m_host.length())
      throw CException("Please, define the host name");
   if (portNumber) m_port = portNumber;

   sockaddr_in addr;
   struct hostent *host_info;

   host_info = gethostbyname(m_host.c_str());
   if (!host_info)
      throw CException("Can't connect. Host is unknown.");

   memset(&addr, 0, sizeof(addr));
   addr.sin_family = m_domain;
   memcpy(&addr.sin_addr, host_info->h_addr, host_info->h_length);
   addr.sin_port = htons(m_port);

   if ( active() ) close();

   open_addr(addr);
}

void CSocket::listen(int portNumber) {
   if (portNumber) m_port = portNumber;

   sockaddr_in addr;

   memset(&addr, 0, sizeof(addr));
   addr.sin_family = m_domain;
   addr.sin_addr.s_addr = htonl(INADDR_ANY);
   addr.sin_port = htons(m_port);

   open_addr(addr,true);
}

void CSocket::close() {
   if (m_sockfd != INVALID_SOCKET) {
      m_reader.close();
      FD_CLR(m_sockfd,&m_inputs);
      FD_CLR(m_sockfd,&m_outputs);
#ifndef _WIN32
      shutdown(m_sockfd, SHUT_RDWR);
#else
      closesocket(m_sockfd);
#endif
      m_sockfd = INVALID_SOCKET;
   }
}

void CSocket::accept(int& clientSocketFD,struct sockaddr_in& clientInfo) {
   socklen_t len = sizeof(clientInfo);
   clientSocketFD = ::accept(m_sockfd,(struct sockaddr *)&clientInfo,&len);
}

void CSocket::attach(int socketHandle) { 
   close(); 
   m_sockfd = socketHandle; 

   FD_SET(m_sockfd, &m_inputs);
   FD_SET(m_sockfd, &m_outputs);

   m_reader.open(m_sockfd);
}

char CSocket::getChar() {
   char ch;
#ifdef _WIN32
   int bytes = recv(m_sockfd, &ch, 1, NULL);
#else
   int bytes = ::read(m_sockfd, &ch, 1);
#endif
   if (!bytes) return 0;
   return ch;
}

int CSocket::readLine(char *buffer,int size) {
   return m_reader.read(buffer,size,true);
}

int CSocket::readLine(CBuffer& buffer) {
   int rc = m_reader.readLine(buffer);
   return rc;
}

int CSocket::readLine(std::string& s) {
   m_reader.readLine(m_stringBuffer);
   s = m_stringBuffer.data();
   return m_stringBuffer.size() - 1;
}

int CSocket::read(char *buffer,int size) {
   return m_reader.read(buffer,size);
}

int CSocket::read(CBuffer& buffer) {
   int rc = m_reader.read(buffer.data(),buffer.size(),false);
   buffer.bytes(rc);
   return rc;
}

int CSocket::write(const char *buffer,int size) {
   int        bytes;
   const char *p = buffer;
   int   total = size;
   while (size > 0) {
#ifdef _WIN32
      bytes = send(m_sockfd, p, size, NULL);
#else
      bytes = ::write(m_sockfd, p, size);
#endif
      size -= bytes;
      p += bytes;
   }
   return total;
}

int  CSocket::write(const CBuffer& buffer) {
   char *ptr = buffer.data();
   int   bytes = buffer.bytes();
   while (bytes > 0) {
      int rc = write(ptr,bytes);
      bytes -= rc;
      ptr += rc;
   }
   return buffer.bytes();
}

bool CSocket::readyToRead(int wait_msec) {
   struct timeval timeout;
   timeout.tv_sec = wait_msec / 1000;
   timeout.tv_usec = wait_msec % 1000 * 1000;

    //FD_ZERO(&inputs);
   FD_SET(m_sockfd,&m_inputs);

   select(FD_SETSIZE, &m_inputs, NULL, NULL, &timeout);
   return (FD_ISSET(m_sockfd ,&m_inputs)>0);
}

bool CSocket::readyToWrite() {
   return true;
}

#ifdef _WIN32
# define VALUE_TYPE(val) (char*)(val)
#else
# define VALUE_TYPE(val) (void*)(val)
#endif

int CSocket::setOption(int level,int option,int  value) {
   socklen_t len = sizeof(socklen_t);
   return setsockopt(m_sockfd, level, option, VALUE_TYPE(&value), len);
}

int CSocket::getOption(int level,int option,int& value) {
   socklen_t len = sizeof(socklen_t);
   return getsockopt(m_sockfd, level, option, VALUE_TYPE(&value), &len);
}

const CSocket& CSocket::operator << (const std::string& s) {
   write(s.c_str(),(int)s.length());
   return *this;
}

const CSocket& CSocket::operator >> (std::string& s) {
   readLine(s);

   return *this;
}
