#include "GakumasLocalify/Plugin.h"
#include "GakumasLocalify/Log.h"
#include "GakumasLocalify/Local.h"
#include "GakumasLocalify/camera/camera.hpp"
#include "GakumasLocalify/config/Config.hpp"
#include "GakumasLocalify/Il2cppUtils.hpp"

#include <stdinclude.hpp>
#include "deps/UnityResolve/UnityResolve.hpp"
#include <cpprest/details/basic_types.h>
#include <cpprest/details/http_helpers.h>

bool mh_inited;
extern std::filesystem::path gakumasLocalPath;

void patchGameAssenbly();

namespace
{
    class AndroidHookInstaller : public GakumasLocal::HookInstaller
    {
    public:
        explicit AndroidHookInstaller(const std::string& il2cppLibraryPath, const std::string& localizationFilesDir)
        {
            this->m_Il2CppLibrary = GetModuleHandle("GameAssembly.dll");
            this->m_il2cppLibraryPath = il2cppLibraryPath;
            this->localizationFilesDir = localizationFilesDir;
        }

        ~AndroidHookInstaller() override {
			if (!mh_inited) return;
            MH_DisableHook(MH_ALL_HOOKS);
            MH_Uninitialize();
        }

        void* InstallHook(void* addr, void* hook, void** orig) override
        {
            auto createStat = MH_CreateHook(addr, hook, orig);
            MH_EnableHook(addr);
            return reinterpret_cast<void*>(createStat);
        }

        GakumasLocal::OpaqueFunctionPointer LookupSymbol(const char* name) override
        {
            return reinterpret_cast<GakumasLocal::OpaqueFunctionPointer>(GetProcAddress(reinterpret_cast<HMODULE>(m_Il2CppLibrary), name));
        }

    private:
        void* m_Il2CppLibrary;
    };
}

void* load_library_w_orig;
HMODULE __stdcall load_library_w_hook(const wchar_t* path)
{
    // GakumasLocal::Log::DebugFmt("LoadLibrary %s", utility::conversions::to_utf8string(path).c_str());

    using namespace std;
    if (path == L"cri_ware_unity.dll"sv) {
    // if (path == L"NVUnityPlugin"sv) {
        patchGameAssenbly();
    }
    return reinterpret_cast<decltype(LoadLibraryW)*>(load_library_w_orig)(path);
}

void patchGameAssenbly() {
	static bool patched = false;
	if (patched) return;
	patched = true;

    auto& plugin = GakumasLocal::Plugin::GetInstance();
    plugin.InstallHook(std::make_unique<AndroidHookInstaller>("GameAssembly.dll", gakumasLocalPath.string()));
}

void initHook() {
	mh_inited = true;

    if (MH_Initialize() != MH_OK)
        return;

    auto stat1 = MH_CreateHook(LoadLibraryW, load_library_w_hook, &load_library_w_orig);
    auto stat2 = MH_EnableHook(LoadLibraryW);

    GakumasLocal::Log::InfoFmt("initHook stat: %s - %s\n", MH_StatusToString(stat1), MH_StatusToString(stat2));
}

void unInitHook() {
	if (!mh_inited) return;
    MH_DisableHook(MH_ALL_HOOKS);
    MH_Uninitialize();
}


void loadConfig(const std::string& configJson) {
	GakumasLocal::Config::LoadConfig(configJson);
}

void loadConfig(const std::filesystem::path& filePath) {
    std::ifstream file(filePath);
    if (!file.is_open()) {
        GakumasLocal::Log::ErrorFmt("Load config %s failed.\n", filePath.string().c_str());
        loadConfig(std::string("{}"));
        return;
    }
    std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
    file.close();

    loadConfig(fileContent);
}

bool getCurrentLodingProgress(int* stepTotal, int* stepCurrent, int* currTotal, int* currCurrent) {
    *stepTotal = UnityResolveProgress::assembliesProgress.total;
	*stepCurrent = UnityResolveProgress::assembliesProgress.current;
	*currTotal = UnityResolveProgress::classProgress.total;
	*currCurrent = UnityResolveProgress::classProgress.current;

    return UnityResolveProgress::startInit;
}

namespace GakumasLocal::WinHooks {
    using Il2cppString = UnityResolve::UnityType::String;

	void* LoadAssetBundle(const std::string& path) {
        Il2cppString* bundlePath = Il2cppString::New(path);

        static auto LoadFromFileAsync = Il2cppUtils::GetMethod("UnityEngine.AssetBundleModule.dll", "UnityEngine", "AssetBundle", "LoadFromFileAsync");
        static auto get_assetBundle = Il2cppUtils::GetMethod("UnityEngine.AssetBundleModule.dll", "UnityEngine", "AssetBundleCreateRequest", "get_assetBundle");

        auto bundleTask = LoadFromFileAsync->Invoke<void*>(bundlePath);

		if (!bundleTask) {
			return nullptr;
		}
		return get_assetBundle->Invoke<void*>(bundleTask);
	}

}

// TODO keyboard events