#pragma once

#include "Monitor.h"

#include <iostream>
using namespace std;


class CJnior;


struct HEADER {
	short OFFSET;
	char SYNC_BYTE;
	short LENGTH_HI;
	short LENGTH_LO;
	short CRC_HI;
	short CRC_LO;
	char PAYLOAD[0xffff];
	int PAYLOAD_LENGTH;
};

enum {
	WAIT_SYNC_BYTE,
	WAIT_LENGTH_HI,
	WAIT_LENGTH_LO,
	WAIT_CRC_HI,
	WAIT_CRC_LO,
	WAIT_PAYLOAD,
	PAYLOAD_READY
};


class CJniorConnection
{
public:
	CJniorConnection(CJnior* jnior);
	~CJniorConnection(void);

	int SetSocket(SOCKET sckt);
	int Disconnect();

	int Send(char* bytes, int size);

	int BuildLoginPacket(int level);
	int BuildMonitorPacket();
	int BuildExtendedMonitorPacket();

	int SetDumpFile(char* filename);
	//static void Log(CJniorConnection* jnior, char* filename, char* text);
	void Log(char* text);
	void Log(char* filename, char* text);

	bool m_quit;

private:
	CMonitor m_mutex;

	CJnior* m_jnior;

	SOCKET m_sckt;

	void RemoveConnection();

	// this is the buffer that is used to receive the packets.  0xffff + 5 is 
	// the largets packet possible since the length feild can be 0xffff and 
	// the header is 5 bytes long
	char m_recvbuf[0xffff + 5];
	int m_nextReadPos;
	int m_pos;
	HEADER m_recvHeader;
	int m_recvState;

	void PacketReceived(int messageType, HEADER recvHeader);
	void DataReady();

	int ProcessUsageMeterPacket();

	void hexdump(void *pAddressIn, int offset, int size, char direction);
	char* m_dumpFileName;




	class ReceiverThread;
	ReceiverThread* m_thd;

	class ReceiverThread
	{
	private:
		CJniorConnection* m_jniorConnection;
		HANDLE m_hThread;
		DWORD m_dwThreadId;

	public:
		bool m_quit;

	public:
		ReceiverThread(CJniorConnection* jniorConnection) {
			m_jniorConnection = jniorConnection;

			m_quit = false;

			//if we havent created a thread to handle incoming bytes, do so now
			m_hThread = CreateThread(
				NULL,										// default security attributes 
				0,											// use default stack size  
				(LPTHREAD_START_ROUTINE) this->Run,	// thread function 
				this,										// argument to thread function 
				0,											// use default creation flags 
				&m_dwThreadId);								// returns the thread identifier 
		}

		static DWORD WINAPI Run ( LPVOID lparam )
		{
			// This is the owned of the thread
			ReceiverThread* parent = (ReceiverThread*) lparam;
			bool closed = false;

			//parent->m_jnior;

			// we want to be notified when either a socket is ready for reading or it 
			// has closed.  We use WSAEventSelect and WSAWaitForMultipleEvents to 
			// accompish this

			// loop until quit is true
			while (!parent->m_quit && !closed) {

				// A successful delivery of data will return a zero so we start 
				// with a non zero value.
				WSASetLastError (0);
				int bytesRecv = SOCKET_ERROR;
				while(!parent->m_quit && bytesRecv == SOCKET_ERROR)  // waiting for communications
				{
					//cout << "WAIT FOR SOCKET EVENT" << endl;
					WSAEVENT readOrCloseEvent = WSA_INVALID_EVENT;
					readOrCloseEvent = WSACreateEvent();
					WSAEventSelect(parent->m_jniorConnection->m_sckt, readOrCloseEvent, FD_READ | FD_CLOSE);

					WSAEVENT EventArray[1];
					EventArray[0] = readOrCloseEvent;

					// wait for some event to occur
					int waitResult = WSAWaitForMultipleEvents(1, &readOrCloseEvent, FALSE, 30000, FALSE);
					//cout << "WSAWaitForMultipleEvents: " << waitResult << endl;

					if (parent->m_quit) break;

					if (waitResult == WSA_WAIT_TIMEOUT)
					{
						WSANETWORKEVENTS* NetworkEvents = new WSANETWORKEVENTS;
						
						int enumResult = WSAEnumNetworkEvents(parent->m_jniorConnection->m_sckt, EventArray[0], NetworkEvents);
						//cout << "WSAEnumNetworkEvents: " << enumResult << endl;

						if (enumResult == SOCKET_ERROR) {
							break;
						}

						//sendHeartbeat(parent->m_jnior);
						continue;
					} 

					else if ((waitResult != WSA_WAIT_FAILED) && (waitResult != WSA_WAIT_TIMEOUT)) {
						WSANETWORKEVENTS* NetworkEvents = new WSANETWORKEVENTS;
						
						int enumResult = WSAEnumNetworkEvents(parent->m_jniorConnection->m_sckt, EventArray[0], NetworkEvents);
						//cout << "WSAEnumNetworkEvents: " << enumResult << endl;

						if (enumResult == SOCKET_ERROR) {
							break;
						} else if (enumResult == 0) {
							if ((NetworkEvents->lNetworkEvents & FD_READ) == FD_READ) {
								printf("Read Event\n");
							}

							if ((NetworkEvents->lNetworkEvents & FD_CLOSE) == FD_CLOSE) {
								parent->m_jniorConnection->Log("Close Event\n");

								// close the socket
								closesocket(parent->m_jniorConnection->m_sckt);
								closed = true;
								parent->m_quit = true;
								break;
							}
						} else {
						//cout << "WSAEnumNetworkEvents Error: " << enumResult << " " << WSAGetLastError() << endl;
						}
					}

					else if (waitResult != 0) {
						//cout << "WSAEnumNetworkEvents Error: " << waitResult << " " << WSAGetLastError() << endl;
					}

					// reset the event
					WSAResetEvent(&readOrCloseEvent);

					// initialize a socket set.
					fd_set fds;
					FD_ZERO(&fds);
					FD_SET(parent->m_jniorConnection->m_sckt, &fds);

					timeval timeout;
					timeout.tv_sec = 0;
					timeout.tv_usec = 100;

					timeout.tv_sec = 3; //0;
					timeout.tv_usec = 0;

					int isReadable = select(0, &fds, NULL, NULL, &timeout);
					if (isReadable != 1) {
						cout << "read select: Error: " << isReadable << endl;
					}

					if (isReadable == SOCKET_ERROR ) {
						if (parent->m_quit) {
							break;
						}

						//parent->m_jnior->Error(CJnior::SOCKET_NOT_READABLE);

						// close the socket
						parent->m_jniorConnection->Log("Socket Error\n");
						closesocket(parent->m_jniorConnection->m_sckt);
						closed = true;
						parent->m_quit = true;
						break;
					}
					
					// data is ready to be read from the socket
					else if (isReadable > 0) {
						parent->m_jniorConnection->DataReady();


						//// now we can check to see if we need to send a keep alive to the 
						//// JNIOR.  The JNIOR will time out the connection after 15 
						//// minutes of inactivity.  We will send atleast once every 10 
						//// minutes.
						//		
						//// To be alerted that the SOCKET connection has died we must try 
						//// to send to the SOCKET.  The keep alive helps with that as well.  
						//// If we are receiving data within the past 30 or so seconds the 
						//// SOCKET must be ok.  The 30 second wait is deterined by the 
						//// timeout on the select call for the reader set.
						//sendHeartbeat(parent->m_jnior);
					}

				}

			}

			//parent->m_jnior->Status(STATUS_RECV_THREAD_DONE);

			parent->m_hThread = NULL;


			//parent->m_jniorConnection->m_jnior->
			parent->m_jniorConnection->RemoveConnection();
			//delete parent->m_jniorConnection;

			return 0;
		}

	};

};