#include "GPS.h"

GPS* GPS::_instance = NULL;
BOOL GPS::_bGpsReady = FALSE;
volatile LONG GPS::lLock = 0;

/************************************************
 * self & Release
 ***********************************************/
GPS* GPS::self(DWORD dwTimeout, DWORD dwMaximumAge) {
	while (InterlockedExchange((LPLONG)&lLock, 1) != 0)
		Sleep(1);

	if (_instance == NULL) {
		_instance = new(std::nothrow) GPS();

		if (_instance)
			_instance->Initialize(dwTimeout, dwMaximumAge);
	}

	InterlockedExchange((LPLONG)&lLock, 0);

	return _instance;
}

/************************************************
 * Constructor & Destructor
 * Protected members
 ***********************************************/
GPS::GPS(void) : deviceObj(NULL) {
	_iReference = 0;
	_hMutex = NULL;
	_hGpsDevice = NULL;
	_dwTickCount = 0;
	_dwTimeout = 0;
	_dwLastGps = 0;
	bInitialized = FALSE;
	deviceObj = Device::self();
}

void GPS::Stop() {
	WAIT_AND_SIGNAL(this->_hMutex);

	_iReference--;

	if (_iReference > 0) {
		UNLOCK(this->_hMutex);
		return;
	}

	if (this->_hGpsDevice != NULL && this->_hGpsDevice != NULL) {
		GPSCloseDevice(_hGpsDevice);
		_hGpsDevice = NULL;
	}

	if (deviceObj)
		deviceObj->ReleaseGpsPowerState();

	bInitialized = FALSE;
	UNLOCK(this->_hMutex);
}

BOOL GPS::Start() {
	BOOL bRet;

	WAIT_AND_SIGNAL(this->_hMutex);
	_iReference++;

	// Non possiamo fare lo start due volte di seguito
	if (bInitialized) {
		UNLOCK(this->_hMutex);
		return TRUE;
	}

	// Entrambi i parametri sono gia' valorizzati al momento della ::self()
	bRet = Initialize(_dwTimeout, _dwMaximumAge);

	UNLOCK(this->_hMutex);
	return bRet;
}

GPS::~GPS() {
	if (this->_hMutex != NULL)
		CloseHandle(_hMutex);
	
	if (this->_hGpsDevice != NULL && this->_hGpsDevice != NULL)
		GPSCloseDevice(_hGpsDevice);
}

BOOL GPS::Initialize(DWORD dwTimeout, DWORD dwMaximumAge) {
	if (bInitialized)
		return TRUE;

	_dwTimeout = dwTimeout;
	_dwMaximumAge = dwMaximumAge;

	if (_hMutex == NULL)
		_hMutex = CreateMutex(NULL, FALSE, NULL);

	if (_hMutex == NULL) {
		bInitialized = FALSE;
		return FALSE;
	}

	if (_dwTimeout <= 0 || _dwMaximumAge == 0) {
		bInitialized = FALSE;
		return FALSE;	// Cannot initialize object!
	}

	_hGpsDevice = GPSOpenDevice(NULL, NULL, NULL, 0);

	if (_hGpsDevice == NULL) {	// GPS Initialization failed
		bInitialized = FALSE;
		return FALSE;
	}

	if (_hGpsDevice != NULL)
		GPS::_bGpsReady = TRUE;

	// Viene (e va) chiamato solo una volta
	if (deviceObj)
		deviceObj->SetGpsPowerState();

	bInitialized = TRUE;
	return TRUE;
}

BOOL GPS::GpsReady()
{
	return GPS::_bGpsReady;
}

/**
 *	Request refresh of ?
 **/
BOOL GPS::RefreshData() {
	WAIT_AND_SIGNAL(this->_hMutex);

	GPS_POSITION position;
	BOOL bResult = FALSE;

	if (GpsReady() == FALSE) {
		UNLOCK(this->_hMutex);
		return bResult;
	}
	
	ZeroMemory(&position, sizeof(GPS_POSITION));

	position.dwVersion = GPS_VERSION_1;
	position.dwSize = sizeof(GPS_POSITION);

	if (GPSGetPosition(_hGpsDevice, &position, _dwMaximumAge, 0) == ERROR_SUCCESS) {
		CopyMemory(&_gpsPosition, &position, sizeof(GPS_POSITION));
		_dwTickCount = GetTickCount();
		bResult = TRUE;
	}

	UNLOCK(this->_hMutex);
	return bResult;
}

BOOL GPS::getGPS(GPS_POSITION *_out) {
	if (_hGpsDevice == INVALID_HANDLE_VALUE || _hGpsDevice == NULL)
		return FALSE;

	if (GpsReady() == FALSE)
		return FALSE;

	if (_dwTickCount == 0 || ((GetTickCount() - _dwTickCount) > _dwMaximumAge)) {
		if (RefreshData()) {
			CopyMemory((void *)_out, (void *)&_gpsPosition, sizeof(GPS_POSITION));
			return TRUE;
		}
	}

	return FALSE;
}

// 6378137.0f -> semiasse equatoriale dell'ellissoide internazionale WGS-84
// 1/298.257223563 -> schiacciamento dell'ellissoide internazionale WGS-84
// 6356752.3142 -> raggio polare dell'ellissoide internazionale WGS-84
DOUBLE GPS::VincentFormula(DOUBLE lat1, DOUBLE lon1, DOUBLE lat2, DOUBLE lon2) {
	double a = 6378137.0f, b = 6356752.3142,  f = 1/298.257223563;
	double rad = PI / 180.0f, lon_rad = (lon2 - lon1) * rad;
	double U1 = atan((1 - f) * tan(lat1 * rad));
	double U2 = atan((1 - f) * tan(lat2 * rad));
	double sinU1 = sin(U1), cosU1 = cos(U1);
	double sinU2 = sin(U2), cosU2 = cos(U2);
	double cosSqAlpha, cos2SigmaM, sinSigma, cosSigma, sigma, sinAlpha;
	double cosLambda, sinLambda;
	double uSq, A, B, C, deltaSigma, s; 
	double lambda = lon_rad, lambdaP = 2 * PI;
	int iterations = 20;

	__try {
		while (fabs(lambda-lambdaP) > 0.000000000001f && --iterations > 0) {
			sinLambda = sin(lambda), cosLambda = cos(lambda);

			sinSigma = sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda) + 
				(cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda));

			if (sinSigma == 0.0f) 
				return 0.0f;  // I punti sono coincidenti

			cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
			sigma = atan2(sinSigma, cosSigma);

			sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;

			cosSqAlpha = 1 - sinAlpha * sinAlpha;
			cos2SigmaM = cosSigma - 2 * sinU1 * sinU2/cosSqAlpha;

			//if (isnan(cos2SigmaM)) cos2SigmaM = 0;

			C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));

			lambdaP = lambda;
			lambda = lon_rad + (1-C) * f * sinAlpha *
				(sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
		}

		if (iterations == 0) 
			return 0.0f;  // La formula non e' riuscita a convergere

		uSq = cosSqAlpha * (a * a - b * b) / (b * b);

		A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
		B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));

		deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)-
			B / 6* cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));

		s = b * A * (sigma - deltaSigma);
		return s;
	} __except(EXCEPTION_EXECUTE_HANDLER) {
		return 0.0f;
	}
}