#include "Passphrase.h"

#include "Poco/Types.h"
#include "Poco/Tuple.h"


#include "../SingletonManager/ErrorManager.h"

#include "KeyPairEd25519.h"

#include "../ServerConfig.h"

#include "../lib/DataTypeConverter.h"

#define STR_BUFFER_SIZE 25

static std::vector<Poco::Tuple<int, std::string>> g_specialChars = {
	{ 0xa4, "auml" },{ 0x84, "Auml" },
	{ 0xbc, "uuml" },{ 0x9c, "Uuml" },
	{ 0xb6, "ouml" },{ 0x96, "Ouml" },
	{ 0x9f, "szlig" }
};

Passphrase::Passphrase(const std::string& passphrase, const Mnemonic* wordSource)
	: mPassphraseString(filter(passphrase)), mWordSource(wordSource)
{
	memset(mWordIndices, 0, PHRASE_WORD_COUNT * sizeof(Poco::UInt16));
	getWordIndices();
}


std::string Passphrase::filter(const std::string& passphrase)
{
	std::string filteredPassphrase;
	auto passphrase_size = passphrase.size();

	for (int i = 0; i < passphrase_size; i++) {
		unsigned char c = passphrase.data()[i];
		// asci 128 even by utf8 (hex)
		// 0000 0000 – 0000 007F
		// utf8
		if (c > 0x0000007F) {
			int additionalUtfByteCount = 0;
			//filteredPassphrase += c;
			if ((c & 0x00000080) == 0x00000080) {
				// c3 a4 => ä
				// c3 bc => ü
				// c3 b6 => ö
				// c3 84 => Ä
				// c3 96 => Ö
				// c3 9c => Ü
				// c3 9f => ß



				unsigned char c2 = passphrase.data()[i + 1];
				bool insertedHtmlEntitie = false;
				for (auto it = g_specialChars.begin(); it != g_specialChars.end(); it++) {
					if (c2 == it->get<0>()) {
						auto htmlEntitie = it->get<1>();
						filteredPassphrase += "&";
						filteredPassphrase += htmlEntitie;
						filteredPassphrase += ";";
						i++;
						insertedHtmlEntitie = true;
						break;
					}
				}
				if (insertedHtmlEntitie) continue;
				additionalUtfByteCount = 1;
			}
			else if ((c & 0x00000800) == 0x00000800) {
				additionalUtfByteCount = 2;
			}
			else if ((c & 0x00010000) == 0x00010000) {
				additionalUtfByteCount = 3;
			}
			for (int j = 0; j <= additionalUtfByteCount; j++) {
				filteredPassphrase += passphrase.data()[i + j];
				i++;
			}
		}
		else {
			// 32 = Space
			// 65 = A
			// 90 = Z
			// 97 = a
			// 122 = z
			// 59 = ;
			// 38 = &
			if (c == 32 || c == 59 || c == 38 ||
				(c >= 65 && c <= 90) ||
				(c >= 97 && c <= 122)) {
				filteredPassphrase += c;
			}
			else if (c == '\n' || c == '\r') {
				filteredPassphrase += ' ';
			}
		}

	}
	return filteredPassphrase;
}

Poco::AutoPtr<Passphrase> Passphrase::transform(const Mnemonic* targetWordSource)
{

	/*
	if (!currentWordSource || !targetWordSource) {
		return "";
	}
	if (targetWordSource == currentWordSource) {
		return passphrase;
	}
	auto word_indices = createWordIndices(passphrase, currentWordSource);
	if (!word_indices) {
		return "";
	}

	return createClearPassphraseFromWordIndices(word_indices, targetWordSource);*/
//	Poco::SharedPtr<Passphrase> transformedPassphrase = new Passphrase()

	if (!targetWordSource || !mWordSource) {
		return nullptr;
	}
	if (targetWordSource == mWordSource) {
		duplicate();
		return this;
	}
	if (createWordIndices()) {
		return create(mWordIndices, targetWordSource);
	}
	return nullptr;
}

Poco::AutoPtr<Passphrase> Passphrase::create(const std::string& passphrase, const Mnemonic* wordSource)
{
	return new Passphrase(passphrase, wordSource);
}

Poco::AutoPtr<Passphrase> Passphrase::create(const MemoryBin* wordIndices, const Mnemonic* wordSource)
{
	if (PHRASE_WORD_COUNT * sizeof(Poco::UInt16) >= wordIndices->size()) {
		return nullptr;
	}

	const Poco::UInt16* word_indices_p = (const Poco::UInt16*)wordIndices->data();
	return create(word_indices_p, wordSource);
}

std::string Passphrase::createClearPassphrase() const
{
	auto word_indices = getWordIndices();
	std::string clear_passphrase;
	auto word_source = &ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER];
	for (int i = 0; i < PHRASE_WORD_COUNT; i++) {
		auto word = word_source->getWord(word_indices[i]);
		if (word) {
			clear_passphrase += word;
			clear_passphrase += " ";
		}
	}
	return clear_passphrase;
}

Poco::AutoPtr<Passphrase> Passphrase::create(const Poco::UInt16 wordIndices[PHRASE_WORD_COUNT], const Mnemonic* wordSource)
{
	std::string clearPassphrase;
	for (int i = 0; i < PHRASE_WORD_COUNT; i++) {
		auto word = wordSource->getWord(wordIndices[i]);
		if (word) {
			clearPassphrase += word;
			clearPassphrase += " ";
		}
		else {
			return nullptr;
		}
	}
	return new Passphrase(clearPassphrase, wordSource);
}

Poco::AutoPtr<Passphrase> Passphrase::generate(const Mnemonic* wordSource)
{
	auto em = ErrorManager::getInstance();
	auto mm = MemoryManager::getInstance();
	auto word_indices = mm->getFreeMemory(PHRASE_WORD_COUNT * sizeof(Poco::UInt16));
	Poco::UInt16* word_indices_p = (Poco::UInt16*)word_indices->data();

	for (int i = 0; i < PHRASE_WORD_COUNT; i++) {
		word_indices_p[i] = randombytes_random() % 2048;
	}
	auto result_passphrase = create(word_indices_p, wordSource);
	mm->releaseMemory(word_indices);

	return result_passphrase;
	/*

	unsigned int random_indices[PHRASE_WORD_COUNT];

	unsigned int str_sizes[PHRASE_WORD_COUNT];
	unsigned int phrase_buffer_size = 0;
	static const char* function_name = "Passphrase::generate";
	bool error_reloading_mnemonic_word_list = false;
	int loop_trys = 0;
	Poco::RegularExpression check_valid_word("^[a-zA-ZÄÖÜäöüß&;]*$");

	// TODO: make sure words didn't double
	for (int i = 0; i < PHRASE_WORD_COUNT; i++) {
		random_indices[i] = randombytes_random() % 2048;
		auto word = wordSource->getWord(random_indices[i]);
		if (loop_trys > 10 || error_reloading_mnemonic_word_list) {
			return nullptr;
		}
		if (!word) {
			em->addError(new ParamError(function_name, "empty word get for index", random_indices[i]));
			em->sendErrorsAsEmail();

			random_indices[i] = randombytes_random() % 2048;
			word = wordSource->getWord(random_indices[i]);
			if (!word) return nullptr;

		}
		else {
			if (!check_valid_word.match(word, 0, Poco::RegularExpression::RE_NOTEMPTY)) {
				em->addError(new ParamError(function_name, "invalid word", word));
				em->addError(new Error(function_name, "try to reload mnemonic word list, but this error is maybe evidence for a serious memory problem!!!"));
				if (!ServerConfig::loadMnemonicWordLists()) {
					em->addError(new Error(function_name, "error reloading mnemonic word lists"));
					error_reloading_mnemonic_word_list = true;
				}
				else {
					i = 0;
					loop_trys++;
				}
				em->sendErrorsAsEmail();
			}
		}
		str_sizes[i] = strlen(word);
		phrase_buffer_size += str_sizes[i];
	}
	phrase_buffer_size += PHRASE_WORD_COUNT + 1;

	std::string phrase_buffer(phrase_buffer_size, '\0');
	int phrase_buffer_cursor = 0;

	for (int i = 0; i < PHRASE_WORD_COUNT; i++) {
		memcpy(&phrase_buffer[phrase_buffer_cursor], wordSource->getWord(random_indices[i]), str_sizes[i]);

		phrase_buffer_cursor += str_sizes[i];
		phrase_buffer[phrase_buffer_cursor++] = ' ';
	}


	return create(;
	*/
}

bool Passphrase::createWordIndices()
{
	auto er = ErrorManager::getInstance();
	auto mm = MemoryManager::getInstance();
	const char* functionName = "Passphrase::createWordIndices";

	if (!mWordSource) {
		er->addError(new Error(functionName, "word source is empty"));
		return false;
	}


	//DHASH key = DRMakeStringHash(passphrase);
	size_t pass_phrase_size = mPassphraseString.size();

	char acBuffer[STR_BUFFER_SIZE]; memset(acBuffer, 0, STR_BUFFER_SIZE);
	size_t buffer_cursor = 0;

	// get word indices for hmac key
	unsigned char word_cursor = 0;
	for (auto it = mPassphraseString.begin(); it != mPassphraseString.end(); it++)
	{
		if (*it == ' ') {
			if (buffer_cursor < 3) {
				continue;
			}
			if (PHRASE_WORD_COUNT > word_cursor && mWordSource->isWordExist(acBuffer)) {
				mWordIndices[word_cursor] = mWordSource->getWordIndex(acBuffer);
				//word_indices_old[word_cursor] = word_source->getWordIndex(acBuffer);
			}
			else {
				er->addError(new ParamError(functionName, "word didn't exist", acBuffer));
				er->sendErrorsAsEmail();
				return false;
			}
			word_cursor++;
			memset(acBuffer, 0, STR_BUFFER_SIZE);
			buffer_cursor = 0;

		}
		else {
			acBuffer[buffer_cursor++] = *it;
		}
	}
	if (PHRASE_WORD_COUNT > word_cursor && mWordSource->isWordExist(acBuffer)) {
		mWordIndices[word_cursor] = mWordSource->getWordIndex(acBuffer);
		//word_indices_old[word_cursor] = word_source->getWordIndex(acBuffer);
		word_cursor++;
	}
	//printf("word cursor: %d\n", word_cursor);
	/*if (memcmp(word_indices_p, word_indices_old, word_indices->size()) != 0) {

	printf("not identical\n");
	memcpy(word_indices_p, word_indices_old, word_indices->size());
	}*/
	return true;
}

const Poco::UInt16* Passphrase::getWordIndices()
{
	if (!(mWordIndices[0] | mWordIndices[1] | mWordIndices[2] | mWordIndices[3])) {
		if (createWordIndices()) return mWordIndices;
	}
	return mWordIndices;
}

const Poco::UInt16* Passphrase::getWordIndices() const
{
	return mWordIndices;
}

bool Passphrase::checkIfValid()
{
	std::istringstream iss(mPassphraseString);
	std::vector<std::string> results(std::istream_iterator<std::string>{iss},
	std::istream_iterator<std::string>());

	bool existAll = true;
	for (auto it = results.begin(); it != results.end(); it++) {
		if (*it == "\0" || *it == "" || it->size() < 3) continue;
		if (!mWordSource->isWordExist(*it)) {
			return false;
		}
	}
	return true;
}
const Mnemonic* Passphrase::detectMnemonic(const std::string& passphrase, const KeyPairEd25519* keyPair /* = nullptr*/)
{
	std::istringstream iss(passphrase);
	std::vector<std::string> results(std::istream_iterator<std::string>{iss},
		std::istream_iterator<std::string>());


	std::string user_public_key_hex;

	if (keyPair) {
		user_public_key_hex = DataTypeConverter::pubkeyToHex(keyPair->getPublicKey());
		//printf("user public key hex: %s\n", user_public_key_hex.data());
	}
    std::string last_words_not_found[ServerConfig::Mnemonic_Types::MNEMONIC_MAX];
	for (int i = 0; i < ServerConfig::Mnemonic_Types::MNEMONIC_MAX; i++) {
		Mnemonic& m = ServerConfig::g_Mnemonic_WordLists[i];
		bool existAll = true;
		for (auto it = results.begin(); it != results.end(); it++) {
			if (*it == "\0" || *it == "" || it->size() < 3) continue;
			if (!m.isWordExist(*it)) {
				existAll = false;
				//printf("couldn't find word: %s\n", (*it).data());
				last_words_not_found[i] = (*it);
				// leave inner for-loop
				break;
			}
		}
		if (existAll) {
			if (keyPair) {
				Poco::AutoPtr<Passphrase> test_passphrase = new Passphrase(passphrase, &m);
				test_passphrase->createWordIndices();
				auto key_pair = KeyPairEd25519::create(test_passphrase);
				if (key_pair) {

					if (*key_pair != *keyPair) {
#ifdef _TEST_BUILD // additional infos for debugging if error occure in test
						printf("public key mismatch\n");
						std::string generated_key_pair_hex = DataTypeConverter::pubkeyToHex(key_pair->getPublicKey());
						std::string parameter_key_pair_hex = DataTypeConverter::pubkeyToHex(keyPair->getPublicKey());
						printf("parameter: %s, generated: %s\n", parameter_key_pair_hex.data(), generated_key_pair_hex.data());
#endif
						delete key_pair;
						continue;
					}
					else {
						delete key_pair;
					}
				}
			}
			return &ServerConfig::g_Mnemonic_WordLists[i];
		}
		//printf("last word not found: %s in %s\n", last_words_not_found[i].data(), ServerConfig::mnemonicTypeToString((ServerConfig::Mnemonic_Types)i));
	}
	
	return nullptr;
}
