// dllmain.cpp : Defines the entry point for the DLL application.
#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <sstream>
#include <string>
#include <math.h>
using namespace std;

#define MAX_CHARMSG_SIZE	4096


#define WAIT_RESPONSE	while(carmaRemoteFunctionReturnMessage == NULL) { Sleep(0); }

#define MAX_THREADS 1 

void CarmaAttach( void );
void CarmaDetach( void );
DWORD WINAPI CarmaFunctionListener( LPVOID lpParam );

void extTestFunction( void );


void RVEngine_hintSilent(const char* message);
float RVEngine_time( void );
void RVEngine_returnInt(int value);

HANDLE	listenerHandle[MAX_THREADS];
DWORD	threadId[MAX_THREADS];
CRITICAL_SECTION critSec;

const char* carmaExternalFunctionCallMessage;
const char* carmaRemoteFunctionReturnMessage;
char* carmaReturnFunctionCallMessage = new char[MAX_CHARMSG_SIZE];
bool exitListener = false;

void CarmaAttach( void ) {
	if (!InitializeCriticalSectionAndSpinCount(&critSec, 0x00000400) ) 
        return;
	for(int i = 0; i < MAX_THREADS; i++) {
		listenerHandle[i] = CreateThread( 
				NULL,						// default security attributes
				0,							// use default stack size  
				CarmaFunctionListener,      // thread function name
				NULL,						// argument to thread function 
				0,							// use default creation flags 
				&threadId[i]);				// returns the thread identifier
	}
	
}

void CarmaDetach( void ) {
	exitListener = true;
	WaitForMultipleObjects(MAX_THREADS, listenerHandle, TRUE, INFINITE);

	for(int i = 0; i < MAX_THREADS; i++)
	{
		CloseHandle(listenerHandle[i]);
	}
	DeleteCriticalSection(&critSec);
}

DWORD WINAPI CarmaFunctionListener( LPVOID lpParam ) {
	while(!exitListener) {
		while(carmaExternalFunctionCallMessage == NULL) {
			Sleep(1);
		}
		if(!strcmp(carmaExternalFunctionCallMessage, "TestFunction")) {
			extTestFunction();
		}
		carmaExternalFunctionCallMessage = NULL;
	}
	return 0;
}

void extTestFunction( void ) {
	float time;
	ostringstream messageStream;
	string message;
	time = RVEngine_time();
	messageStream << "Called from C++, the RVEngine time is: " << time;
	message = messageStream.str();
	RVEngine_hintSilent(message.c_str());
	RVEngine_returnInt(914);
}

float RVEngine_time( void ) {
	float returnTime;
	carmaRemoteFunctionReturnMessage = NULL;
	EnterCriticalSection(&critSec);
	carmaReturnFunctionCallMessage = "ctime;";
	LeaveCriticalSection(&critSec);
	WAIT_RESPONSE;
	EnterCriticalSection(&critSec);
	returnTime = atof(carmaRemoteFunctionReturnMessage);
	LeaveCriticalSection(&critSec);
	carmaRemoteFunctionReturnMessage = NULL;
	return returnTime;
}

void RVEngine_returnInt(int value) {
	ostringstream messageStream;
	messageStream << "f" << value;
	EnterCriticalSection(&critSec);
	char* tempMsg = new char[MAX_CHARMSG_SIZE];
	strncpy(tempMsg, messageStream.str().c_str(), MAX_CHARMSG_SIZE-1);
	carmaReturnFunctionCallMessage = tempMsg;
	LeaveCriticalSection(&critSec);
}

void RVEngine_hintSilent(const char* message) {
	ostringstream messageStream;
	size_t strLen = 0;
	EnterCriticalSection(&critSec);
	messageStream << "chintSilent \"" << message << "\"";
	strLen = messageStream.str().length();
	char* tempMsg = new char[MAX_CHARMSG_SIZE];
	strncpy(tempMsg, messageStream.str().c_str(), MAX_CHARMSG_SIZE-1);
	carmaReturnFunctionCallMessage = tempMsg;
	LeaveCriticalSection(&critSec);
	WAIT_RESPONSE;
	carmaRemoteFunctionReturnMessage = NULL;
}


extern "C" 
{
	__declspec(dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function); 
};

void __stdcall RVExtension(char *output, int outputSize, const char *function)
{
	bool doEx = false;
	carmaReturnFunctionCallMessage = NULL;
	if(function[0] == 'e') {
		EnterCriticalSection(&critSec);
		carmaExternalFunctionCallMessage = function+1;
		doEx = true;
		LeaveCriticalSection(&critSec);
	} else if (function[0] == 'r') {
		EnterCriticalSection(&critSec);
		if(function[1] != NULL) {
			carmaRemoteFunctionReturnMessage = function+1;
		} else {
			carmaRemoteFunctionReturnMessage = " ";
		}
		doEx = true;
		LeaveCriticalSection(&critSec);
	} else if(function[0] == 'c' || function[0] == 'f') {
		if(function[1] != NULL) {
			strncpy(output,function+1,outputSize-1);
		} else {
			strncpy(output,"",outputSize-1);
		}
		return;
	}
	
	if(doEx) {
		while(carmaReturnFunctionCallMessage == NULL) {
			Sleep(1);
		}
		strncpy(output,carmaReturnFunctionCallMessage,outputSize-1);
		carmaReturnFunctionCallMessage = NULL;
	} else {
		strncpy(output,"err",outputSize);
	}
}

BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
		CarmaAttach();
		break;
    case DLL_THREAD_ATTACH:
		break;
    case DLL_THREAD_DETACH:
		break;
    case DLL_PROCESS_DETACH:
		CarmaDetach();
        break;
    }
    return TRUE;
}