/* 
 * File:   FsLink.cpp
 * Author: jens
 * 
 * Created on 9. Januar 2015, 20:14
 */

#include "FsLink.hpp"
#include "memdump.h"
#define	TWO_PWR_16	(65536.0)
#define	TWO_PWR_32	(TWO_PWR_16*TWO_PWR_16)
#ifdef FS_DEBUG
#define TIMER_INTEVAL_MS	1000
#else
#define TIMER_INTEVAL_MS	100
#endif
const char* FsLink::linkStateString[] = {"Init", "Disconnected", "Connected", "Paused", "Recording"};
const char* FsLink::flightStateString[] = {"Init", "Suspend", "Ground", "Take off", "Enroute", "Touch down"};

typedef struct _DumpStatistics
{
	bool isDynamic;
	
} DumpStatistics;

static DumpStatistics dumpStatistics[0x7800];
static uint8_t dumpStatisticsDataInitial[0x7800];
static uint8_t dumpStatisticsDataCurr[0x7800];

FsLink::FsLink(const char *pDocRoot)
: m_docRoot(pDocRoot)
, m_pKml(nullptr)
, m_linkState(ls_init)
, m_linkStateNext(ls_disconnected)
, m_flightState(fs_init)
, m_flightStateNext(fs_suspend)
, m_kmlUpdateInterval(10)
, m_kmlUpdateCount(0)
, m_dumpOffset(0)
, m_dumpLearnCount(0)
{
	startTimer(TIMER_INTEVAL_MS);
}

FsLink::FsLink(const FsLink& orig)
{
	(void)orig;
}

FsLink::~FsLink()
{
}

void FsLink::setDumpOffset(uint32_t offset)
{
	m_dumpOffset = offset;
}

void FsLink::dump()
{
	uint8_t dump[1024];
    DWORD result;

	printf("---------------------------------------------------\n");
	FSUIPC_Read(m_dumpOffset, sizeof(dump), dump, &result);
	if (result == FSUIPC_ERR_OK)
		FSUIPC_Process(&result);

	if (result == FSUIPC_ERR_OK)
	{
		memdump(dump, m_dumpOffset, 16, sizeof(dump));
	}
}

void FsLink::statisticsLearnStart(uint32_t count)
{
    DWORD result;

	m_dumpLearnCount = count;
	memset(dumpStatistics, 0, sizeof(dumpStatistics));
	FSUIPC_Read(0, sizeof(dumpStatisticsDataInitial), dumpStatisticsDataInitial, &result);
	if (result == FSUIPC_ERR_OK)
		FSUIPC_Process(&result);
	else
		printf("%s: Problem FSUIPC_Read\n", __func__);

	printf("Learning(%d) ", m_dumpLearnCount);	
}

void FsLink::statisticsLearn()
{
    DWORD result;
	if (m_dumpLearnCount)
	{
		FSUIPC_Read(0, sizeof(dumpStatisticsDataCurr), dumpStatisticsDataCurr, &result);
		if (result == FSUIPC_ERR_OK)
			FSUIPC_Process(&result);
		else
			printf("%s: Problem FSUIPC_Read\n", __func__);
		
		for (uint32_t i=0; i < sizeof(dumpStatisticsDataInitial); i++)
		{
			if (dumpStatisticsDataInitial[i] != dumpStatisticsDataCurr[i])
			{
				dumpStatistics[i].isDynamic = true;
			}
		}
		m_dumpLearnCount--;
		if (m_dumpLearnCount)
		{
			printf(".");
		}
		else
		{
			printf("done\n");
		}
	}
}

void FsLink::statisticsEval()
{
	printf("---------------------------------------------------\n");
	printf("Dump eval\n");
	printf("---------------------------------------------------\n");

    DWORD result;
	FSUIPC_Read(0, sizeof(dumpStatisticsDataCurr), dumpStatisticsDataCurr, &result);
	if (result == FSUIPC_ERR_OK)
		FSUIPC_Process(&result);
	else
		printf("%s: Problem FSUIPC_Read\n", __func__);
	
	for (uint32_t i=0; i < sizeof(dumpStatisticsDataCurr); i++)
	{
		if (dumpStatisticsDataInitial[i] != dumpStatisticsDataCurr[i])
		{
			if (!dumpStatistics[i].isDynamic)
			{
				printf("%08X : Initial=%02X, Current=%02X\n", i, dumpStatisticsDataInitial[i], dumpStatisticsDataCurr[i]);
			}
		}
	}
}

void FsLink::addListener(IFsLinkListener *pListener)
{
	m_listeners.add(pListener);
}

void FsLink::setKml(IKml *pKml)
{
	m_pKml = pKml;
}

void FsLink::setUpdateInterval(double seconds)
{
	m_kmlUpdateInterval = (int)(1000*seconds/TIMER_INTEVAL_MS + 0.5);
}

bool FsLink::open()
{
	unsigned long result;
	static char chOurKey[] = "CJM4KBNN1RLQ"; // As obtained from Pete Dowson
	
	memset(m_idStr, 0, sizeof(m_idStr));

	if(FSUIPC_Open(SIM_ANY, &result))
	{
		// Okay, we're linked, and already the FSUIPC_Open has had an initial
		// exchange with FSUIPC to get its version number and to differentiate
		// between FS's.

		// Now to auto-Register with FSUIPC, to save the user of an Unregistered FSUIPC
		// having to Register UIPCHello for us:

		if (FSUIPC_Write(0x8001, 12, chOurKey, &result))
			FSUIPC_Process(&result); // Process the request(s)

		// I've not checked the reslut of the above -- if it didn't register us,
		// and FSUIPC isn't fully user-Registered, the next request will not
		// return the FS lock time

		FSUIPC_Read(0x24, sizeof(m_idStr), m_idStr, &result);
		FSUIPC_Process(&result);
		return true;
	}

	return false;
}

void FsLink::close()
{
	FSUIPC_Close(); // Closing when it wasn't open is okay, so this is safe here
}

int FsLink::gatherData()
{
    DWORD result;
    unsigned lat_lo, lon_lo, alt_lo;
    char lat[8], lon[8], alt[8], FSRdy2Fly, FSDialog;
    int lat_hi, lon_hi, alt_hi, radio_alt, bank, pitch, hdg_true, gs, tas, ias, bpa, vs, groundLevel;
    short mag_var, parking_brake, paused, framerate, plane_on_ground;
	short replay_active;
	
    FSUIPC_Read(0x600C, sizeof(m_flightData.timeZulu), &m_flightData.timeZulu, &result);
    FSUIPC_Read(0x20, sizeof(groundLevel), &groundLevel, &result);
    FSUIPC_Read(0x560, sizeof(lat), lat, &result);
    FSUIPC_Read(0x568, sizeof(lon), lon, &result);
    FSUIPC_Read(0x31E4, sizeof(radio_alt), &radio_alt, &result);
    FSUIPC_Read(0x570, sizeof(alt), alt, &result);
    FSUIPC_Read(0x578, sizeof(pitch), &pitch, &result);
    FSUIPC_Read(0x57C, sizeof(bank), &bank, &result);
    FSUIPC_Read(0x580, sizeof(hdg_true), &hdg_true, &result);
    FSUIPC_Read(0x264, sizeof(paused), &paused, &result);
    FSUIPC_Read(0x274, sizeof(framerate), &framerate, &result);
    FSUIPC_Read(0x2A0, sizeof(mag_var), &mag_var, &result);
    FSUIPC_Read(0x2B4, sizeof(gs), &gs, &result);
    FSUIPC_Read(0x2B8, sizeof(tas), &tas, &result);
    FSUIPC_Read(0x2BC, sizeof(ias), &ias, &result);
    FSUIPC_Read(0x2C4, sizeof(bpa), &bpa, &result);
    FSUIPC_Read(0x2C8, sizeof(vs), &vs, &result);
    FSUIPC_Read(0x3364, sizeof(FSRdy2Fly), &FSRdy2Fly, &result);
    FSUIPC_Read(0x3365, sizeof(FSDialog), &FSDialog, &result);
    FSUIPC_Read(0x3D00, sizeof(m_flightData.aircraftTitle), m_flightData.aircraftTitle, &result);
    FSUIPC_Read(0x3E00, sizeof(m_fsInstallPath), m_fsInstallPath, &result);
    FSUIPC_Read(0x1F96, sizeof(m_flightData.tcasStr), m_flightData.tcasStr, &result);
    FSUIPC_Read(0xF096, sizeof(m_flightData.tcasStr2), m_flightData.tcasStr2, &result);
    FSUIPC_Read(0x3F04, sizeof(m_flightData.flightname), m_flightData.flightname, &result);
    FSUIPC_Read(0x3130, sizeof(m_flightData.atcFlightNumStr), m_flightData.atcFlightNumStr, &result);
    FSUIPC_Read(0x313C, sizeof(m_flightData.atcFlightIDStr), m_flightData.atcFlightIDStr, &result);
    FSUIPC_Read(0x3148, sizeof(m_flightData.atcAirlineStr), m_flightData.atcAirlineStr, &result);
    FSUIPC_Read(0x3160, sizeof(m_flightData.atcAircraftTypeStr), m_flightData.atcAircraftTypeStr, &result);
    FSUIPC_Read(0x366, sizeof(plane_on_ground), &plane_on_ground, &result);
    FSUIPC_Read(0xBC8, sizeof(parking_brake), &parking_brake, &result);
    FSUIPC_Read(0x11D4, sizeof(replay_active), &replay_active, &result);
    FSUIPC_Process(&result);

	if (result != FSUIPC_ERR_OK)
	{
	    return -(int)result;    
	}

    memcpy(&lat_hi, &lat[4], sizeof(int));
    memcpy(&lat_lo, &lat[0], sizeof(int));
    m_flightData.lat = 90.0*((double)lat_hi + (double)lat_lo/TWO_PWR_32)/10001750;	// degrees

    memcpy(&lon_hi, &lon[4], sizeof(int));
    memcpy(&lon_lo, &lon[0], sizeof(int));
    m_flightData.lon = 360.0*((double)lon_hi + (double)lon_lo/TWO_PWR_32)/TWO_PWR_32;	// degrees

    memcpy(&alt_hi, &alt[4], sizeof(int));
    memcpy(&alt_lo, &alt[0], sizeof(int));
    m_flightData.gl_meter = groundLevel/256;									// meter
    m_flightData.alt_meter = (double)radio_alt/65536 + m_flightData.gl_meter;					// meter
	m_flightData.alt_feet = 3.28084*m_flightData.alt_meter;

    m_flightData.pitch_deg = 360.0*(double)pitch/TWO_PWR_32;							// degrees
    m_flightData.bank_deg = 360.0*(double)bank/TWO_PWR_32;								// degrees
    m_flightData.hdgTrue_deg = 360.0*(double)hdg_true/TWO_PWR_32;						// degrees
    if (m_flightData.hdgTrue_deg < 0.0)
            m_flightData.hdgTrue_deg += 360.0;
    if (m_flightData.hdgTrue_deg > 360.0)
            m_flightData.hdgTrue_deg -= 360.0;

    m_flightData.magVar_deg = 360.0*(double)mag_var/TWO_PWR_16;						// degrees
    m_flightData.hdgMag_deg = m_flightData.hdgTrue_deg - m_flightData.magVar_deg;
    if (m_flightData.hdgMag_deg < 0.0)
            m_flightData.hdgMag_deg += 360.0;
    if (m_flightData.hdgMag_deg > 360.0)
            m_flightData.hdgMag_deg -= 360.0;

    m_flightData.gs_meterPerSec = (double)gs/TWO_PWR_16;							// m/s
	m_flightData.gs_knots = 1.946*m_flightData.gs_meterPerSec;						// knots
    m_flightData.tas_knots = (double)tas/128.0;										// knots
    m_flightData.ias_knots = (double)ias/128.0;										// knots
    m_flightData.bpa_knots = (double)bpa/128.0;										// knots
    m_flightData.vs_feetPerMin = 100*(double)vs/128.0;								// ft/min
    m_flightData.framerate = TWO_PWR_16/(2*framerate);
    m_flightData.isReady = (FSRdy2Fly == 0) && (FSDialog == 0);
    m_flightData.isPaused = (paused != 0) && (replay_active != 0xFADE);
    m_flightData.isParked = (parking_brake != 0);
    m_flightData.isOnGround = (plane_on_ground != 0);

#ifdef FS_DEBUG	
	printf("\n");
	printf("FSRdy2Fly     = %08X\n", FSRdy2Fly);
	printf("FSDialog      = %08X\n", FSDialog);
	printf("paused        = %08X\n", paused);
	printf("replay_active = %08X\n", replay_active);
	printf("\n");
#endif
	uint32_t x = (FSRdy2Fly == 0) << 3 | (FSDialog == 0) << 2 | (replay_active == 0xfade) << 1 | (paused == 0);
	
	m_flightData.state = FlightData::State::STATE_INVALID;
	switch(x)
	{
		case 0x0d:
			m_flightData.state = FlightData::State::STATE_ACTIVE;
			break;
			
		case 0x09:
		case 0x0c:
			m_flightData.state = FlightData::State::STATE_PAUSED;
			break;
			
		case 0x00:
		case 0x05:
		case 0x08:
			m_flightData.state = FlightData::State::STATE_STOPPED;
			break;

		default:
#ifdef FS_DEBUG	
			printf("State : 0x%02X\n", x);
#endif
			break;
			
	}
    return 0;    
}

void FsLink::timerCallback()
{
	bool doUpdateState = false;
	bool doUpdateData = false;

	if (m_linkState == ls_disconnected)
	{
		m_linkStateNext = ls_connected;
		if (!open())
		{
			close();
			m_linkStateNext = ls_disconnected;
		}
	}
	else
	{
		if (gatherData() < 0)
		{
			close();
			m_linkStateNext = ls_disconnected;
		}
	}

//	dump();
	statisticsLearn();
	switch(m_linkState)
	{
		case ls_connected:
		if (m_flightData.state == FlightData::State::STATE_ACTIVE)
		{
			m_pKml->updateFlightData(m_flightData);
			if (m_pKml)
			{
				m_pKml->create();
			}
			m_linkStateNext = ls_recording;
		}
		break;

		case ls_recording:
		if (m_flightData.state == FlightData::State::STATE_STOPPED)
		{
			m_pKml->destroy();
			m_linkStateNext = ls_connected;
			break;
		}
		if (m_flightData.state == FlightData::State::STATE_PAUSED)
		{
			m_linkStateNext = ls_paused;
			break;
		}
		doUpdateData = true;
		break;

		case ls_paused:
		if (m_flightData.state == FlightData::State::STATE_STOPPED)
		{
			m_pKml->destroy();
			m_linkStateNext = ls_connected;
			break;
		}
		if (m_flightData.state == FlightData::State::STATE_ACTIVE)
		{
			m_linkStateNext = ls_recording;
			break;
		}
		break;

		default:
		break;
	}

	switch(m_flightState)
	{
		case fs_suspend:
		if (m_linkState == ls_disconnected)
			break;
		
		if (m_flightData.isReady && !m_flightData.isPaused)
		{
			m_flightStateNext = fs_ground;
			if (!m_flightData.isOnGround)
			{
				m_flightStateNext = fs_enroute;
			}

		}
		break;

		case fs_ground:
		if (!m_flightData.isOnGround)
		{
			m_flightStateNext = fs_takeoff;
		}
		break;

		case fs_takeoff:
			m_flightStateNext = fs_enroute;
		break;

		case fs_enroute:
		if (m_flightData.isOnGround)
		{
			m_flightStateNext = fs_touchdown;
		}
		break;

		case fs_touchdown:
		m_flightStateNext = fs_ground;
		break;

		default:
		break;
	}

	// Override flight states
	if (!m_flightData.isReady || m_flightData.isPaused)
	{
		m_flightStateNext = fs_suspend;
	}

	doUpdateState = m_linkState != m_linkStateNext;
	doUpdateState |= m_flightState != m_flightStateNext;

	m_linkState = m_linkStateNext;
	m_flightState = m_flightStateNext;

	if (doUpdateState)
	{
		m_listeners.call(&IFsLinkListener::onStateChanged, *this);
	}

	if (doUpdateData)
	{
		m_listeners.call(&IFsLinkListener::onDataChanged, *this);

		m_pKml->updateFlightData(m_flightData);
		if (m_kmlUpdateCount > 0)
		{
			m_kmlUpdateCount--;
		}
		else
		{
			m_pKml->updateTrack();
			m_pKml->exportKml(m_pKml->getPath());
			m_kmlUpdateCount = m_kmlUpdateInterval;
		}

	}

}

const char* FsLink::getLinkState()
{
    return linkStateString[m_linkState];
}

const char* FsLink::getFlightState()
{
    return flightStateString[m_flightState];
}

FlightData FsLink::getFlightData()
{
	return m_flightData;
}
