/********************************************************************************
* FreeTrackClientDll Implements the FreeTrack 2.0 interface for FT-enabled		*
*					games.														*
*					It uses the FreeTrack protocol (memory mapping) to			*
*					receive data from FaceTrackNoIR (or FreeTrack, or ...).		*
*																				*
* Copyright (C) 2013	Wim Vriend (Developing)									*
*						Ron Hendriks (Testing and Research)						*
*																				*
* Homepage				<http://facetracknoir.sourceforge.net/home/default.htm>	*
*																				*
* This program is free software; you can redistribute it and/or modify it		*
* under the terms of the GNU General Public License as published by the			*
* Free Software Foundation; either version 3 of the License, or (at your		*
* option) any later version.													*
*																				*
* This program is distributed in the hope that it will be useful, but			*
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY	*
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for	*
* more details.																	*
*																				*
* You should have received a copy of the GNU General Public License along		*
* with this program; if not, see <http://www.gnu.org/licenses/>.				*
*																				*
********************************************************************************/
/*
	Modifications (last one on top):

	20130208 - WVR: The old DLL from FreeTrack seems to crash ArmA2. And we need 64-bit support.
	
*/

/*
 * COMPILE WITH:
 * % i686-w64-mingw32-gcc -shared -o FreeTrackClient.dll -O2 FreeTrackClient.c
 * DO NOT USE MSVC! IT INTRODUCES FAULTY MANIFEST!!!
 * -- sthalik
 */

#ifdef WIN64
#pragma comment(linker, "/export:FTGetData")
#pragma comment(linker, "/export:FTReportName")
#pragma comment(linker, "/export:FTGetDllVersion")
#pragma comment(linker, "/export:FTProvider")
#endif

#define FT_DECLSPEC __declspec(dllexport)

#define FT_EXPORT(t) FT_DECLSPEC t __stdcall
#define	NP_AXIS_MAX				16383

#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <tchar.h>

typedef struct TFreeTrackData {
	int DataID;
	int CamWidth;
    int CamHeight;
    // virtual pose
    float Yaw;   // positive yaw to the left
    float Pitch; // positive pitch up
    float Roll;  // positive roll to the left
    float X;
    float Y;
    float Z;
    // raw pose with no smoothing, sensitivity, response curve etc. 
    float RawYaw;
    float RawPitch;
    float RawRoll;
    float RawX;
    float RawY;
    float RawZ;
    // raw points, sorted by Y, origin top left corner
    float X1;
    float Y1;
    float X2;
    float Y2;
    float X3;
    float Y3;
    float X4;
    float Y4;
} TFreeTrackData;
typedef TFreeTrackData * PFreetrackData;

//
// Functions to create/open the file-mapping
// and to destroy it again.
//
float scale2AnalogLimits( float x, float min_x, float max_x );
float getDegreesFromRads ( float rads );
FT_EXPORT(BOOL) FTCreateMapping(void);

#if 0
static FILE *debug_stream;
#define dbg_report(...) if (debug_stream) { fprintf(debug_stream, __VA_ARGS__); fflush(debug_stream); }
#else
#define dbg_report(...)
#endif

//
// Handles to 'handle' the memory mapping
//
#define FT_MM_DATA "FT_SharedMem"
#define FREETRACK_MUTEX "FT_Mutext"

static HANDLE hFTMemMap = 0;
static TFreeTrackData *pMemData = 0;
static HANDLE hFTMutex = 0;
static const char* dllVersion = "1.0.0.0";
static const char* dllProvider = "FreeTrack";

static unsigned short gameid = 0;

//
// DllMain gets called, when the DLL is (un)loaded or a process attaches.
//
FT_EXPORT(BOOL) WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason) {
		case DLL_PROCESS_ATTACH:
#ifdef WIN64
		    dbg_report("\n= WIN64 =========================================================================================\n");
#else
		    dbg_report("\n= WIN32 =========================================================================================\n");
#endif
			dbg_report("DllMain: (0x%p, %d, %p)\n", hinstDLL, fdwReason, lpvReserved);
            dbg_report("DllMain: Attach request\n");
//			debug_stream = fopen("c:\\FreeTrackClient.log", "a");
            DisableThreadLibraryCalls(hinstDLL);
            break;

		case DLL_PROCESS_DETACH:
			dbg_report("DllMain: Detach\n");
			dbg_report("DllMain: (0x%p, %d, %p)\n", hinstDLL, fdwReason, lpvReserved);
		    dbg_report("==========================================================================================\n");
            break;
    }
    return TRUE;
}

/******************************************************************
 *		FTGetData (FreeTrackClient.1)
 */
#pragma comment(linker, "/export:FTGetData@4=FTGetData")
FT_EXPORT(BOOL) FTGetData(TFreeTrackData* data)
{
  static int frame = 0;
  static int prevDataID = 0;
  static int dlyTrackingOff = 0;
  static int tracking = 0;

//  dbg_report("NP_GetData called.");
  if (FTCreateMapping() == FALSE) return FALSE;

  if (hFTMutex && WaitForSingleObject(hFTMutex, 5) == WAIT_OBJECT_0) {
	if (pMemData) {

		//
		// When FaceTrackNoIR does not update frames (any more), don't update the data.
		//
		if (prevDataID != pMemData->DataID) {
			*data = *pMemData;
			dlyTrackingOff = 0;
		}
		else {
			dlyTrackingOff++;
			if (dlyTrackingOff > 20) {
				dlyTrackingOff = 100;
				tracking = FALSE;
			}
		}
		prevDataID = pMemData->DataID;
		
		//
		// Limit the range of DataID
		//
		if (pMemData->DataID > 1000) {
			pMemData->DataID = 0;
		}
		data->DataID = pMemData->DataID;

		//
		// Send the ID to FaceTrackNoIR, so it can display the game-name.
		// This could be a FreeTrack-specific ID
		//
		//sprintf(pMemData->GameID, "%d", gameid );

	}
	ReleaseMutex(hFTMutex);
  }
  return TRUE;
}

/******************************************************************
 *		FTReportName (FreeTrackClient.2)
 */
#pragma comment(linker, "/export:FTReportName@4=FTReportName")
//
// For some mysterious reason, the previously existing function FTReportID has been changed to FTReportName, but with an integer as argument.
// The Delphi-code from the FreeTrack repo suggest a char * as argument, so it cost me an afternoon to figure it out (and keep ArmA2 from crashing).
// Thanks guys!
//
FT_EXPORT(void) FTReportName( int name )
{
	dbg_report("FTReportName request (ID = %d).\n", name);
	gameid = name;												// They might have really passed the name here... but they didn't!
	return;
}

/******************************************************************
 *		FTGetDllVersion (FreeTrackClient.3)
 */
#pragma comment(linker, "/export:FTGetDllVersion@0=FTGetDllVersion")
FT_EXPORT(const char*) FTGetDllVersion(void)
{
    dbg_report("FTGetDllVersion request.\n");

	return dllVersion;
}

/******************************************************************
 *		FTProvider (FreeTrackClient.4)
 */
#pragma comment(linker, "/export:FTProvider@0=FTProvider")
FT_EXPORT(const char*) FTProvider(void)
{
    dbg_report("FTProvider request.\n");

	return dllProvider;
}

//
// Create a memory-mapping to the Freetrack data.
// It contains the tracking data, a handle to the main-window and the program-name of the Game!
//
//
#pragma comment(linker, "/export:FTCreateMapping@0=FTCreateMapping")
FT_EXPORT(BOOL) FTCreateMapping(void)
{
	BOOL bMappingExists = FALSE;
	PDWORD_PTR MsgResult = 0;

	//
	// Memory-mapping already exists!
	//
	if ( pMemData != NULL ) {
		return TRUE;
	}

    dbg_report("FTCreateMapping request (pMemData == NULL).\n");

	//
	// A FileMapping is used to create 'shared memory' between the FTClient and the FTServer.
	//
	// Try to create a FileMapping to the Shared Memory. This is done to check if it's already there (what
	// may mean the face-tracker program is already running).
	//
	// If one already exists: close it and open the file-mapping to the existing one.
	//
	hFTMemMap = CreateFileMappingA( INVALID_HANDLE_VALUE , 00 , PAGE_READWRITE , 0 , 
		                           sizeof( TFreeTrackData ), 
								   (LPCSTR) FT_MM_DATA );

	if ( ( hFTMemMap != 0 ) && ( GetLastError() == ERROR_ALREADY_EXISTS ) ) {
		dbg_report("FTCreateMapping: Mapping already exists.\n");
		bMappingExists = TRUE;				// So the server was (probably) already started!
		CloseHandle( hFTMemMap );
		hFTMemMap = 0;
	}

	//
	// Create a new FileMapping, Read/Write access
	//
	hFTMemMap = OpenFileMappingA( FILE_MAP_ALL_ACCESS , FALSE , (LPCSTR) FT_MM_DATA );
	if ( ( hFTMemMap != 0 ) ) {
		dbg_report("FTCreateMapping: Mapping opened.\n");
		pMemData = (TFreeTrackData *) MapViewOfFile(hFTMemMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof( TFreeTrackData ) );
	    hFTMutex = CreateMutexA(NULL, FALSE, FREETRACK_MUTEX);
	}
	else {
		return FALSE;
	}
	return TRUE;
}

//
// Destory the FileMapping to the shared memory
//
#pragma comment(linker, "/export:FTDestroyMapping@0=FTDestroyMapping")
FT_EXPORT(void) FTDestroyMapping(void)
{
	if ( pMemData != NULL ) {
		UnmapViewOfFile ( pMemData );
	}
	
	CloseHandle( hFTMutex );
	CloseHandle( hFTMemMap );
	pMemData = 0;
	hFTMemMap = 0;
}

//
// 4 convenience
//
float getDegreesFromRads ( float rads ) { 
	return (rads * 57.295781f); 
}

//
// Scale the measured value to the TIR values
//
float scale2AnalogLimits( float x, float min_x, float max_x ) {
double y;
double local_x;
	
	local_x = x;
	if (local_x > max_x) {
		local_x = max_x;
	}
	if (local_x < min_x) {
		local_x = min_x;
	}
	y = ( NP_AXIS_MAX * local_x ) / max_x;

	return (float) y;
}