#pragma once

#include <memory>
#include <map>

#include "ecc.h"
#include "base64.h"
#include "uniconv.h"
#include "except.h"
#include "sdkregistration.h"
#include "certificate.h"
#include "propertycollection.h"

using namespace std;

class LicenseTemplateImpl {
friend LicenseTemplate;

	struct FieldInfo
	{
		FieldInfo(int _type, int _size, int _offset)
		{
			type = (BitStruct::FIELD_TYPE)_type;
			size = _size;
			offset = _offset;
		}

		BitStruct::FIELD_TYPE type;
		int size;
		int offset;
	};

public:
	LicenseTemplateImpl(const char * xmlTemplate = NULL): 
		m_defaultDataOffset(0),
		m_defaultValidationOffset(0)
	{
		Init();

		if (xmlTemplate)
			LoadXml(xmlTemplate);
	}

	void SetVersion(int version)
	{
		if (version > 3)
			throw new LicensingException(STATUS_UNSUPPORTED_VERSION);

		m_version = version;
	}

	unsigned GetVersion() const
	{
		return m_version;
	}

	void SetNumberOfGroups(int numGroups)
	{
		m_numGroups = numGroups;
	}

	unsigned GetNumberOfGroups() const
	{
		return m_numGroups;
	}

	void SetCharactersPerGroup(int charsPerGroup)
	{
		m_charsPerGroup = charsPerGroup;
	}

	unsigned GetCharactersPerGroup() const
	{
		return m_charsPerGroup;
	}

	void SetGroupSeparator(const char * groupSep)
	{
		m_groupSeparator = groupSep;
	}

	const char * GetGroupSeparator() const
	{
		return m_groupSeparator.c_str();
	}

	void SetEncoding(int encoding)
	{
		m_keyEncoding = encoding;
	}

	int GetEncoding() const
	{
		return m_keyEncoding;
	}

	void SetHeader(const char * header)
	{
		m_header = header;
	}

	const char * GetHeader() const
	{
		return m_header.c_str();
	}

	void SetFooter(const char * footer)
	{
		m_footer = footer;
	}

	const char * GetFooter() const
	{
		return m_footer.c_str();
	}

	void SetDataSize(int sizeInBits)
	{
		m_dataSize = sizeInBits;
	}
	
	unsigned GetDataSize() const
	{
		return m_dataSize;
	}

	void AddDataField(const char * fieldName, int fieldType, int fieldSizeInBits, int fieldOffset = -1)
	{
		if (fieldOffset < 0)
			fieldOffset = m_defaultDataOffset;

		m_dataFields.emplace(std::piecewise_construct, forward_as_tuple(fieldName), forward_as_tuple(fieldType, fieldSizeInBits, fieldOffset));
	
		m_defaultDataOffset += fieldSizeInBits;
	}

	bool GetDataField(const char * fieldName, int * fieldType, int * fieldSizeInBits, int * fieldOffset) const
	{
		auto info = m_dataFields.find(fieldName);

		if (info == m_dataFields.end())
			return false;

		*fieldType = info->second.type;
		*fieldSizeInBits = info->second.size;
		*fieldOffset = info->second.offset;

		return true;
	}

	bool EnumDataFields(void **enumHandle, const char **fieldName, int * fieldType, int * fieldSizeInBits, int * fieldOffset) const
	{
		map<string, FieldInfo>::const_iterator* pIter;
		static string name;

		if (*enumHandle == NULL)
		{
			pIter = new map<string, FieldInfo>::const_iterator(m_dataFields.begin());
			if (!pIter)
				return false;
		}
		else
			pIter = (map<string, FieldInfo>::const_iterator*)(*enumHandle);

		if (*pIter == m_dataFields.end())
		{
			delete pIter;
			return false;
		}

		name = (*pIter)->first;
		*fieldName = name.c_str();

		*fieldType = (*pIter)->second.type;
		*fieldSizeInBits = (*pIter)->second.size;
		*fieldOffset = (*pIter)->second.offset;

		pIter->operator ++();  // *Iter++ does not work. Think why :)

		*enumHandle = pIter;

		return true;
	}

	void SetValidationDataSize(int sizeInBits)
	{
		m_validationDataSize = sizeInBits;
	}

	unsigned GetValidationDataSize() const
	{
		return m_validationDataSize;
	}

	void AddValidationField(const char * fieldName, int fieldType, int fieldBitSize, int fieldOffset = 0)
	{
		if (fieldOffset < 0)
			fieldOffset = m_defaultValidationOffset;

		m_validationFields.emplace(piecewise_construct, forward_as_tuple(fieldName), forward_as_tuple(fieldType, fieldBitSize, fieldOffset));
	
		m_defaultValidationOffset += fieldBitSize;
	}

	bool GetValidationField(const char * fieldName, int * fieldType, int * fieldSizeInBits, int * fieldOffset) const
	{
		auto info = m_validationFields.find(fieldName);

		if (info == m_validationFields.end())
			return false;

		*fieldType = info->second.type;
		*fieldSizeInBits = info->second.size;
		*fieldOffset = info->second.offset;

		return true;
	}

	bool EnumValidationFields(void **enumHandle, const char **fieldName, int * fieldType, int * fieldSize, int * fieldOffset) const
	{
		map<string, FieldInfo>::const_iterator* pIter;
		static string name;

		if (*enumHandle == NULL)
		{
			pIter = new map<string, FieldInfo>::const_iterator(m_validationFields.begin());
			if (!pIter)
				return false;
		}
		else
			pIter = (map<string, FieldInfo>::const_iterator*)(*enumHandle);

		if (*pIter == m_validationFields.end())
		{
			delete pIter;
			return false;
		}

		name = (*pIter)->first;
		*fieldName = name.c_str();

		*fieldType = (*pIter)->second.type;
		*fieldSize = (*pIter)->second.size;
		*fieldOffset = (*pIter)->second.offset;

		pIter->operator ++();  // *Iter++ does not work. Think why :)

		*enumHandle = pIter;

		return true;
	}

	void SetSignatureSize(int sizeInBits)
	{
		if (sizeInBits < 76 || sizeInBits > 322)
			throw new LicensingException(STATUS_INVALID_SIGNATURE_SIZE);

		m_signatureSize = sizeInBits;

		if (sizeInBits <= 108)
		{
			m_signatureKeyType = ECC::ECC_54;
			m_signatureKeySize = 54;
		} else
		if (sizeInBits <= 146)
		{
			m_signatureKeyType = ECC::ECC_73;
			m_signatureKeySize = 73;
		} else
		if (sizeInBits <= 158)
		{
			m_signatureKeyType = ECC::ECC_79;
			m_signatureKeySize = 79;
		} else
		if (sizeInBits <= 182)
		{
			m_signatureKeyType = ECC::ECC_91;
			m_signatureKeySize = 91;
		} else
		if (sizeInBits <= 200)
		{
			m_signatureKeyType = ECC::ECC_100;
			m_signatureKeySize = 100;
		} else
		if (sizeInBits <= 240)
		{
			m_signatureKeyType = ECC::ECC_120;
			m_signatureKeySize = 120;
		} else
		if (sizeInBits <= 262)
		{
			m_signatureKeyType = ECC::ECC_131;
			m_signatureKeySize = 131;
		} else
		if (sizeInBits <= 282)
		{
			m_signatureKeyType = ECC::ECC_141;
			m_signatureKeySize = 141;
		} else
		{
			m_signatureKeyType = ECC::ECC_161;
			m_signatureKeySize = 161;
		}
	}

	unsigned GetSignatureSize() const
	{
		return m_signatureSize;
	}

	void LoadXml(const char * xmlTemplate);
	const char * SaveXml(bool savePrivateKey);

	void LoadJson(const char * jsonTemplate);
	const char * SaveJson(bool savePrivateKey);

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

	const char * GetLicensingServiceUrl() const
	{
		return m_licensingServiceUrl.c_str();
	}

	void SetTemplateId(const char * templateId)
	{
		m_templateId = templateId;
	}

	const char * GetTemplateId() const
	{
		return m_templateId.c_str();
	}

	void SetLicensingServiceParameters(const char * params)
	{
		m_licensingServiceParameters = params;
	}

	void SetLicenseGracePeriod(int days)
	{
		m_licenseGracePeriod = days;
	}

	int GetLicenseGracePeriod() const
	{
		return m_licenseGracePeriod;
	}

	void SetPublicKeyCertificate(const char * base64Certificate)
	{
		m_certificate = make_unique<Certificate>(base64Certificate);
		
		ValidateCertificate();

		unsigned char keyBuf[256]; int keyLen = 256;

		m_certificate->GetPublicKey(keyBuf, &keyLen);

		m_verificationKey = make_unique<ECC::Key>(keyBuf, keyLen);
	}

	const char * GetPublicKeyCertificate() const
	{
		if (!m_certificate)
			throw new LicensingException(STATUS_GENERIC_ERROR, "the key template does not contain a certificate");

		return m_certificate->ToString();
	}

	void SetPrivateKey(const char * base64Key)
	{
		
		BASE64 base64;
		int keyLen;
		int keyLenSizeT;

		auto keyData = base64.decode(base64Key, (unsigned)strlen(base64Key), &keyLenSizeT);

		if (keyData.empty())
			throw new LicensingException(STATUS_GENERIC_ERROR, "invalid base64 private key");

		keyLen = (unsigned)keyLenSizeT;

		SetPrivateKey(keyData.data(), keyLen);
	}

	const char * GetPrivateKey() const
	{
		if (!m_signingKey)
			throw new LicensingException(STATUS_GENERIC_ERROR, "the key template does not contain a private key");

		return m_signingKey->ToString();
	}

	void GenerateSigningKeyPair()
	{
		Key * privateKey = NULL,
			* publicKey = NULL;

		if (!SDKRegistrationImpl::IsRegistered())
		{
			privateKey = new Key(); privateKey->Load(&m_demoPrivateKeys[m_signatureKeyType][1], m_demoPrivateKeys[m_signatureKeyType][0], m_signatureKeyType);
			publicKey = new Key(); publicKey->Load(&m_demoPublicKeys[m_signatureKeyType][1], m_demoPublicKeys[m_signatureKeyType][0], m_signatureKeyType);
		} else
		{
			ECC::GenerateKeyPair(m_signatureKeyType, &privateKey, &publicKey);
		}

		m_certificate.reset(Certificate::Generate(m_signatureSize, privateKey, publicKey, NULL, NULL, NULL, 0));

		ValidateCertificate();

		m_signingKey.reset(privateKey);

		m_verificationKey.reset(publicKey);
	}

    void SetProperty(const char * path, const char * name, const char * value)
    {
        m_properties.SetProperty(path, name, value);
    }

    const char * GetProperty(const char * path, const char * name) const
    {
        return m_properties.GetProperty(path, name);
    }

public:
	// general
	int m_version;

	// key format
	int m_numGroups;
	int m_charsPerGroup;
	int m_keyEncoding;
	string m_groupSeparator;
	string m_header;
	string m_footer;
	string m_licensingServiceUrl;
	string m_templateId;
	string m_licensingServiceParameters;

	map<string, FieldInfo> m_dataFields;
	int m_defaultDataOffset;

	map<string, FieldInfo> m_validationFields;
	int m_defaultValidationOffset;

	// key signature
	unique_ptr<ECC::Key> m_verificationKey;
	unique_ptr<ECC::Key> m_signingKey;

	unique_ptr<Certificate> m_certificate;
	bool m_hasPrivateKey;

	ECC::KEY_TYPE m_signatureKeyType;
	int m_signatureKeySize;
	int m_signatureSize;
	int m_dataSize;
	int m_validationDataSize;

	// buffer used to hold the xml representation of the template
	string m_xmlTemplate;

	// other stuff
	int m_licenseGracePeriod;

	PropertyCollection m_properties;

protected:
	static const unsigned char m_demoPublicKeys[10][23];
	static const unsigned char m_demoPrivateKeys[10][23];

private:
	void Init()
	{	
		SetVersion(3);
		SetLicenseGracePeriod(0);
		SetGroupSeparator("-");
		SetNumberOfGroups(6);
		SetCharactersPerGroup(6);
		SetEncoding(ENCODING_BASE32X);
		SetSignatureSize(158);
		SetDataSize(22); // 6 groups * 6 chars per group * 5 bits per char - 158 signature size = 22 data size
		SetValidationDataSize(0);
		m_hasPrivateKey = false;
	}

	void SetPrivateKey(const void * keyBuf, int keyLen)
	{
		ValidatePrivateKey(keyBuf, keyLen);

		m_signingKey.reset(new Key(keyBuf, keyLen));

		m_hasPrivateKey = true;
	}

	void ValidatePrivateKey(const void * keyBuf, int keyLen)
	{
		if (keyLen <= 2)
		{
			throw new LicensingException(STATUS_INVALID_PARAM, "Invalid private key length");
		}

		keyLen -= 2;
		keyBuf = (unsigned char *)keyBuf + 2;

		if (!SDKRegistrationImpl::IsRegistered())
		{
			if (keyLen != m_demoPrivateKeys[m_signatureKeyType][0] || memcmp(keyBuf, &m_demoPrivateKeys[m_signatureKeyType][1], keyLen) != 0)
			{
				m_signingKey.reset();
				throw new LicensingException(STATUS_INVALID_PARAM, "Invalid public/private key. The SDK is in DEMO mode because a SDK license key was not set. In this mode, only certain public/private keys can be set. Did you set a valid SDK license key via ANSCENTER::Licensing::SDKRegistration::SetLicenseKey() ?");
			}
		}
	}

	void ValidateCertificate()
	{
		unique_ptr<Certificate> validationCertificate = make_unique<Certificate>("AE4vDQELjMGgCJew5QSVMBWSAN2PaBa0zzYpjSYe0rcu88lYggE=");

		if (!Certificate::VerifySignature(m_certificate.get(), validationCertificate.get()))
			throw new LicensingException(STATUS_GENERIC_ERROR, "certificate signature is not valid");
	}
};
