/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CSocket.cpp  -  description
                             -------------------
    begin                : July 10 2002
    copyright            : (C) 2002-2006 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 <stdio.h>
//#include <stdlib.h>
//#include <string.h>
#include <errno.h>

//#include <iostream>

#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

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

using namespace std;
using namespace sptk;

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


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) {
		string errorStr = sys_errlist[errno];
		throw CException("Can't create a new socket: "+errorStr);
	}

	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;
}
