#ifndef ___SOCKET_CLIENT_H_INCLUDED___
#define ___SOCKET_CLIENT_H_INCLUDED___

#include <winsock2.h>
#include "mem_manager.h"

#define RECV_BUFFER_EMPTY	-2

enum IO_SOCK_MODES {_CLOSE, _ACCEPT, _READ, _WRITE, _PENDING};

///////////////////////////////////////////////////////////////////////////////////////////////
//---------------------------- CLASS ----------------------------------------------------------
// Overlapped structure used with this implementation of the IOCP.
// Only the "Overlapped" member is used in context with the
// standard IOCP API, anything else is just to help this particular
// implementation.
typedef struct {
	// It is very important "Overlapped" to be the very first 
	// member of the structure because pointer pointing to 
	// "this" is == to the address of the first member. 
	// This makes usage of the MYOVERLAPPED equivalent 
	// to the OVERLAPPED in context of the standard IOCP API (just a trick).
	OVERLAPPED				Overlapped;

	// A structure that points to the data packet and size of the data 
	// packet associated with current overlapped operation.
	WSABUF					DataBuf;

	// Operation type associated with current overlapped operation.
	IO_SOCK_MODES			OperationType;

	// Internally used. If nSession != ClientSocket::m_nSession
	// that means that socket was closed (recently) and
	// operation retrieved from IOCP queue or TaskPool queue 
	// is not relevant, to the current session of the socket.
	volatile unsigned int	nSession;

	// Method is used by 'QueuedBlocks' from
	// "mem_manager.h".
	void Clear() {
		memset(this, 0, sizeof(MYOVERLAPPED));
	};
} MYOVERLAPPED;

// a pool of the MYOVERLAPPED structures
typedef StaticBlocks<MYOVERLAPPED> Overlapped;
QueuedBlocks<MYOVERLAPPED> *Overlapped::blocks = NULL;

///////////////////////////////////////////////////////////////////////////////////////////////
//---------------------------- CLASS ----------------------------------------------------------
// A class wrapping basic client socket's operations
template<class T>
class ClientSocket {
	//friend class MyServerService;
private:
	SOCKET					m_ClientSock;
	volatile unsigned int	m_nSession;
	QMutex					m_qMutex;
	struct sockaddr_in		m_psForeignAddIn;
	volatile bool			m_blnIsBusy;
	T						m_objAttachment;

protected:
public:
	ClientSocket(): m_objAttachment(), m_qMutex() {
		this->m_nSession = 1;
		this->m_ClientSock = 0;
		this->m_blnIsBusy = false;
	};

	~ClientSocket() { 
		if (this->m_blnIsBusy) this->CloseSocket(); 
	};

	// Is the object assigned a socket.
	bool IsBusy() { return this->m_blnIsBusy; };

	// Method is used by 'QueuedBlocks' from
	// "mem_manager.h".
	void Clear() { CloseSocket(); };
	
	// Returns the socket associated with the object.
	SOCKET GetSocket() { return this->m_ClientSock; };
	void GetAddrIn(struct sockaddr_in& addrin)	{memcpy(&addrin, &m_psForeignAddIn,sizeof(struct sockaddr_in));}
	// Returns the session of the current client socket.
	// Technically, this is the counter of how may times
	// current instance was pooled, but really, this is used
	// to check if session of operation (MYOVERLAPPED) 
	// is == with the session of the socket.
	unsigned int GetSession() { return this->m_nSession; };

	// Internally used by this implementation. Don't call it.
	void Lock() { m_qMutex.Lock(); };

	// Internally used by this implementation. Don't call it.
	void UnLock() { m_qMutex.Unlock(); };

	// Associate the object with a socket.
	int Associate(SOCKET sSocket, struct sockaddr_in *psForeignAddIn) {
		int nRet = SOCKET_ERROR;

		if (sSocket < 0) return nRet;
		if (this->m_blnIsBusy) return nRet;

		this->m_blnIsBusy = true;
		this->m_ClientSock = sSocket;
		memcpy(&m_psForeignAddIn, psForeignAddIn, sizeof(struct sockaddr_in));
		m_objAttachment.Clear();

		return (int) this->m_ClientSock;
	};

	// Returns the attachment of the object.
	// Use the attachment to associate the socket with any other
	// additional information required.
	T* GetAttachment() { return &m_objAttachment; };

	// Write data to the socket,
	// uses overlapped style.
	int WriteToSocket(char *pBuffer, DWORD buffSize) {
		int nRet = SOCKET_ERROR ;
		DWORD dwSendBytes = 0;
		DWORD dwFlags = 0;

		if ( this->m_blnIsBusy ) {
			// Get a free instance of the structure
			// from the pool.
			MYOVERLAPPED *mov = Overlapped::Get();

			if ( mov != NULL ) {
				mov->DataBuf.buf = pBuffer;
				mov->DataBuf.len = buffSize;
				mov->OperationType = _WRITE;
				mov->nSession = GetSession();

				nRet = WSASend(this->m_ClientSock, &mov->DataBuf, 1, 
					&dwSendBytes, dwFlags, (OVERLAPPED *) mov, NULL);

				if (nRet == SOCKET_ERROR) {
					if ((WSAGetLastError()) == WSA_IO_PENDING) nRet = 0;
				}
			}
		}
		return nRet;
	};

	// Read data from the socket,
	// uses overlapped style.
	int ReadFromSocket(char *pBuffer, DWORD buffSize) {
		int nRet = SOCKET_ERROR ;
		DWORD dwRecvBytes;
		DWORD dwFlags = 0;

		if ( this->m_blnIsBusy ) {
			if (buffSize > 0) {
				// Get a free instance of the structure
				// from the pool.
				MYOVERLAPPED *mov = Overlapped::Get();

				if ( mov != NULL ) {
					mov->DataBuf.buf = pBuffer;
					mov->DataBuf.len = buffSize;
					mov->OperationType = _READ;
					mov->nSession = GetSession();

					nRet = WSARecv(this->m_ClientSock, &mov->DataBuf, 1, 
						&dwRecvBytes, &dwFlags, (OVERLAPPED *) mov, NULL);

					if (nRet == SOCKET_ERROR) {
						if ((WSAGetLastError()) == WSA_IO_PENDING) nRet = 0;
					}
				}
			}
			else nRet = RECV_BUFFER_EMPTY;
		}
		return nRet;
	};

	// Closes the socket associated with this instance 
	// of the object and performs clean-up.
	void CloseSocket() {
		if ( this->m_blnIsBusy ) {
			this->m_blnIsBusy = false;
			if ((this->m_nSession++) > 0x0FFFD) this->m_nSession = 1;

			shutdown(this->m_ClientSock, 2);
			closesocket(this->m_ClientSock);

			this->m_ClientSock = 0;
			memset(&m_psForeignAddIn, 0 , sizeof(struct sockaddr_in));
			m_objAttachment.Clear();
		}
	};
};

#endif