#pragma once

#include <memory>
#include <vector>

#include "except.h"
#include "template.h"
#include "generator.h"
#include "validator.h"
#include "ntpclient.h"
#include "uniconv.h"
#include "license.h"
#include "licensevalidationargs.h"
#include "licensevalidationresult.h"

#include <time.h>

#ifdef _WIN32
#include <winhttp.h>
#include <tchar.h>
#else
#include <curl/curl.h>
#endif

using namespace std;

// these methods are compiler workarounds to eliminate circular references.
// They only call the corresponding LicensingClient::... methods 
// GCC complains of cross-references between LicensingClientImpl and LicensingClientT

const wchar_t * GetCurrentHardwareIdW(void *);
const char * GetCurrentHardwareIdA(void *);
bool MatchCurrentHardwareIdW(void *, const wchar_t *);
bool MatchCurrentHardwareIdA(void *, const char *);

class LicensingClientImpl
{
public:
	LicensingClientImpl(void * licensingClient, bool usesWideChar)
	{
		licenseKeyValidationDataLen = 0;
		licenseTemplateId.clear();
		parentLicensingClient = licensingClient;
		parentUsesWideChar = usesWideChar;
		timeValidationMethod = PREFER_INTERNET_TIME;
		m_licenseValidityDays = 0;
		// GetCurrentHardwareIdCallback = GetCurrentHardwareId;
		// MatchCurrentHardwareIdCallback = MatchCurrentHardwareId;
	}

	void SetLicenseTemplate(const LicenseTemplateImpl* tmpl)
	{
		licenseTemplate = tmpl;
	}

	void SetLicenseKey(const char * key)
	{
		licenseKey = key;
	}

	void SetActivationKey(const char * key)
	{
		activationKey = key;
	}

	const char * GetActivationKey()
	{
		return activationKey.c_str();
	}

	void SetHardwareId(const char * hwid)
	{
		hardwareId = hwid;
	}

	const char * GetHardwareId()
	{
		return hardwareId.c_str();
	}

	void SetLicenseTemplateId(const char * templateId)
	{
		licenseTemplateId = templateId;
	}

	const char * GetLicenseTemplateId()
	{
		return licenseTemplateId.c_str();
	}

	void SetLicenseKeyValidationData(const void* data, unsigned len)
	{
		licenseKeyValidationData.resize(len);

		memcpy(licenseKeyValidationData.data(), data, len);

		licenseKeyValidationDataLen = len;
	}

	LicenseTemplateImpl * CreateActivationTemplate()
	{
		LicenseTemplateImpl * tmpl = new LicenseTemplateImpl();

		tmpl->SetCharactersPerGroup(licenseTemplate->GetCharactersPerGroup());
		tmpl->SetNumberOfGroups(licenseTemplate->GetNumberOfGroups());
		tmpl->SetGroupSeparator(licenseTemplate->GetGroupSeparator());
		//activationKeyTemplate.SetDataSize(t.GetDataSize());
		tmpl->SetSignatureSize(licenseTemplate->GetSignatureSize());
		tmpl->SetEncoding(licenseTemplate->GetEncoding());
		tmpl->SetPublicKeyCertificate(licenseTemplate->GetPublicKeyCertificate());

		int keyLen = tmpl->GetNumberOfGroups() * tmpl->GetCharactersPerGroup()
			+ (tmpl->GetNumberOfGroups() - 1) * strlen(tmpl->GetGroupSeparator());
		
		int hwidLen = 29;

		if (hardwareId.length() > 0)
			hwidLen = hardwareId.length();

		tmpl->SetDataSize(tmpl->GetCharactersPerGroup() * tmpl->GetNumberOfGroups() * tmpl->GetEncoding() - tmpl->GetSignatureSize());
		tmpl->SetValidationDataSize((keyLen + hwidLen ) * 8 + licenseTemplate->GetValidationDataSize());

		tmpl->AddDataField("ExpirationDate", FIELD_TYPE_RAW, 16, 0);
		tmpl->AddValidationField("LicenseKey", FIELD_TYPE_STRING, keyLen * 8, 0);
		tmpl->AddValidationField("HardwareId", FIELD_TYPE_STRING, hwidLen * 8, keyLen * 8);

		if (!licenseKeyValidationData.empty())
		{
			tmpl->AddValidationField("LicenseKeyValidationData", FIELD_TYPE_RAW, licenseKeyValidationDataLen * 8, (keyLen + hwidLen) * 8);
		}

		if (licensingServiceUrl.empty())
			tmpl->SetLicensingServiceUrl(licenseTemplate->GetLicensingServiceUrl());
		else
			tmpl->SetLicensingServiceUrl(licensingServiceUrl.c_str());

		if (licenseTemplateId.empty())
			tmpl->SetTemplateId(licenseTemplate->GetTemplateId());
		else
			tmpl->SetTemplateId(licenseTemplateId.c_str());

		return tmpl;
	}

	bool ValidateHardwareId()
	{
		if (!MatchCurrentHardwareIdCallback(hardwareId.c_str()))
		{
			activationStatus = STATUS_INVALID_HARDWARE_ID;
			return false;
		}

		return true;
	}

	bool ValidateActivationKey(LicenseTemplateImpl* activationTemplate = nullptr, bool validateExpirationDate = true)
	{
		bool ownActivationTemplate = false;

		try {
			if (!activationTemplate)
			{
				activationTemplate = CreateActivationTemplate();
				ownActivationTemplate = true;
			}

			KeyValidatorImpl validator(activationTemplate);

			validator.SetValidationData("LicenseKey", licenseKey.c_str());
			validator.SetValidationData("HardwareId", hardwareId.c_str());

			if (!licenseKeyValidationData.empty())
				validator.SetValidationData("LicenseKeyValidationData", licenseKeyValidationData.data(), (activationTemplate->GetValidationDataSize() + 7) >> 3);

			try 
			{
				validator.SetKey(activationKey.c_str());
			}
			catch (Exception * ex)
			{
				ex->Destroy();
				activationStatus = STATUS_INVALID_ACTIVATION_KEY;
				if (ownActivationTemplate) delete activationTemplate;
				return false;
			}

			if (!validator.IsKeyValid())
			{
				activationStatus = STATUS_INVALID_ACTIVATION_KEY;
				if (ownActivationTemplate) delete activationTemplate;
				return false;
			}

			unsigned char rawKeyData[2];
			int rawKeyDataBits = 16;

			validator.QueryKeyData("ExpirationDate", rawKeyData, &rawKeyDataBits);

			unsigned short keyData = (unsigned short)(((unsigned short)rawKeyData[0]) << 8 | rawKeyData[1]);

			memset(&licenseExpirationTime, 0, sizeof(licenseExpirationTime));

			if (keyData != 0)
			{
				licenseExpirationTime.tm_year = (2000 + (keyData >> 9)) - 1900;
				licenseExpirationTime.tm_mon = ((keyData & 0x01FF) >> 5) - 1;
				licenseExpirationTime.tm_mday = keyData & 0x001F;

				if (!validateExpirationDate)
				{
					if (ownActivationTemplate) delete activationTemplate;
					return true;
				}

				time_t currentTime;

				if (timeValidationMethod != USE_LOCAL_TIME)
				{
					currentTime = NTPClient::GetCurrentTimeUTC();

					if (currentTime == 0) // if couldn't obtain internet time
					{
						if (timeValidationMethod == USE_INTERNET_TIME) // internet time was mandatory
						{
							activationStatus = STATUS_NET_ERROR;
							if (ownActivationTemplate) delete activationTemplate;
							return false;
						}

						// fallback to local time
						currentTime = time(NULL);
					}
				}
				else
					currentTime = time(NULL);

				double diffSeconds = difftime(mktime(&licenseExpirationTime), currentTime);

				if (diffSeconds < 0)
				{
					m_licenseValidityDays = licenseTemplate->GetLicenseGracePeriod() - (int)(((time_t)(-diffSeconds) + 86400 - 1) / 86400);
					
					if (m_licenseValidityDays < 0)
						m_licenseValidityDays = 0;
					
					activationStatus = STATUS_LICENSE_EXPIRED;
					
					if (ownActivationTemplate) delete activationTemplate;
					
					return false;
				} else
					m_licenseValidityDays = licenseTemplate->GetLicenseGracePeriod() + (int)(((time_t)diffSeconds + 86400 - 1) / 86400);
			}
			else
				m_licenseValidityDays = -1;

			if (ownActivationTemplate) delete activationTemplate;
			
			return true;
		}
		catch (...)
		{
			if (ownActivationTemplate && activationTemplate)
				delete activationTemplate;

			throw;
		}
	}

	bool IsLicenseValid()
	{
		activationStatus = STATUS_GENERIC_ERROR;

		try 
		{
			if (ValidateHardwareId() && ValidateActivationKey())
			{
				activationStatus = STATUS_SUCCESS;
				return true;
			}
			else
				return false;
		}
		catch (Exception * ex)
		{
			ex->Destroy();

			activationStatus = STATUS_GENERIC_ERROR;
			return false;
		}
		catch (...)
		{
			activationStatus = STATUS_GENERIC_ERROR;
			return false;
		}
	}

	LicenseValidationResultImpl& ValidateLicense(LicenseValidationArgsImpl * args)
	{
		const LicenseImpl * license = args->GetLicense();
		const char * lk = args->GetLicenseKey();

		licenseKey = (lk) ? lk : "";

		if (license == NULL && licenseKey.length() == 0)
			throw new LicensingException(STATUS_GENERIC_ERROR, "either a license or a license key must be provided");

		if (license == NULL)
		{
			try {
				KeyValidatorImpl keyValidator;
				
				keyValidator.SetKeyTemplate(licenseTemplate);

				keyValidator.SetKey(lk);

				if (licenseTemplate->GetValidationDataSize() > 0)
				{
					int fieldType, fieldSize, fieldOffset;

					for (std::list<NameValuePair>::iterator iter = args->m_licenseKeyValidationData.begin(); iter != args->m_licenseKeyValidationData.end(); iter++)
					{
						if (!licenseTemplate->GetValidationField(iter->name.c_str(), &fieldType, &fieldSize, &fieldOffset))
							throw new LicensingException(STATUS_INVALID_LICENSE_KEY, "license key validation data is invalid");

						switch (fieldType) 
						{
						case FIELD_TYPE_INTEGER:
							keyValidator.SetValidationData(iter->name.c_str(), iter->intValue);
							break;

						case FIELD_TYPE_STRING:
							keyValidator.SetValidationData(iter->name.c_str(), iter->stringValue.c_str());
							break;

						case FIELD_TYPE_DATE13:
						case FIELD_TYPE_DATE14:
						case FIELD_TYPE_DATE16:
							keyValidator.SetValidationData(iter->name.c_str(), iter->dateValue.year, iter->dateValue.month, iter->dateValue.day);
							break;

						case FIELD_TYPE_RAW:
							// keyValidator.SetValidationData(iter->name.c_str(), &(iter->value.binaryValue[0]), iter->value.binaryValue.size);
							break;

						default:
							break;
						}
					}

					licenseKeyValidationDataLen = (licenseTemplate->GetValidationDataSize() + 7) >> 3;
					
					licenseKeyValidationData.resize(licenseKeyValidationDataLen);

					keyValidator.QueryValidationData(NULL, licenseKeyValidationData.data(), &licenseKeyValidationDataLen);
				}
				
				if (!keyValidator.IsKeyValid())
				{
					throw new LicensingException(STATUS_INVALID_LICENSE_KEY, "license key is not valid");
				}
				
				AcquireLicense();
			}
			catch (LicensingException * ex)
			{
				if (ex->GetCode() == STATUS_PAYMENT_REQUIRED)
				{
					m_licenseValidationResult.SetPaymentRequired(true);
					ex->Destroy();
				}
				else
					throw;
			}
		}
		else
		{
			licenseKey = license->GetLicenseKey();
			hardwareId = license->GetHardwareId();
			activationKey = license->GetActivationKey();

			const void* buf; int len;
			license->GetLicenseKeyValidationData(&buf, &len);

			if (buf && (len > 0))
				SetLicenseKeyValidationData(buf, (unsigned int)len);

			if (ValidateActivationKey(NULL, true))
			{
				m_licenseValidationResult.SetLicenseValid(true);
				m_licenseValidationResult.SetPaymentRequired(false);
				m_licenseValidationResult.SetLicenseExpired(false);
				m_licenseValidationResult.SetLicense(NULL);
				return m_licenseValidationResult;
			}
			else
			{
				if (activationStatus == STATUS_LICENSE_EXPIRED)
				{
					m_licenseValidationResult.SetLicenseExpired(true);
				}

				if ((activationStatus == STATUS_LICENSE_EXPIRED) && (m_licenseValidityDays > 0) && license->IsLease())
				{
					try 
					{
						AcquireLicense();
						m_licenseValidationResult.SetLicenseExpired(false);
					}
					catch (LicensingException * ex)
					{
						if (ex->GetCode() == STATUS_PAYMENT_REQUIRED)
						{
							m_licenseValidationResult.SetPaymentRequired(true);
							ex->Destroy();
						}
						else
							throw;
					}
				}
			}
		}

		if (licenseExpirationTime.tm_year > 0)
		{
			m_licenseValidationResult.SetLicenseExpirationDate(
					1900 + licenseExpirationTime.tm_year, 
					1 + licenseExpirationTime.tm_mon, 
					licenseExpirationTime.tm_mday);
		}
		else
		{
			m_licenseValidationResult.SetLicenseExpirationDate(0, 0, 0);
		}

		return m_licenseValidationResult;
	}

	void AcquireLicense()
	{
		if (hardwareId.empty())
			hardwareId = (parentLicensingClient) ? GetCurrentHardwareIdCallback() : GetCurrentHardwareId();

		unique_ptr<LicenseTemplateImpl> activationTemplate(CreateActivationTemplate());

		const char * templateId = activationTemplate->GetTemplateId();

		if (templateId != NULL && templateId[0] != '\0')
		{
			char params[64] = "ProductId=";
			sprintf(params + sizeof("ProductId"), "%s", activationTemplate->GetTemplateId());
			activationTemplate->SetLicensingServiceParameters(params);
		}

		KeyGeneratorImpl generator(activationTemplate.get());

		generator.SetKeyData("ExpirationDate", (int)0);

		generator.SetValidationData("LicenseKey", licenseKey.c_str());

		generator.SetValidationData("HardwareId", hardwareId.c_str());

		if (!licenseKeyValidationData.empty())
		{
			generator.SetValidationData("LicenseKeyValidationData", licenseKeyValidationData.data(), licenseKeyValidationDataLen * 8);
		}

		try {
			activationKey = generator.GenerateKey();
		}
		catch (LicensingException * ex)
		{
			if ((ex->GetCode() == STATUS_NET_ERROR) && (generator.GetSigningServiceStatusCode() == 406))
			{
				m_licenseValidationResult.SetPaymentRequired(true);
				ex->Destroy();
			}
			else
				throw;
		}

		if (ValidateActivationKey(activationTemplate.get(), false))
		{
			activationStatus = STATUS_SUCCESS;
		}
		else
			throw new LicensingException(STATUS_GENERIC_ERROR, "internal error - invalid returned activation key");
		
		m_licenseValidationResult.m_license = std::make_unique<LicenseImpl>();

		m_licenseValidationResult.m_license->SetHardwareId(hardwareId.c_str());
		m_licenseValidationResult.m_license->SetActivationKey(activationKey.c_str());
		m_licenseValidationResult.m_license->SetLicenseKey(licenseKey.c_str());
		m_licenseValidationResult.m_license->SetLicenseKeyValidationData(licenseKeyValidationData.data(), licenseKeyValidationDataLen);

		int status = generator.GetSigningServiceStatusCode();

		if (status == 200 /* HTTP OK */)
			m_licenseValidationResult.m_license->SetLease(false);
		else
			if (status == 201 || status == 202)
				m_licenseValidationResult.m_license->SetLease(true);

		m_licenseValidationResult.SetLicenseValid(true);
	}

	void SetLicensingServiceUrl(const char * url)
	{
		licensingServiceUrl = url;
	}

	int GetLicenseActivationStatus()
	{
		return activationStatus;
	}

	void GetLicenseExpirationDate(int * year, int * month, int * day)
	{
		if (licenseExpirationTime.tm_year > 0)
		{
			*year = 1900 + licenseExpirationTime.tm_year;
			*month = 1 + licenseExpirationTime.tm_mon;
			*day = licenseExpirationTime.tm_mday;
		} else
		{
			*year = 0;
			*month = 0;
			*day = 0;
		}
	}

	const char * GetCurrentHardwareIdCallback()
	{
		if (parentUsesWideChar) 
		{
			static string hwid = w2s(GetCurrentHardwareIdW(parentLicensingClient));
			return hwid.c_str();
		}
		else
		{
			return GetCurrentHardwareIdA(parentLicensingClient);
		}
	}

	bool MatchCurrentHardwareIdCallback(const char * hwid)
	{
		if (parentUsesWideChar) 
		{
			return MatchCurrentHardwareIdW(parentLicensingClient, s2w(hwid).c_str());
		}
		else
			return MatchCurrentHardwareIdA(parentLicensingClient, hwid);
	}

	static const char * GetCurrentHardwareId()
	{
		return KeyHelper::GetCurrentHardwareId();
	}

	static bool MatchCurrentHardwareId(const char * hwid)
	{
		return KeyHelper::MatchCurrentHardwareId(hwid);
	}

	void SetTimeValidationMethod(int method)
	{
		timeValidationMethod = method;
	}

private:
	void StrReplaceA(std::string& str, const std::string& oldStr, const std::string& newStr)
	{
		size_t pos = 0;
		while ((pos = str.find(oldStr, pos)) != std::string::npos)
		{
			str.replace(pos, oldStr.length(), newStr);
			pos += newStr.length();
		}
	}

private:
	const LicenseTemplateImpl* licenseTemplate;

	LicenseValidationResultImpl m_licenseValidationResult;

	struct tm licenseExpirationTime;
	string licenseKey;
	string activationKey;
	string hardwareId;
	string licensingServiceUrl;
	
	vector<unsigned char> licenseKeyValidationData;
	int licenseKeyValidationDataLen;
	int activationStatus;
	string licenseTemplateId;
	void * parentLicensingClient;
	bool parentUsesWideChar;
	int timeValidationMethod;
	int m_licenseValidityDays;
	//const char * (*GetCurrentHardwareIdCallback)(void * callerContext);
	//bool (*MatchCurrentHardwareIdCallback)(void * callerContext, const char * hwid);
};
