#ifdef __GNUG__
    #pragma implementation "TCPSocket.h"
#endif

#ifdef __WXMSW__
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWindows headers)
#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif
#else
#include <stdafx.h>
#include <portable.h>
#endif

#include "tcpsocket.h"

TCPSocket::TCPSocket() {
	m_connected = false;
	m_block_mode = true;
	newfd = 0;
}

TCPSocket::~TCPSocket() {
	Close();
}

bool TCPSocket::Create() {
	sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(sockfd==-1)
		return false;
	
	return true;
}

bool TCPSocket::Attach(TCPSocket& sock)
{
	sockfd = sock.newfd;
	m_block_mode = sock.m_block_mode;

	sock.newfd = 0;

	if(sockfd<=0)
		return false;

	m_connected = true;

	return true;
}

bool TCPSocket::Create(char nType) {
	if(nType == TCP)
		return Create();
	if(nType == UDP) {
		sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
		if(sockfd==-1)
			return false;
		
		return true;
	}
	return false;
}

bool TCPSocket::Connect(int nPort, const char *Hostname) {
	hostent *Host;
	
	if((Host = gethostbyname(Hostname)) == NULL) {
		wxLogError("gethosebyname");
		return false;
	}

	
	INetAddress.sin_family = AF_INET;
	INetAddress.sin_port = htons(nPort);
	INetAddress.sin_addr = *((struct in_addr *)Host->h_addr);
	memset(&(INetAddress.sin_zero), 0, 8);
	
	if(connect(sockfd, (struct sockaddr *)&INetAddress, sizeof(struct sockaddr)) == -1) {
		wxLogDebug("connect");
		return false;
	}

	m_connected = TRUE;

	return true;
}

int TCPSocket::Send(const void *Message, int nLength) {
	int Bytes_Sent;
	
	if(newfd==0) 
		Bytes_Sent = send(sockfd, (const char *)Message, nLength, 0);
	else
		Bytes_Sent = send(newfd, (const char *)Message, nLength, 0);
	
	return Bytes_Sent;
}

int TCPSocket::Recv(void *Buffer, int nLength) {
	int Bytes_Recv;
	if(newfd==0)
		Bytes_Recv = recv(sockfd, (char *)Buffer, nLength, 0);
	else
		Bytes_Recv = recv(newfd, (char *)Buffer, nLength, 0);
	
	return Bytes_Recv;
}

bool TCPSocket::Write8(wxUint8 i)
{
	return Send(&i, 1) == 1;
}

bool TCPSocket::Write32(wxUint32 i)
{
	return Send(&i, 4) == 4;
}

bool TCPSocket::WriteString(const wxString& string)
{
#ifndef _AFX
#if wxUSE_UNICODE
  const wxWX2MBbuf buf = string.mb_str(m_conv);
#else
  const wxWX2MBbuf buf = string.mb_str();
#endif
#else
	const char *buf = string;
#endif
  size_t len = strlen(buf);
  Write32(len);
  if (len > 0)
      Send(buf, len);

  return TRUE;
}

bool TCPSocket::ReadString(wxString& string, int ms_timeout)
{
	wxUint32 len;

	if (!Read32(len, ms_timeout))
		return false;

	string = "";
	if (len) {
#ifndef _AFX
		string.Alloc(len);
#endif
		for (int index=len; index; index--) {
			string+= (wxChar) Read8();
		}
	}
	return true;
}

wxUint8 TCPSocket::Read8()
{
	wxUint8 ret;

	Recv(&ret, 1);

	return ret;
}

bool TCPSocket::Read32(wxUint32& value, int ms_timeout)
{
	int bytes;
	unsigned char *p;
	bool mode = m_block_mode;
/*
	if (ms_timeout) {
		SetBlockMode(false);
	}
*/
	bytes = 0;
	p = (unsigned char *)&value;
	do {
		if (ms_timeout) {
			int avb;
			if (GetBytesAvailable(avb, ms_timeout)<0 || !avb) {
				return false;
			}
		}
		bytes+= Recv(p, 4-bytes);
		p = ((unsigned char *)&value) + bytes;
	} while (bytes<4);

//	SetBlockMode(mode);
	return true;
}

bool TCPSocket::Close() {
	int Result;
	if(newfd==0)
		Result = closesocket(sockfd);
	else
		Result = closesocket(newfd);
	if(Result == -1) 
		return false;

	m_connected = FALSE;

	return true;
}

bool TCPSocket::InboundClose() {

	if (!newfd)
		return false;

	int Result;
	Result = closesocket(newfd);
	
	if(Result == -1)
		return false;
	
	return true;
}

bool TCPSocket::Create(int nPort, char nType, bool blocking) {
	if(nType == TCP) {
		sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if(sockfd==-1)
			return false;
	}
	if(nType == UDP) {
		sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
		if(sockfd==-1)
			return false;
	}
	
	if (!blocking)
		SetBlockMode(false);

	INetAddress.sin_family = AF_INET;
	INetAddress.sin_port = htons(nPort);
//	INetAddress.sin_addr.s_addr = inet_addr("127.0.0.1");

	INetAddress.sin_addr.s_addr = INADDR_ANY;
	memset(INetAddress.sin_zero, 0, 8);
	
	if(bind(sockfd, (struct sockaddr *)&INetAddress, sizeof(struct sockaddr)) == -1) {
		int wsa_err = WSAGetLastError();
		wxLogDebug("bind %d", wsa_err);
		return false;
	}
	return true;
}

bool TCPSocket::SetBlockMode(bool block)
{
	unsigned long on = block ? 0 : 1;

	if (block==m_block_mode)
		return true;

	if (ioctlsocket(sockfd, FIONBIO, &on)<0)
	{
		return false;
	}
	m_block_mode = block;
	return true;
}

bool TCPSocket::Listen() {
	if(listen(sockfd, 5) == -1) {
		wxLogDebug("Listen");
		return false;
	}
	return true;
}

bool TCPSocket::WaitConnection(int ms_timeout)
{
	int total;
	struct fd_set Writer, Reader;
	struct timeval timeout;

	FD_ZERO(&Reader);
	FD_ZERO(&Writer);

	// Check for connection attempts.
	FD_SET(sockfd, &Reader);

	timeout.tv_sec = 0;
	timeout.tv_usec = ms_timeout * 1000;

	if ((total = select(sockfd+1, &Reader, &Writer, NULL, &timeout)) == SOCKET_ERROR)
	{
		return false;
	}
	if (FD_ISSET(sockfd, &Reader))
	{
		return true;
	}
	return false;
}

bool TCPSocket::Accept() {
	size_t sin_size = sizeof(struct sockaddr_in);
	//sockaddr_in RemoteAddress;
	if((newfd = accept(sockfd, (struct sockaddr *)&RemoteAddress, (int *)&sin_size)) == -1) {
		wxLogDebug("accept");
		return false;
	}
	return true;
}

char *TCPSocket::GetRemoteIP() {
	return inet_ntoa(RemoteAddress.sin_addr);
}

int TCPSocket::GetBytesAvailable(int& bytes, int ms_timeout)
{
	int total;
	struct fd_set Reader;
	struct timeval timeout;

	FD_ZERO(&Reader);

	// Check for connection attempts.
	FD_SET(sockfd, &Reader);

	timeout.tv_sec = 0;
	timeout.tv_usec = ms_timeout * 1000;

	if ((total = select(sockfd+1, &Reader, NULL, NULL, &timeout)) == SOCKET_ERROR)
	{
		return -2;
	}
	if (FD_ISSET(sockfd, &Reader))
	{
		unsigned long on;
		ioctlsocket(sockfd, FIONREAD, &on);
		bytes = on;
       	return 0;
	}
	// timeout
	return -1;
}
