#include "StdAfx.h"
#include "Jnior.h"

#include "utility.h"
//#include "CRC16.h"

#include <time.h>

#define FD_SETSIZE	500

CJnior::CJnior(CONNECT_PARAMS* cp)
{
	m_cp = new CONNECT_PARAMS();
	m_cp->host = new char[strlen(cp->host) + 1];
	strncpy(m_cp->host, cp->host, strlen(cp->host));
	m_cp->host[strlen(cp->host)] = '\0';
	m_cp->port = cp->port;

	m_cp->port = cp->port;

	// set the handle then increment the next handle
	m_handle = NEXT_HANDLE++;
	m_quit = false;

	m_lastInputs = m_inputs = 0;
	m_lastOutputs = m_outputs = 0;

	for (int i = 0; i < 8; i++) {
		m_counters[i] = 0;
		m_inputUsageMeters[i] = 0;
		m_outputUsageMeters[i] = 0;
	}

	m_nextPulse = 0;
	m_lastPulse = 0;
	m_pulseQueue = new char[6 * 32];

	// start the pulse thread
	DWORD dwPulseId;
	HANDLE hThd = CreateThread(
		NULL,										// default security attributes 
		0,											// use default stack size  
		(LPTHREAD_START_ROUTINE) PulseThread,	// thread function 
		this,											// argument to thread function 
		0,										// use default creation flags 
		&dwPulseId);							// returns the thread identifier 
}

CJnior::~CJnior(void)
{
}


//  Added this 02-22-2017 for the 412 and 414
void CJnior::SetInputCount(int inputCount) {
	m_inputCount = inputCount;
}

//  Added this 02-22-2017 for the 412 and 414
int CJnior::GetInputCount() {
	return m_inputCount;
}

int CJnior::GetInput(int channel)
{
	return (m_inputs >> channel) & 0x01;
}

int CJnior::GetInputs()
{
	return m_inputs & 0xffff;
}

int CJnior::SetInputs(int channelMask, int stateMask)
{
	int mask = channelMask ^ 0xffff & 0xff;
	m_inputs = m_inputs & mask;
	m_inputs |= stateMask & channelMask;

	if (m_inputs != m_lastInputs) {
		// figure out which inputs actually chagned and increment the counters
		int changedMask = m_inputs ^ m_lastInputs;
		for (int i = 0; i < 8; i++) {
			if (changedMask & 0x01 != 0) {
				if (m_inputs >> i & 0x01 == 0x01) {
					// the input went high.  increment the counter and capture the time (clock uptime)
					m_counters[i]++;
					m_inputHiTransitionTime[i] = clock();
				} else {
					m_inputUsageMeters[i] += (clock() - m_inputHiTransitionTime[i]);
				}
			}
			changedMask >>= 1;
		}

		// for each connection
		std::vector<CJniorConnection*>::iterator itConnections;
		for(itConnections = connections.begin(); itConnections != connections.end(); itConnections++)
		{
			(*(itConnections))->BuildMonitorPacket();
		}
		SendServerInfo();
		m_lastInputs = m_inputs;
	}

	return STATUS_OK;
}

//  Added this 02-22-2017 for the 412 and 414
void CJnior::SetOutputCount(int outputCount) {
	m_outputCount = outputCount;
}

//  Added this 02-22-2017 for the 412 and 414
int CJnior::GetOutputCount() {
	return m_outputCount;
}

int CJnior::GetOutput(int channel)
{
	return (m_outputs >> channel) & 0x01;
}

int CJnior::GetOutputs()
{
	return m_outputs & 0xffff;
}

int CJnior::SetOutputs(int channelMask, int stateMask)
{
	//char log[128];

//#if DEBUG				
//	sprintf(log, "Setting Outputs Current: %d Change: %d %d", m_outputs, channelMask, stateMask);
//	CJniorConnection::Log(tosstring(m_handle), log);
//#endif

	int mask = channelMask ^ 0xffff;
	m_outputs = m_outputs & mask;
	m_outputs |= stateMask & channelMask;

//#if DEBUG				
//	sprintf(log, "Outputs: %d", m_outputs);
//	CJniorConnection::Log(tosstring(m_handle), log);
//#endif

	if (m_outputs != m_lastOutputs) {
		// figure out which inputs actually chagned and increment the counters
		int changedMask = m_outputs ^ m_lastOutputs;
		for (int i = 0; i < 8; i++) {
			if (changedMask & 0x01 != 0) {
				if (m_outputs >> i & 0x01 == 0x01) {
					// the input went high.  capture the time (clock uptime)
					m_outputHiTransitionTime[i] = clock();
				} else {
					m_outputUsageMeters[i] += (clock() - m_outputHiTransitionTime[i]);
				}
			}
			changedMask >>= 1;
		}

		// for each connection
		std::vector<CJniorConnection*>::iterator itConnections;
		for(itConnections = connections.begin(); itConnections != connections.end(); itConnections++)
		{
			(*(itConnections))->BuildMonitorPacket();
			(*(itConnections))->BuildExtendedMonitorPacket();
		}
		SendServerInfo();
		m_lastOutputs = m_outputs;
	}

	return STATUS_OK;
}

int CJnior::PulseOutputs(int channelMask, int stateMask, int duration) {
	// check to see if the pulse queue is full
	if (((m_lastPulse + 1) % 32) == m_nextPulse) {
		return STATUS_FAILED;
	}

	// load the pulse conditions into the pulse queue
	int pos = m_lastPulse * 6;
	m_pulseQueue[pos++] = stateMask;
	m_pulseQueue[pos++] = channelMask;
	setint(m_pulseQueue, pos, duration);

	// increment the last pulse pointer
	m_lastPulse = (m_lastPulse + 1) % 32;

	return STATUS_OK;
}

/*********************** PULSE THREAD ****************************************/
DWORD WINAPI CJnior::PulseThread( LPVOID lparam )
{
	CJnior* jnior = (CJnior*)lparam;
	char log[128];

	do {
		// wait for a pulse action
		while (jnior->m_nextPulse == jnior->m_lastPulse && !jnior->m_quit) {
			Sleep(10);
		}
			
		// if we quit then exit
		if (jnior->m_quit) {
			return 0;
		}

		int pos = jnior->m_nextPulse * 6;
		// get the states mask
		int statesMask = jnior->m_pulseQueue[pos++];
		// get the channels mask
		int channelMask = jnior->m_pulseQueue[pos++];
		// get the duration
		int duration = getint(jnior->m_pulseQueue, pos);

//#if DEBUG				
//		sprintf(log, "New Pulse Queue: %d %d %d", channelMask, statesMask, duration);
//		CJniorConnection::Log(tosstring(jnior->m_handle), log);
//#endif

		// save the current states
		jnior->m_outputRevertStateMask = jnior->GetOutputs();
		// save this pulses channel mask
		jnior->m_outputRevertChannelMask = channelMask;

		// apply the new states
		jnior->SetOutputs(channelMask, statesMask);

		// sleep for pulse duration
		Sleep(duration);

//#if DEBUG				
//		sprintf(log, "End Pulse Queue: %d %d", jnior->m_outputRevertChannelMask, jnior->m_outputRevertStateMask);
//		CJniorConnection::Log(tosstring(jnior->m_handle), log);
//#endif

		// revert the states
		jnior->SetOutputs(jnior->m_outputRevertChannelMask, jnior->m_outputRevertStateMask);

		// increment the next pulse pointer
		jnior->m_nextPulse = (jnior->m_nextPulse + 1) % 32;
	} while (!jnior->m_quit);

	return 0;
}

int CJnior::GetInputCounter(int channel) {
	return m_counters[channel];
}

long long CJnior::GetInputUsageMeter(int channel) {
	int accruing = 0;
	if (m_inputs >> channel & 0x01 == 0x01) {
		accruing = clock() - m_inputHiTransitionTime[channel];
	}
	return m_inputUsageMeters[channel] + accruing;
}

long long CJnior::GetOutputUsageMeter(int channel) {
	int accruing = 0;
	if (m_outputs >> channel & 0x01 == 0x01) {
		accruing = clock() - m_outputHiTransitionTime[channel];
	}
	return m_outputUsageMeters[channel] + accruing;
}

//// SetUpListener /////////////////////////////////////////////////////
// Sets up a listener on the given interface and port, returning the
// listening socket if successful; if not, returns INVALID_SOCKET.

SOCKET SetUpListener(const char* pcAddress, int nPort)
{
    u_long nInterfaceAddr = inet_addr(pcAddress);
    if (nInterfaceAddr != INADDR_NONE) {
        SOCKET sd = socket(AF_INET, SOCK_STREAM, 0);
        if (sd != INVALID_SOCKET) {
            sockaddr_in sinInterface;
            sinInterface.sin_family = AF_INET;
            sinInterface.sin_addr.s_addr = nInterfaceAddr;
            sinInterface.sin_port = nPort;
            if (bind(sd, (sockaddr*)&sinInterface, 
                    sizeof(sockaddr_in)) != SOCKET_ERROR) {
                listen(sd, SOMAXCONN);
                return sd;
            }
            else {
                cout << WSAGetLastError() << " bind() failed" <<
                        endl;
            }
        }
    }

    return INVALID_SOCKET;
}

void CJnior::SendServerInfo() {
	if (ServerInfoNotify != NULL) {
		SERVER_INFO* si = new SERVER_INFO();
		si->cp = m_cp;
		si->connectedClients = connections.size();
		si->handle = m_handle;
		si->inputs = m_inputs;
		si->outputs = m_outputs;
		ServerInfoNotify(si);
	}
}

int errors = 0;
DWORD WINAPI CJnior::AcceptConnections( LPVOID lparam )
{
	CJnior* jr = (CJnior*)lparam;
	SOCKET ListeningSocket;

	char simFileName[128];
	sprintf(simFileName, "Simulator.%d.log", jr->m_cp->port);

	cout << "Establishing the listener..." << endl;
	ListeningSocket = SetUpListener(jr->m_cp->host, htons(jr->m_cp->port));
	if (ListeningSocket == INVALID_SOCKET) {
		cout << endl << WSAGetLastError() << " establish listener" << 
				endl;
		return 3;
	}

	sockaddr_in sinRemote;
    int nAddrSize = sizeof(sinRemote);


	jr->SendServerInfo();

	while (!jr->m_quit) {
		char log[128];
		sprintf( log, "%s:%d Waiting for connections...", jr->m_cp->host, jr->m_cp->port );
		Logger( simFileName, log );

		SOCKET sd = accept(ListeningSocket, (sockaddr*)&sinRemote,
					&nAddrSize);

        if (sd != INVALID_SOCKET) {
			// create a new jnior connection object
			CJniorConnection* jniorConnection = new CJniorConnection(jr);

			jr->connections.push_back(jniorConnection);

			jr->SendServerInfo();

			char log[128];
			sprintf(log, "Accepted connection from %s on port %d", inet_ntoa(sinRemote.sin_addr), jr->m_cp->port);

			char filename[64];
			sprintf(filename, "Simulator.%s.%d.log", inet_ntoa(sinRemote.sin_addr), jr->m_cp->port);
			jniorConnection->SetDumpFile(filename);

			//char* handle = tosstring(jr->m_handle);
			jniorConnection->Log(log);
			//delete [] handle;

			Logger( simFileName, log );

			CONNECT_PARAMS* newCp = new CONNECT_PARAMS();
			int len = strlen(inet_ntoa(sinRemote.sin_addr));
			newCp->host = new char[len + 1];
			newCp->host[len] = '\0';
			strncpy(newCp->host, inet_ntoa(sinRemote.sin_addr), len);
			newCp->port = ntohs(sinRemote.sin_port);
			//newCp->retryCount = newCp->retriesLeft = 0;
			//newCp->retryInterval = 0;

			jniorConnection->SetSocket(sd);

			//if (AcceptedConnectionCallback != NULL) {
			//	AcceptedConnectionCallback(handle);
			//}
        }
        else {
			errors++;
			int error = WSAGetLastError();
			char s[128];
			sprintf(s, "%d - accept() on port %d failed, total %d\r\n", error, jr->m_cp->port, errors);
            cout << s;
			jr->SendServerInfo();
            return 1;
        }
    }
}

int CJnior::Disconnect() {
	std::vector<CJniorConnection*>::iterator itConnections;
	for(itConnections = connections.begin(); itConnections != connections.end(); itConnections++)
	{
		(*(itConnections))->Disconnect();
	}

	return STATUS_OK;
}

void CJnior::RemoveConnection(CJniorConnection* jniorConnection) {
	std::vector<CJniorConnection*>::iterator itConnections;
	for(itConnections = connections.begin(); itConnections != connections.end(); itConnections++)
	{
		if (*itConnections == jniorConnection) {
			connections.erase(itConnections, itConnections + 1);
			break;
		}
	}

	SendServerInfo();
}