#pragma once

#include "bitstream.h"
#include "base32.h"
#include "base64.h"

#include <map>

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

using namespace std;

class KeyGeneratorImpl {
public:
	KeyGeneratorImpl():
		m_signingServiceStatusCode(-1)
	{

	}

	KeyGeneratorImpl(const LicenseTemplateImpl * templ):
		KeyGeneratorImpl()
	{
		SetKeyTemplate(templ);
	}

	void SetKeyTemplate(const LicenseTemplateImpl * templ)
	{
		m_keyTemplate = templ;

		m_keyData.Create(m_keyTemplate->m_numGroups * m_keyTemplate->m_charsPerGroup * m_keyTemplate->m_keyEncoding);
		m_keyData.GetBitStream().Clear();

		for (const auto& field : m_keyTemplate->m_dataFields)
			m_keyData.AddField(field.first.c_str(), field.second.type, field.second.size, field.second.offset);

		if (m_keyTemplate->m_validationDataSize)
		{
			m_validationData.Create(m_keyTemplate->m_validationDataSize);
			m_validationData.GetBitStream().Clear();

			for (const auto& field : m_keyTemplate->m_validationFields)
				m_validationData.AddField(field.first.c_str(), field.second.type, field.second.size, field.second.offset);
		}
	}

	void SetKeyData(const char * fieldName, const void * buf, int len)
	{
		m_keyData.Set(fieldName, buf, len);
	}

	void SetKeyData(const char * fieldName, const char * data)
	{
		m_keyData.Set(fieldName, data);
	}

	void SetKeyData(const char * fieldName, int data)
	{
		m_keyData.Set(fieldName, data);
	}

	void SetKeyData(const char * fieldName, int year, int month, int day)
	{
		m_keyData.Set(fieldName, year, month, day);
	}

	void SetValidationData(const char * fieldName, const void * buf, int len)
	{
		m_validationData.Set(fieldName, buf, len);
	}

	void SetValidationData(const char * fieldName, const char * data)
	{
		m_validationData.Set(fieldName, data);
	}

	void SetValidationData(const char * fieldName, int data)
	{
		m_validationData.Set(fieldName, data);
	}

#ifndef _WIN32
	static size_t
	CurlWriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
	{
		KeyGeneratorImpl * pthis = (KeyGeneratorImpl *)userp;
		size_t realsize = size * nmemb;

		if (realsize > 1024 || pthis->m_keyBuffer.length() > 1024)
			return realsize - 1; // signal error

		if (realsize > 0)
			pthis->m_keyBuffer.append((char *)contents, realsize);

		return realsize;
	}
#endif

	const char * GenerateKey()
	{
		BitStream signedData;
		BitStream signature;
		ECC::Signer signer;
		int sigLen, sigLenBits;

#ifndef LICENSING_NO_NETWORKING
		if (m_keyTemplate->m_signingKey == NULL)
		{
			

			m_signingServiceStatusCode = -1;

			string activationQuery("Activate.ashx?");

			//if (activationQuery[m_keyTemplate->m_signingServiceUrl.length() - 1] != '/')
			//	activationQuery.append(1, '/');
			//activationQuery.append("Activate.ashx?");

			unsigned char * buf = (unsigned char *)_alloca(1 + ((m_validationData.GetSize() + 7) >> 3));

			void * enumHandle = NULL;
			const char * name;
			BitStruct::FIELD_TYPE type;
			int size, offset;

			while (m_keyTemplate->EnumValidationFields(&enumHandle, &name, (int*)&type, &size, &offset))
			{
				m_validationData.GetBitStream().Seek(offset);
				m_validationData.GetBitStream().Read(buf, size);
				
				activationQuery.append(name);
				activationQuery.append("=");

				if ((int)type == FIELD_TYPE_STRING)
				{
					buf[(size + 7) >> 3] = '\0';
					activationQuery.append((char *)buf);
				} else
				{
					BASE64 base64;

					string base64Value = base64.encode(buf, (size + 7) >> 3, true);

					replace_all(base64Value, "+", "%2B");
					replace_all(base64Value, "/", "%2F");
					replace_all(base64Value, "=", "%3D");

					activationQuery.append(base64Value.c_str());
				}

				activationQuery.append("&");
			}

			if (m_keyTemplate->m_licensingServiceParameters.length() > 0)
			{
				activationQuery.append(m_keyTemplate->m_licensingServiceParameters);
				activationQuery.append("&");
			}

			activationQuery.erase(activationQuery.length() - 1, 1);

			m_keyBuffer.clear();

#ifdef _WIN32
			URL_COMPONENTS urlComponents;

			ZeroMemory(&urlComponents, sizeof(urlComponents));
			urlComponents.dwStructSize = sizeof(urlComponents);

			urlComponents.dwHostNameLength  = (DWORD)-1;
			urlComponents.dwUrlPathLength = (DWORD)-1;

			auto licensingServiceUrl = s2w(m_keyTemplate->m_licensingServiceUrl);

			if (!WinHttpCrackUrl(licensingServiceUrl.c_str(), m_keyTemplate->m_licensingServiceUrl.length(), 0L, &urlComponents))
				throw new LicensingException(STATUS_NET_ERROR, _T("networking error (1)"));

			std::wstring hostName(urlComponents.lpszHostName, urlComponents.dwHostNameLength);

			unique_ptr<VOID, decltype(&WinHttpCloseHandle)> internet_ptr(WinHttpOpen(L"ANSCENTER Licensing SDK 2.0", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0L), WinHttpCloseHandle);

			if (!internet_ptr)
				throw new LicensingException(STATUS_NET_ERROR, _T("networking error (2)"));

			unique_ptr<VOID, decltype(&WinHttpCloseHandle)> connection_ptr(WinHttpConnect(internet_ptr.get(), hostName.c_str(), urlComponents.nPort, 0L), WinHttpCloseHandle);
			if (!connection_ptr)
				throw new LicensingException(STATUS_NET_ERROR, _T("networking error (3)"));

			activationQuery.insert(0, w2s(urlComponents.lpszUrlPath, urlComponents.dwUrlPathLength));

			DWORD requestFlags = WINHTTP_FLAG_BYPASS_PROXY_CACHE;
			if (urlComponents.nScheme == INTERNET_SCHEME_HTTPS)
				requestFlags |= WINHTTP_FLAG_SECURE;

			unique_ptr<VOID, decltype(&WinHttpCloseHandle)> request_ptr(WinHttpOpenRequest(connection_ptr.get(), L"GET", s2w(activationQuery).c_str(), NULL, NULL, NULL, requestFlags), WinHttpCloseHandle);
			if (!request_ptr)
				throw new LicensingException(STATUS_NET_ERROR, _T("networking error (4)"));

			if (!WinHttpSendRequest(request_ptr.get(), NULL, 0L, NULL, 0L, 0L, 0L))
			{
				DWORD result = GetLastError();
				throw new LicensingException(STATUS_NET_ERROR, _T("networking error (5)"));
			}

			if (!WinHttpReceiveResponse(request_ptr.get(), NULL))
				throw new LicensingException(STATUS_NET_ERROR, _T("networking error (6)"));

			DWORD status;
			DWORD statusSize = sizeof(DWORD);

			if (!WinHttpQueryHeaders(request_ptr.get(), WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &status, &statusSize, WINHTTP_NO_HEADER_INDEX))
				throw new LicensingException(STATUS_NET_ERROR, _T("networking error (7)"));

			m_signingServiceStatusCode = status;

			if (status != HTTP_STATUS_OK && status != HTTP_STATUS_ACCEPTED && status != HTTP_STATUS_CREATED)
			{
				WCHAR buffer[1024];
				DWORD bufferSize = 1024;

				if (!WinHttpQueryHeaders(request_ptr.get(), WINHTTP_QUERY_STATUS_TEXT, WINHTTP_HEADER_NAME_BY_INDEX, buffer, &bufferSize, WINHTTP_NO_HEADER_INDEX))
					throw new LicensingException(STATUS_NET_ERROR, "networking error (8)");

				throw new LicensingException(STATUS_NET_ERROR, w2s(buffer).c_str());
			}

			DWORD dataSize, count;
			char buffer[0x100];

			do {
				if (!WinHttpQueryDataAvailable(request_ptr.get(), &dataSize))
					break;

				if (size > 0x100)
					size = 0x100;

				if (WinHttpReadData(request_ptr.get(), buffer, dataSize, &count))
					m_keyBuffer.append(buffer, count);

			} while (dataSize > 0);
#else
			CURL *curl;
			CURLcode res;
 
			if ((curl = curl_easy_init()) == NULL)
				throw new LicensingException(STATUS_NET_ERROR, "cURL initalization failed");

			if (*(m_keyTemplate->m_licensingServiceUrl.rbegin()) != '/')
				activationQuery.insert(0, "/");

			activationQuery.insert(0, m_keyTemplate->m_licensingServiceUrl);

			curl_easy_setopt(curl, CURLOPT_URL, activationQuery.c_str());
			
			curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
 
			curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
 
			curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);

			res = curl_easy_perform(curl);
			
			if(res != CURLE_OK)
			  throw new LicensingException(STATUS_NET_ERROR, curl_easy_strerror(res));
 
			curl_easy_cleanup(curl);
#endif

			m_key = m_keyBuffer.c_str();

			return m_key.c_str();
		}
#else
		if (m_keyTemplate->m_signingKey == NULL)
		{
			throw new LicensingException(STATUS_NET_ERROR, "private key not provided");
		}
#endif

		// signed data is license key data followed by validation data
		signedData.Create(m_keyTemplate->m_dataSize + m_keyTemplate->m_validationDataSize);

		if (m_keyTemplate->m_dataSize)
			signedData.Write(m_keyData.GetBuffer(), m_keyTemplate->m_dataSize);

		if (m_keyTemplate->m_validationDataSize)
			signedData.Write(m_validationData.GetBuffer(), m_keyTemplate->m_validationDataSize);
		
		signedData.ZeroPadToNextByte();

		// create a bit stream to hold the signature
		signature.Create(m_keyTemplate->m_signatureSize);

		// sign data

		// we use a different algorithm than ECDSA when the signature size must be smaller than twice the key size
		if (m_keyTemplate->m_signatureSize < (m_keyTemplate->m_signatureKeySize << 1))
			signer.SetHashSize(m_keyTemplate->m_signatureSize - m_keyTemplate->m_signatureKeySize);
		else
			signer.SetHashSize(0);

		signer.SetPrivateKey(m_keyTemplate->m_signingKey.get());

		signer.Sign(signedData.GetBuffer(), (signedData.GetSize() + 7) >> 3, 
					signature.GetBuffer(), &sigLen, &sigLenBits);

		signature.ReleaseBuffer(sigLenBits);

		// write the signature at the end of key data
		m_keyData.GetBitStream().Seek(m_keyTemplate->m_dataSize);
		m_keyData.GetBitStream().Write(signature.GetBuffer(), m_keyTemplate->m_signatureSize);

		// text-encode the license key
		EncodeKey();

		// success
		return m_key.c_str();
	}

	int GetSigningServiceStatusCode()
	{
		return m_signingServiceStatusCode;
	}

	void EncodeKey()
	{
		
		string buf;

		// set all bits to 0 until the next byte boundary
		//m_rawKey.ZeroPadToNextByte();

		// reverse last byte from the stream, 
		// so that we will not lose significant bits in the padless, truncated BAS32/64 encoding
		unsigned char * lastBytePtr = (unsigned char *)m_keyData.GetBuffer() + ((m_keyData.GetSize() + 7) >> 3) - 1;
		*lastBytePtr = (unsigned char)(((*lastBytePtr * 0x0802LU & 0x22110LU) | (*lastBytePtr * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16);
		
		switch (m_keyTemplate->m_keyEncoding)
		{
		case ENCODING_BASE32X:
			{
				BASE32 base32;
				buf = base32.encode((unsigned char *)m_keyData.GetBuffer(), (m_keyData.GetSize() + 7) >> 3, true);
			}
			break;

		case ENCODING_BASE64X:
			{
				BASE64 base64;
				buf = base64.encode((unsigned char *)m_keyData.GetBuffer(), (m_keyData.GetSize() + 7) >> 3, false);
			}
			break;

		default:
			throw new LicensingException(STATUS_INVALID_KEY_ENCODING);
		}

		if (buf.empty())
			throw new LicensingException(STATUS_OUT_OF_MEMORY);

		m_key = buf.c_str();

		if ((int)m_key.length() > m_keyTemplate->m_numGroups * m_keyTemplate->m_charsPerGroup)
			m_key.resize(m_keyTemplate->m_numGroups * m_keyTemplate->m_charsPerGroup);

		int i;
		int insertPos;
		// separate character groups
		for (i = 0, insertPos = m_keyTemplate->m_charsPerGroup; i < m_keyTemplate->m_numGroups - 1; i++)
		{
			m_key.insert(insertPos, m_keyTemplate->m_groupSeparator);
			insertPos += (unsigned)( m_keyTemplate->m_groupSeparator.length() + m_keyTemplate->m_charsPerGroup );
		}

		// add header and footer (if any)
		if (!m_keyTemplate->m_header.empty())
			m_key.insert(0, m_keyTemplate->m_header + "\r\n");

		if (!m_keyTemplate->m_footer.empty())
			m_key.append("\r\n" + m_keyTemplate->m_footer);
	}

	std::size_t replace_all(std::string& inout, std::string_view what, std::string_view with)
	{
		std::size_t count{};

		for (std::string::size_type pos{};
			inout.npos != (pos = inout.find(what.data(), pos, what.length()));
			pos += with.length(), ++count) {
			inout.replace(pos, what.length(), with.data(), with.length());
		}

		return count;
	}

	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* m_keyTemplate;

	string m_keyBuffer;
	string m_key;

	BitStruct m_keyData;
	BitStruct m_validationData;
	
	int m_signingServiceStatusCode;
};
