//=========================================================
// mSock
//=========================================================

#include "mSock.h"

// Platform-specific includes and definitions
#ifdef WIN32

	// Windows
	#include <winsock2.h>
	#pragma comment(lib, "ws2_32.lib")
	
	#define EWOULDBLOCK WSAEWOULDBLOCK
	#define LASTERROR WSAGetLastError()
	
#else

	// Linux / POSIX
	#include <fcntl.h>
	#include <netdb.h>
	#include <sys/types.h>
	#include <sys/socket.h>
	#include <netinet/in.h>
	#include <errno.h>
	
	typedef unsigned int SOCKET;
	#define LASTERROR errno
	#define SD_BOTH 2
	#define closesocket close
	#define INVALID_SOCKET -1
	#define SOCKET_ERROR -1
	#define SOCKADDR_IN sockaddr_in
	#define SOCKADDR sockaddr
	
#endif

// Internal configuration
#define MSOCK_MAX_BUFFER_SIZE 4096
#define MSOCK_MAX_QUEUE_CONNS 10

// Required libraries
#include <string>
#include <cstring> // for strlen()
using namespace std;

struct mSock_data_members {
	SOCKET sock;
	bool bValid;
	string sCache;
};

//=========================================================
// Helpers
//=========================================================

void mSock_unblock_socket(SOCKET sock) {
	u_long iMode = 1;
#ifdef WIN32
	ioctlsocket(sock, FIONBIO, &iMode);
#else
	int flags = fcntl(sock, F_GETFL, 0);
	fcntl(sock, F_SETFL, flags | O_NONBLOCK);
#endif
};

void mSock_invalidate_data(mSock_data_members* d) {
	if (! d->bValid) return;
	shutdown(d->sock, SD_BOTH);
	closesocket(d->sock);
	d->sock = INVALID_SOCKET;
	d->bValid = false;
};

void mSock_check_err(mSock_data_members* d) {
	int err = LASTERROR;
	if (err != 0 && err != EWOULDBLOCK) mSock_invalidate_data(d);
};

//=========================================================
// Client socket
//=========================================================

mSock::mSock(const char* hostname, unsigned short port) {
	d = new mSock_data_members;
	d->bValid = false;

#ifdef WIN32
	WSADATA wsd;
	if (WSAStartup(MAKEWORD(2,2), &wsd)) return;
#endif

	d->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (d->sock == INVALID_SOCKET) return;

	hostent *host = gethostbyname(hostname);
	if (host == NULL) return;

	SOCKADDR_IN sa;
	sa.sin_port   = htons(port);
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = *((unsigned long*)(host->h_addr));

	if( connect( d->sock, (SOCKADDR*)(&sa), sizeof(sa) ) ) return;

	mSock_unblock_socket(d->sock);

	d->bValid = true;
	doEvents();
}

#ifndef MSOCK_DISABLE_SERVER
mSock::mSock(mSockClient msc) {
	d = new mSock_data_members;

#ifdef WIN32
	WSADATA wsd;
	if (WSAStartup(MAKEWORD(2,2), &wsd)) return;
#endif

	d->bValid = true;
	d->sock = (SOCKET)msc;
	doEvents();
}
#endif /* MSOCK_DISABLE_SERVER */

mSock::~mSock() {
	if (d->bValid) mSock_invalidate_data(d);
#ifdef WIN32
	WSACleanup();
#endif
	delete d;
}

// Public

bool mSock::isValid() {
	return d->bValid;
}

void mSock::doEvents() {
	if (! d->bValid) return;
	char cBuff[MSOCK_MAX_BUFFER_SIZE] = {0};

	int recvVal = recv(d->sock, cBuff, MSOCK_MAX_BUFFER_SIZE, 0);
	if (recvVal == SOCKET_ERROR) {
		mSock_check_err(d);
	} else if (recvVal == 0) {
		mSock_invalidate_data(d);
	} else {
		d->sCache.append(cBuff, recvVal); 
	}
}

string mSock::get() {
	string sResult;
	sResult.assign(d->sCache);
	d->sCache.assign("");
	return sResult;
}

void mSock::put(const char* data, int count) {
	if (! d->bValid) return;
	if (send(d->sock, data, count, 0) == SOCKET_ERROR) {
		mSock_check_err(d);
	}
}

void mSock::put(const char *data) {
	this->put(data, (int)strlen(data) );
}

void mSock::close() {
	mSock_invalidate_data(d);
}

//=========================================================
// Server socket
//=========================================================

#ifndef MSOCK_DISABLE_SERVER

mSockServer::mSockServer(unsigned short port) {
	d = new mSock_data_members;
	d->bValid = false;

#ifdef WIN32
	WSADATA wsd;
	if (WSAStartup(MAKEWORD(2,2), &wsd)) return;
#endif

	d->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (d->sock == INVALID_SOCKET) return;

	SOCKADDR_IN sa;
	sa.sin_port   = htons(port);
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = INADDR_ANY;

	if( bind(d->sock, (SOCKADDR*)(&sa), sizeof(sa)) == SOCKET_ERROR) return;

	mSock_unblock_socket(d->sock);

	listen(d->sock, MSOCK_MAX_QUEUE_CONNS);

	d->bValid = true;
	
}

mSockServer::~mSockServer() {
	if (d->bValid) mSock_invalidate_data(d);
#ifdef WIN32
	WSACleanup();
#endif
	delete d;
}

// Public

bool mSockServer::isValid() {
	return (d->bValid);
}

mSockClient mSockServer::newConn() {					
	SOCKET incoming = SOCKET_ERROR;
	incoming = accept(d->sock, 0, 0);

	if (incoming != SOCKET_ERROR) {
		mSock_unblock_socket(d->sock);
	} else mSock_check_err(d);

	return (mSockClient) incoming;
}

#endif /* MSOCK_DISABLE_SERVER */
