#include <stdinclude.hpp>

#include <minizip/unzip.h>
#include <TlHelp32.h>

#include <unordered_set>
#include <charconv>
#include <cassert>
#include <format>
#include <cpprest/uri.h>
#include <cpprest/http_listener.h>
#include <ranges>
#include <windows.h>
#include "windowsPlatform.hpp"

extern void start_console();

const auto CONSOLE_TITLE = L"Gakumas Localify";

std::filesystem::path gakumasLocalPath = "./gakumas-local";
std::filesystem::path ProgramConfigJson = gakumasLocalPath / "config.json";
std::filesystem::path ConfigJson = gakumasLocalPath / "localizationConfig.json";

bool g_has_config_file = false;
bool g_enable_console = true;
bool g_useRemoteAssets = false;
bool g_useAPIAssets = false;
std::string g_remoteResourceUrl = "";
std::string g_useAPIAssetsURL = "";

namespace
{
	void create_debug_console()
	{
		AllocConsole();

		// open stdout stream
		auto _ = freopen("CONOUT$", "w+t", stdout);
		_ = freopen("CONOUT$", "w", stderr);
		_ = freopen("CONIN$", "r", stdin);

		SetConsoleTitleW(CONSOLE_TITLE);

		// set this to avoid turn japanese texts into question mark
		SetConsoleOutputCP(65001);
		std::locale::global(std::locale(""));

		wprintf(L"%ls Loaded! - By chinosk\n", CONSOLE_TITLE);
	}
}

void readProgramConfig() {
	std::vector<std::string> dicts{};
	std::ifstream config_stream{ ProgramConfigJson };

	if (!config_stream.is_open())
		return;

	rapidjson::IStreamWrapper wrapper{ config_stream };
	rapidjson::Document document;

	document.ParseStream(wrapper);

	if (!document.HasParseError())
	{
		g_has_config_file = true;
		if (document.HasMember("enableConsole")) {
			g_enable_console = document["enableConsole"].GetBool();
		}

		if (document.HasMember("useRemoteAssets")) {
			g_useRemoteAssets = document["useRemoteAssets"].GetBool();
		}

		if (document.HasMember("transRemoteZipUrl")) {
			g_remoteResourceUrl = document["transRemoteZipUrl"].GetString();
		}

		if (document.HasMember("useAPIAssets")) {
			g_useAPIAssets = document["useAPIAssets"].GetBool();
		}

		if (document.HasMember("useAPIAssetsURL")) {
			g_useAPIAssetsURL = document["useAPIAssetsURL"].GetString();
		}

	}
	config_stream.close();
}

LONG WINAPI AddressExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo) {
	// Check if the exception is an access violation
	if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
		// Check if the violation address is above 0x7FFFFFFFFFFF
		if (ExceptionInfo->ExceptionRecord->ExceptionAddress > (PVOID)0x7FFFFFFFFFFF) {
			// Return EXCEPTION_CONTINUE_EXECUTION to ignore the exception and continue
			return EXCEPTION_CONTINUE_EXECUTION;
		}
	}
	// For other exceptions or addresses, use the default exception handler
	return EXCEPTION_CONTINUE_SEARCH;
}


int __stdcall DllMain(HINSTANCE dllModule, DWORD reason, LPVOID)
{
	if (reason == DLL_PROCESS_ATTACH)
	{
		AddVectoredExceptionHandler(1, AddressExceptionHandler);
		// the DMM Launcher set start path to system32 wtf????
		std::string module_name;
		module_name.resize(MAX_PATH);
		module_name.resize(GetModuleFileName(nullptr, module_name.data(), MAX_PATH));

		std::filesystem::path module_path(module_name);

		// check name
		if (module_path.filename() != "gakumas.exe")
			return 1;

		std::filesystem::current_path(
			module_path.parent_path()
		);

		readProgramConfig();

		if (g_enable_console)
			create_debug_console();

		std::thread init_thread([] {

			if (g_enable_console)
			{
				start_console();
				printf("Command: %s\n", GetCommandLineA());
			}

			loadConfig(ConfigJson);
			initHook();

			std::mutex mutex;
			std::condition_variable cond;
			std::atomic<bool> hookIsReady(false);

			// 依赖检查游戏版本的指针加载，因此在 hook 完成后再加载翻译数据
			std::unique_lock lock(mutex);
			cond.wait(lock, [&] {
				return hookIsReady.load(std::memory_order_acquire);
				});
			if (g_enable_console)
			{
				auto _ = freopen("CONOUT$", "w+t", stdout);
				_ = freopen("CONOUT$", "w", stderr);
				_ = freopen("CONIN$", "r", stdin);
			}

			});
		init_thread.detach();
	}
	else if (reason == DLL_PROCESS_DETACH)
	{
		RemoveVectoredExceptionHandler(AddressExceptionHandler);
		unInitHook();
	}
	return 1;
}
