#include <std_include.hpp>
#include "hashes.hpp"
#include "gsc_funcs.hpp"

#include "definitions/variables.hpp"
#include "loader/component_loader.hpp"
#include <utilities/json_config.hpp>

namespace hashes
{
	namespace
	{
		bool enabled = true;
		std::unordered_map<uint64_t, std::string>& hash_storage()
		{
			static std::unordered_map<uint64_t, std::string> storage{};
			return storage;
		}



		const char* hff_names[]
		{
			"COMMON",
			"STRING",
			"SERIOUS_COMPILER"
		};

		bool is_valid_h32(const char* str)
		{
			const char* s = str;

			while (*s)
			{
				char c = *(s++);

				if (!(
					(c >= 'A' && c <= 'Z')
					|| (c >= 'a' && c <= 'z')
					|| (c >= '0' && c <= '9')
					|| c == '_'))
				{
					return false;
				}
			}
			return true; // [A-Za-z0-9_]+
		}
	}

	const char* lookup(uint64_t hash)
	{
		auto& hs = hash_storage();
		auto it = hs.find(hash);
		if (it == hs.end())
		{
			return nullptr;
		}
		return it->second.c_str();
	}

	const char* lookup_tmp(const char* type, uint64_t hash)
	{
		static char tmp_buffer[0x50];

		const char* val = lookup(hash);

		if (val)
		{
			return val;
		}

		sprintf_s(tmp_buffer, "%s_%llx", type, hash);

		return tmp_buffer;
	}

	void add_hash(uint64_t hash, const char* value)
	{
		if (!enabled)
		{
			return;
		}
		hash_storage()[hash] = value;
	}

	const char* get_format_name(hashes_file_format format)
	{
		if (format >= 0 && format < HFF_COUNT)
		{
			return hff_names[format];
		}
		return "<invalid>";
	}

	hashes_file_format get_format_idx(const char* name)
	{
		for (size_t i = 0; i < HFF_COUNT; i++)
		{
			if (!_strcmpi(name, hff_names[i]))
			{
				return (hashes_file_format)i;
			}
		}
		return HFF_COUNT;
	}

	bool load_file(std::filesystem::path& file, hashes_file_format format)
	{
		if (!enabled)
		{
			return true;
		}

		logger::write(logger::LOG_TYPE_DEBUG, std::format("loading hash file {}", file.string()));
		switch (format)
		{
		case HFF_STRING:
		{
			// basic format, each line is a string, allows fast updates

			std::ifstream s(file);

			if (!s)
			{
				logger::write(logger::LOG_TYPE_ERROR, std::format("can't read hash file {}", file.string()));
				return false; // nothing to read
			}

			std::string line;
			while (s.good() && std::getline(s, line))
			{
				const char* str = line.c_str();

				if (is_valid_h32(str))
				{
					add_hash(gsc_funcs::canon_hash(str), str);
				}

				add_hash(fnv1a::generate_hash(str), str);
			}

			s.close();
			return true;
		}
		case HFF_COMMON:
		{
			// common precomputed format used by greyhound index and other tools
			// each line: <hash>,<string>

			std::ifstream s(file);

			if (!s)
			{
				logger::write(logger::LOG_TYPE_ERROR, std::format("can't read hash file {}", file.string()));
				return false; // nothing to read
			}

			std::string line;
			while (s.good() && std::getline(s, line))
			{
				auto idx = line.rfind(", ", 18);

				if (idx == std::string::npos)
				{
					continue; // bad line
				}
				
				char value[18] = {};
				const char* str = line.c_str();

				memcpy(value, str, idx);

				add_hash(std::strtoull(value, nullptr, 16), str + idx + 1);
			}

			s.close();
			return false;
		}
		case HFF_SERIOUS_COMPILER:
		{
			// precomputed format generated by the serious compiler
			// each line: 0x<hash>, <string>

			std::ifstream s(file);

			if (!s)
			{
				logger::write(logger::LOG_TYPE_ERROR, std::format("can't read hash file {}", file.string()));
				return false; // nothing to read
			}

			std::string line;
			while (s.good() && std::getline(s, line))
			{
				if (!line.starts_with("0x"))
				{
					continue; // bad line
				}

				auto idx = line.rfind(", ", 18);

				if (idx == std::string::npos)
				{
					continue; // bad line
				}

				char value[18] = {};
				const char* str = line.c_str();

				memcpy(value, str + 2, idx - 2);

				add_hash(std::strtoull(value, nullptr, 16), str + idx + 2);
			}

			s.close();

			return true;
		}
		default:
			logger::write(logger::LOG_TYPE_ERROR, std::format("bad format type {} for file {}", (int)format, file.string()));
			return false;
		}
	}


	class component final : public component_interface
	{
	public:
		void pre_start() override
		{
			enabled = utilities::json_config::ReadBoolean("gsc", "hashes", true);

			if (!enabled)
			{
				return;
			}

			// load default files
			std::filesystem::path default_file_name_str = "project-bo4/strings.txt";

			if (std::filesystem::exists(default_file_name_str))
			{
				load_file(default_file_name_str, HFF_STRING);
			}

			std::filesystem::path default_file_name_common = "project-bo4/hashes.csv";

			if (std::filesystem::exists(default_file_name_common))
			{
				load_file(default_file_name_common, HFF_COMMON);
			}
		}
	};
}

REGISTER_COMPONENT(hashes::component)