#ifndef __CERTIFICATE_H
#define __CERTIFICATE_H

#include "ecc.h"
#include "bitstruct.h"
#include "except.h"
#include "base64.h"
#include "sdkregistration.h"
#include "download.h"

#include <time.h>

using namespace ECC;

class Certificate
{
public:
	Certificate()
	{

	}

	Certificate(const char * base64Certificate)
	{
		
		BASE64 base64;
		int len;

		auto buf = base64.decode(base64Certificate, strlen(base64Certificate), &len);

		if (buf.empty())
			throw new LicensingException(STATUS_GENERIC_ERROR, "invalid base64 encoding for certificate");

		Attach(buf, len);
	}

	Certificate(vector<unsigned char>& buf, int len)
	{
		Attach(buf, len);
	}

	void* GetBuffer(int * len)
	{
		int size;
		void * buf = m_certBits.GetBuffer(&size);
		size = (size + 7) >> 3;
		*len = size;
		return buf;
	}

	const char * ToString()
	{
		
		BASE64 base64;
		int len;
		static string base64cert;
		unsigned char * buf = (unsigned char *)m_certBits.GetBuffer(&len); 
		len = (len + 7) >> 3;

		auto base64Buf = base64.encode(buf, len, true);

		if (base64Buf.empty())
			throw new LicensingException(STATUS_GENERIC_ERROR, "could not encode buffer to base64");

		base64cert = base64Buf.c_str();

		return base64cert.c_str();
	}

	/*
	static Certificate * Generate(int signatureSize, time_t expirationDate = 0)
	{
		return Generate(signatureSize, (ECC::Key *)NULL, NULL, NULL, NULL, expirationDate);
	}

	static Certificate * Generate(int signatureSize, Certificate * signingCertificate, time_t expirationDate = 0)
	{
		return Generate(signatureSize, NULL, NULL, signingCertificate, NULL, expirationDate);
	}

	static Certificate * Generate(int signatureSize, const char * certificateAuthorityUrl, time_t expirationDate = 0)
	{
		return Generate(signatureSize, (ECC::Key *)NULL, NULL, NULL, certificateAuthorityUrl, expirationDate);
	}
	*/

	static bool VerifySignature(Certificate * cert, Certificate * validationCert)
	{
		unsigned char rawPublicKey[256]; int rawPublicKeyLen = 256;

		if (validationCert == NULL || cert->m_certBits.GetInt("IsSelfSigned") != 0)
			cert->m_certBits.Get("PublicKey", rawPublicKey, &rawPublicKeyLen);
		else
			validationCert->m_certBits.Get("PublicKey", rawPublicKey, &rawPublicKeyLen);

		Key key(rawPublicKey, rawPublicKeyLen);

		Verifier verifier;

        verifier.SetPublicKey(&key);

		unsigned char * certBuffer = (unsigned char *)cert->m_certBits.GetBuffer();

		int signedPartLen = 4 + cert->m_certBits.GetInt("PublicKeyLen"),
			signatureOffset = 2 + signedPartLen,
			signatureBitLen = cert->m_certBits.GetInt("SignatureSizeBits");
				
        if (!verifier.Verify(certBuffer, signedPartLen, certBuffer + signatureOffset, (signatureBitLen + 7) >> 3, signatureBitLen))
            return false;

		return true;
	}

	void GetPublicKey(void * buf, int * len)
	{
		m_certBits.Get("PublicKey", buf, len);
	}

	static Certificate * Generate(int signatureSize, const char * base64PrivateKey, const char * base64PublicKey, const char * base64SigningPrivateKey, const char * base64SigningCertificate, const char * certificateAuthorityUrl, time_t expirationDate)
	{
		Key * privateKey = NULL,
			* publicKey = NULL,
			* privateSigningKey;

		Certificate * signingCertificate = NULL;
		Certificate * result;

		try {
			if (base64PublicKey != NULL)
				publicKey = new Key(base64PublicKey);

			if (base64PrivateKey != NULL)
				privateKey = new Key(base64PrivateKey);

			if (base64SigningPrivateKey != NULL)
				privateSigningKey = new Key(base64SigningPrivateKey);

			if (base64SigningCertificate != NULL)
				signingCertificate = new Certificate(base64SigningCertificate);

			result = Generate(signatureSize, privateKey, publicKey, privateSigningKey, signingCertificate, certificateAuthorityUrl, expirationDate);
		} catch (...)
		{
			if (privateKey) delete privateKey;
			if (publicKey) delete publicKey;
			if (privateSigningKey) delete privateSigningKey;
			if (signingCertificate) delete signingCertificate;

			throw;
		}

		if (privateKey) delete privateKey;
		if (publicKey) delete publicKey;
		if (privateSigningKey) delete privateSigningKey;
		if (signingCertificate) delete signingCertificate;

		return result;
	}
	
	static Certificate * Generate(int signatureSize, Key * privateKey, Key * publicKey, Key * signingPrivateKey, Certificate * signingCertificate, const char * certificateAuthorityUrl, time_t expirationDate)
	{
		
    
		if (publicKey == NULL)
		{
			throw new LicensingException(STATUS_GENERIC_ERROR, "public key must be provided for the certificate");
		}

        Key * signingKey = NULL;
        bool selfSigned;

        if (signingPrivateKey == NULL && certificateAuthorityUrl == NULL)
        {
            selfSigned = true;
            signingKey = privateKey;
        }
        else
        {
            selfSigned = false;
            if (signingPrivateKey != NULL)
				signingKey = signingPrivateKey;
			// else use certificate authority url for signing
        }

		unsigned char rawPublicKey[256]; int rawPublicKeyLen = 256;
        publicKey->Store(rawPublicKey, &rawPublicKeyLen);

		// unsigned char rawPrivateKey[256]; int rawPrivateKeyLen = 256;
        // signingKey->Store(rawPrivateKey, &rawPrivateKeyLen);

		int certBitsLen = 4 * 8 + rawPublicKeyLen * 8;

        BitStruct certBits(certBitsLen);

        certBits.ResetFieldOffset();

        certBits.AddField("Version", BitStruct::INTEGER, 4);
        certBits.AddField("IsSelfSigned", BitStruct::INTEGER, 1);
        certBits.AddField("ReservedFlags", BitStruct::INTEGER, 3);
        certBits.AddField("ExpirationDate", BitStruct::DATE16, 16);
        certBits.AddField("PublicKeyLen", BitStruct::INTEGER, 8);
        certBits.AddField("PublicKey", BitStruct::RAW, rawPublicKeyLen * 8);

        certBits.Set("Version", 0);
        certBits.Set("IsSelfSigned", (selfSigned) ? 1 : 0);
        certBits.Set("ReservedFlags", 0);

		expirationDate = time(NULL);

		struct tm * t = gmtime(&expirationDate);

        certBits.Set("ExpirationDate", t->tm_year + 1900, t->tm_mon, t->tm_mday);
        certBits.Set("PublicKeyLen", rawPublicKeyLen);
        certBits.Set("PublicKey", rawPublicKey, rawPublicKeyLen);

        unsigned char certSignature[256];
        int sigSizeBytes = 256;
		int sigSizeBits;

        if (signingKey != NULL)
        {
            Signer signer;
            signer.SetPrivateKey(signingKey);
            signer.Sign(certBits.GetBuffer(), (certBitsLen + 7) >> 3, certSignature, &sigSizeBytes, &sigSizeBits);
        }
        else
        {
#ifndef LICENSING_NO_NETWORKING			
			if (!certificateAuthorityUrl)
				throw new LicensingException(STATUS_GENERIC_ERROR, "no means for certificate signing were provided");

			string certificateAuthorityRequest(certificateAuthorityUrl);
			certificateAuthorityRequest.append("?csr=");
			BASE64 base64;
			int len = 4096;
			char buffer[4096];

			auto base64csr = base64.encode((unsigned char *)certBits.GetBuffer(), (certBits.GetSize() + 7) >> 3, true);
			string escapedBase64csr(base64csr.c_str());

			StrReplace(escapedBase64csr, "+", "%2B");
			StrReplace(escapedBase64csr, "/", "%2F");
			StrReplace(escapedBase64csr, "=", "%3D");

			certificateAuthorityRequest.append(escapedBase64csr);

			const char * sdkLicenseKey = SDKRegistrationImpl::GetLicenseKey();

			if (sdkLicenseKey && strlen(sdkLicenseKey) > 0)
			{
				certificateAuthorityRequest.append("&sdklicensekey=");
				certificateAuthorityRequest.append(sdkLicenseKey);
			}

			UrlDownloadToString(certificateAuthorityRequest.c_str(), buffer, &len);

			auto rawBuffer = base64.decode(buffer, len, &len);

			if (rawBuffer.empty())
				throw new LicensingException(STATUS_GENERIC_ERROR, "invalid base64 string");

			auto certBuffer = rawBuffer;

			unique_ptr<Certificate> issuedCert = std::make_unique<Certificate>(certBuffer, len);
		
			sigSizeBits = issuedCert->m_certBits.GetInt("SignatureSizeBits");
			sigSizeBytes = (sigSizeBits + 7) >> 3; 
			issuedCert->m_certBits.Get("Signature", certSignature, &sigSizeBytes);

			memcpy(certBits.GetBuffer(), rawBuffer.data(), 4 + rawPublicKeyLen);
#else
			throw new LicensingException(STATUS_GENERIC_ERROR, "certificate signing private key not provided");
#endif
        }

		int certBufLen = ((certBitsLen + 7) >> 3)   // header + public key
                        + 2                         // signature size in bits
                        + ((sigSizeBits + 7) >> 3); // signature

		vector<unsigned char> certBuf;
		certBuf.resize(certBufLen);

        memcpy(certBuf.data(), certBits.GetBuffer(), (certBitsLen + 7) >> 3);
        
        certBits.Attach(certBuf, certBufLen * 8);

        certBits.AddField("SignatureSizeBits", BitStruct::INTEGER, 16);
        certBits.AddField("Signature", BitStruct::RAW, ((sigSizeBits + 7) >> 3) << 3);

        certBits.Set("SignatureSizeBits", sigSizeBits);
        certBits.Set("Signature", certSignature, (sigSizeBits + 7) >> 3);

        Certificate * cert = new Certificate();

        cert->m_certBits = std::move(certBits);

        return cert;
	}

	static const char * Sign(const char * csr, const char * privateKey, const char * signingPrivateKey, const char * signingCertificate, const char * certificateAuthorityUrl, int expYear, int expMonth, int expDay)
	{
		Certificate * _csr = new Certificate(csr);
		Key * _privateKey = (privateKey) ? new Key(privateKey) : NULL;
		Key * _signingPrivateKey = (signingPrivateKey) ? new Key(signingPrivateKey) : NULL;
		Certificate * _signingCertificate = (signingCertificate) ? new Certificate(signingCertificate) : NULL;
		struct tm _expTm;
		time_t _expTime = 0;

		if (expYear >= 1900 && expMonth >= 1 && expMonth <= 12 && expDay >= 1 && expDay <= 31)
		{
			memset(&_expTm, 0, sizeof(_expTm));

			_expTm.tm_year = expYear - 1900;
			_expTm.tm_mon = expMonth - 1;
			_expTm.tm_mday = expDay;

			_expTime = mktime(&_expTm);
		}
		else
			if (expYear != 0 || expMonth != 0 || expDay != 0)
				throw new LicensingException(STATUS_GENERIC_ERROR, "Invalid certificate expiration date");

		Certificate * _signedCert = Sign(_csr, _privateKey, _signingPrivateKey, _signingCertificate, certificateAuthorityUrl, 0);

		if (_csr) delete _csr;
		if (_privateKey) delete _privateKey;
		if (_signingPrivateKey) delete _signingPrivateKey;
		if (_signingCertificate) delete _signingCertificate;

		const char * signedCert = _signedCert->ToString();

		delete _signedCert;

		return signedCert;
	}

	static Certificate * Sign(Certificate * csr, Key * privateKey, Key * signingPrivateKey, Certificate * signingCertificate, const char * certificateAuthorityUrl, time_t expirationDate)
	{
		unsigned char buf[1024];
		int len = 1024;
		Key * publicKey;

		csr->GetPublicKey(buf, &len);
		publicKey = new Key(buf, len);

		Certificate * cert = Certificate::Generate(0, privateKey, publicKey, signingPrivateKey, signingCertificate, certificateAuthorityUrl, expirationDate);
	
		delete publicKey;

		return cert;
	}

private:
	void Attach(vector<unsigned char>& buf, int len)
	{
        m_certBits.Attach(buf, len << 3);

        m_certBits.ResetFieldOffset();

        m_certBits.AddField("Version", BitStruct::INTEGER, 4);
        m_certBits.AddField("IsSelfSigned", BitStruct::INTEGER, 1);
        m_certBits.AddField("ReservedFlags", BitStruct::INTEGER, 3);
        m_certBits.AddField("ExpirationDate", BitStruct::DATE16, 16);
        m_certBits.AddField("PublicKeyLen", BitStruct::INTEGER, 8);        
        m_certBits.AddField("PublicKey", BitStruct::RAW, m_certBits.GetInt("PublicKeyLen") * 8);

        int signedPartLen = (m_certBits.GetCurrentFieldOffset() + 7) >> 3;

		if (len <= signedPartLen)
			return;

        m_certBits.AddField("SignatureSizeBits", BitStruct::INTEGER, 16);

        int signatureBitLen = m_certBits.GetInt("SignatureSizeBits");
        int signatureLen = (signatureBitLen + 7) >> 3;

        int signatureOffset = m_certBits.GetCurrentFieldOffset() >> 3;

        m_certBits.AddField("Signature", BitStruct::RAW, signatureLen << 3);
	}

	static ECC::KEY_TYPE GetKeyType(int sigSize)
	{
		if (sigSize < 76 || sigSize > 322)
			throw new LicensingException(STATUS_INVALID_SIGNATURE_SIZE);

		ECC::KEY_TYPE keyType;
		int keySize;

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

		return keyType;
	}

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

	BitStruct m_certBits;
};

#endif
