#define NOMINMAX
#define IMGUI_DISABLE_INCLUDE_IMCONFIG_H
#define IMGUI_HAS_DOCK 1

#include "CommonHeaders.h"
#include "HotkeysManager.h"
#include "OSDManager.h"
#include <chrono>
#include <algorithm>    // std::min
// Screen informations
static uint32_t g_width = 0;
static uint32_t g_height = 0;

// Core game dll functions declarations
typedef void (*SetFixesFn)(GameFixes, bool);
typedef void (*SetBoolFn)(bool, bool);
typedef void (*SetFloatFn)(GameSetting, float);

static HMODULE fixLib = nullptr;
static LONG g_coreInitialized = 0;
static LONG g_hotkeysInitialized = 0;
static LONG g_uniformReseted = 0;
static SetBoolFn SetFixEnabled = nullptr;
static SetFixesFn SetFixes = nullptr;
static SetFloatFn SetValues = nullptr;
static GetGameInfosStruct GetGameInfos = nullptr;

void SetFixesEnabled(GameFixes fix, bool value) {
    if (SetFixes) SetFixes(fix, value);
}

// Plugin variables for checkboxes and sliders
static bool skip_Intro_enabled = true;
static bool fov_fix_enabled = false;
static bool DOF_fix_enabled = false;
static bool CA_fix_enabled = false;
static bool Vignetting_fix_enabled = false;
static bool camera_fix_enabled = false;
static bool HUD_fix_enabled = false;
static bool cutscenes_fix_enabled = false;
static bool cutscenes_FPS_fix_enabled = false;
static bool volumetric_fog_fix_enabled = false;
static bool Fog_fix_enabled = false;
static bool fix_enabled = false;
static bool Time_Dilation_fix_enabled = false;
static bool StealthMode_fix_enabled;
static bool GodMode_fix_enabled;
static int worldFOVvalue = 0;
static float cameraDistanceValue = 1.f;
static int HUDvalue = 0;
static float fogDensityValue = 0.1f;
static float fogMaxOpacityValue = 1.f;
static float worldTimeDilationValue = 1.f;
static float AITimeDilationValue = 1.f;
static bool console = true;
static float OSD_duration = 3.f;

// Overlays popups
static bool popup_Informations = false;
static bool show_log_overlay = false;
static std::string log_content;

// Plugin settings
const std::string SETTINGS_FILE = "./pluginsettings.ini";
const char* FIX_VERSION = "1.0.6";
const char* FIX_INFORMATIONS = "This fix allows to:\n - Control FOV in game.\n - Control camera distance.\n - Control HUD scaling.\n - Unlock cutscenes FPS and enable ultrawide.\n - Enable cheats.\n - Disable depth of field.\n - Disable chromatic aberrations.\n - Disable vignetting.\n - Control fog.\n - Re enable console.";
const char* DONATION_URL = "https://buymeacoffee.com/k4sh44";

// Prepare arrays of checkboxes for ImGui
static FixToggle individualFixes[] = {
    { "FOV", &fov_fix_enabled, GameFixes::FOV },
    { "HUD scaling", &HUD_fix_enabled, GameFixes::HUD },
    { "Cutscenes", &cutscenes_fix_enabled, GameFixes::Cutscenes, "Remove black bars"},
    { "Vignetting", &Vignetting_fix_enabled, GameFixes::Vignetting },
    { "Fog", &Fog_fix_enabled, GameFixes::Fog },
    { "Camera", &camera_fix_enabled, GameFixes::Camera },
    { "Cutscenes FPS", &cutscenes_FPS_fix_enabled, GameFixes::Framerate, "Unlock cutscenes up to 240 FPS"},
    { "Depth of field", &DOF_fix_enabled, GameFixes::DOF },
    { "Chromatic aberrations", &CA_fix_enabled, GameFixes::ChromaticAberrations },
    { "Volumetric fog", &volumetric_fog_fix_enabled, GameFixes::VolumetricFog }
};

static FixToggle cheatFixes[] = {
    { "Time dilation", &Time_Dilation_fix_enabled, GameFixes::TimeDilation, "ALT + 1 (top keyboard row) to toggle" },
    { "Stealth mode", &StealthMode_fix_enabled, GameFixes::Stealth, "ALT + 2 (top keyboard row) to toggle.\n"
                                                                    "Stealth may not work when running nearby enemies." },
    { "God mode", &GodMode_fix_enabled, GameFixes::GodMode, "ALT + 3 (top keyboard row) to toggle.\n"
                                                            "Hinako won't loose stamina, sanity and health during fight."},
};
// Prepare array of sliders for ImGui
static SliderFix2 sliders[7];

// Load and unload game core dll functions /!\ necessary
static void LoadFixDLL() {
    if (InterlockedCompareExchange(&g_coreInitialized, 1, 0) != 0) return;

    if (GetModuleHandleA("SilentHillfCore.dll") == nullptr) {
        fixLib = LoadLibraryA("SilentHillfCore.dll");

        if (!fixLib) {
            MessageBoxA(nullptr, "Impossible to load game core dll", "Erreur", MB_OK);
            return;
        }

        SetFixEnabled = (SetBoolFn)GetProcAddress(fixLib, "SetFixEnabled");
        SetFixes = (SetFixesFn)GetProcAddress(fixLib, "SetFixesEnabled");
        SetValues = (SetFloatFn)GetProcAddress(fixLib, "SetValues");
        GetGameInfos = (GetGameInfosStruct)GetProcAddress(fixLib, "GetGameInfos");

        // Apply initial values loaded from settings
        if (SetValues) {
            SetValues(GameSetting::FOV, worldFOVvalue);
            SetValues(GameSetting::CameraDistance, cameraDistanceValue);
            SetValues(GameSetting::FogDensity, fogDensityValue);
            SetValues(GameSetting::FogMaxOpacity, fogMaxOpacityValue);
            SetValues(GameSetting::WorldTimeDilation, worldTimeDilationValue);
            SetValues(GameSetting::AITimeDilation, AITimeDilationValue);
            SetValues(GameSetting::HUD, HUDvalue);
        }
        if (SetFixEnabled) SetFixEnabled(fix_enabled, true);
        if (SetFixes) {
            SetFixes(GameFixes::SkipIntro, skip_Intro_enabled);
            SetFixes(GameFixes::FOV, fov_fix_enabled);
            SetFixes(GameFixes::Camera, camera_fix_enabled);
            SetFixes(GameFixes::HUD, HUD_fix_enabled);
            SetFixes(GameFixes::Cutscenes, cutscenes_fix_enabled);
            SetFixes(GameFixes::Framerate, cutscenes_FPS_fix_enabled);
            SetFixes(GameFixes::DOF, DOF_fix_enabled);
            SetFixes(GameFixes::ChromaticAberrations, CA_fix_enabled);
            SetFixes(GameFixes::Vignetting, Vignetting_fix_enabled);
            SetFixes(GameFixes::VolumetricFog, volumetric_fog_fix_enabled);
            SetFixes(GameFixes::Fog, Fog_fix_enabled);
            SetFixes(GameFixes::TimeDilation, Time_Dilation_fix_enabled);
            SetFixes(GameFixes::Stealth, StealthMode_fix_enabled);
            SetFixes(GameFixes::GodMode, GodMode_fix_enabled);
            SetFixes(GameFixes::DevConsole, console);
        }

        // Declare sliders only when function pointers are initilized  (SetFOV ...)
        sliders[0] = { "In game additional FOV", "##FOVValue", SliderType::Int, &worldFOVvalue, -20, 50, GameSetting::FOV, SetValues };
        sliders[1] = { "Camera distance (*)", "##CameraOffset", SliderType::Float, &cameraDistanceValue, 0, 3, GameSetting::CameraDistance, SetValues, "%.1f", "Value is a multiplier."};
        sliders[2] = { "HUD scaling", "##HUDOffset", SliderType::Int, &HUDvalue, 0, 40, GameSetting::HUD, SetValues };
        sliders[3] = { "Fog density (*)", "##FogDensityValue", SliderType::Float, &fogDensityValue, 0, 5, GameSetting::FogDensity, SetValues, "%.1f",
                       "This will override engine dynamic value." };
        sliders[4] = { "Fog opacity (*)", "##FogOpacityValue", SliderType::Float, &fogMaxOpacityValue, 0, 1, GameSetting::FogMaxOpacity, SetValues, "%.1f",
                       "This will override engine dynamic value." };
        sliders[5] = { "World time dilation", "##WorldTimeDilationValue", SliderType::Float, &worldTimeDilationValue, 0.f, 2.f, GameSetting::WorldTimeDilation, SetValues, nullptr,
                       "Will affect everything in the world.\nDefault value is 1." };
        sliders[6] = { "AI time dilation", "##AITimeDilationValue", SliderType::Float, &AITimeDilationValue, 0.f, 2.f, GameSetting::AITimeDilation, SetValues, nullptr,
                       "Will affect only enemies in the world.\nDefault value is 1." };
    }
}

// Settings functions
static void SaveSettings()
{
    ini::IniFile pluginIniFile;
    pluginIniFile["1#General fix"].setComment(std::vector<std::string>{ "The following sections are saved by plugin",
                                                                        "You should not need to modify them",
                                                                        " ",
                                                                        "Controls if fix mod (globally) is enabled",
                                                                        "Set Console to false if you don't want dev console to be enabled",
                                                                        "Set Skip Intro to false if you don't wan't to skip them."});
    pluginIniFile["1#General fix"]["Console"] = console;
    pluginIniFile["1#General fix"]["Enabled"] = fix_enabled;
    pluginIniFile["1#General fix"]["Skip Intro"] = skip_Intro_enabled;
    pluginIniFile["2#Individual fix"].setComment("Controls each fix individually");
    pluginIniFile["2#Individual fix"]["FOV"] = fov_fix_enabled;
    pluginIniFile["2#Individual fix"]["Camera"] = camera_fix_enabled;
    pluginIniFile["2#Individual fix"]["HUD"] = HUD_fix_enabled;
    pluginIniFile["2#Individual fix"]["Cutscenes"] = cutscenes_fix_enabled;
    pluginIniFile["2#Individual fix"]["CutscenesFPS"] = cutscenes_FPS_fix_enabled;
    pluginIniFile["2#Individual fix"]["DOF"] = DOF_fix_enabled;
    pluginIniFile["2#Individual fix"]["Chromatic aberrations"] = CA_fix_enabled;
    pluginIniFile["2#Individual fix"]["Vignetting"] = Vignetting_fix_enabled;
    pluginIniFile["2#Individual fix"]["Volumetric fog"] = volumetric_fog_fix_enabled;
    pluginIniFile["2#Individual fix"]["Fog"] = Fog_fix_enabled;
    pluginIniFile["2#Individual fix"]["Time dilation"] = Time_Dilation_fix_enabled;
    pluginIniFile["2#Individual fix"]["Stealth Mode"] = StealthMode_fix_enabled;
    pluginIniFile["2#Individual fix"]["God Mode"] = GodMode_fix_enabled;
    pluginIniFile["3#Fixes tuning"].setComment("Individual fix fine tune");
    pluginIniFile["3#Fixes tuning"]["World FOV"] = worldFOVvalue;
    pluginIniFile["3#Fixes tuning"]["Camera distance"] = cameraDistanceValue;
    pluginIniFile["3#Fixes tuning"]["HUD scaling"] = HUDvalue;
    pluginIniFile["3#Fixes tuning"]["Fog density"] = fogDensityValue;
    pluginIniFile["3#Fixes tuning"]["Fog opacity"] = fogMaxOpacityValue;
    pluginIniFile["3#Fixes tuning"]["World time dilation scale"] = worldTimeDilationValue;
    pluginIniFile["3#Fixes tuning"]["AI time dilation scale"] = AITimeDilationValue;

    pluginIniFile.save(SETTINGS_FILE);
}

static void LoadSettings()
{
    ini::IniFile pluginIniFile;
    try {
        pluginIniFile.load(SETTINGS_FILE);
        console                     = pluginIniFile["1#General fix"]["Console"].as<bool>();
        fix_enabled                 = pluginIniFile["1#General fix"]["Enabled"].as<bool>();
        skip_Intro_enabled          = pluginIniFile["1#General fix"]["Skip Intro"].as<bool>();
        fov_fix_enabled             = pluginIniFile["2#Individual fix"]["FOV"].as<bool>();
        camera_fix_enabled          = pluginIniFile["2#Individual fix"]["Camera"].as<bool>();
        HUD_fix_enabled             = pluginIniFile["2#Individual fix"]["HUD"].as<bool>();
        cutscenes_fix_enabled       = pluginIniFile["2#Individual fix"]["Cutscenes"].as<bool>();
        cutscenes_FPS_fix_enabled   = pluginIniFile["2#Individual fix"]["CutscenesFPS"].as<bool>();
        DOF_fix_enabled             = pluginIniFile["2#Individual fix"]["DOF"].as<bool>();
        CA_fix_enabled              = pluginIniFile["2#Individual fix"]["Chromatic aberrations"].as<bool>();
        Vignetting_fix_enabled      = pluginIniFile["2#Individual fix"]["Vignetting"].as<bool>();
        volumetric_fog_fix_enabled  = pluginIniFile["2#Individual fix"]["Volumetric fog"].as<bool>();
        Fog_fix_enabled             = pluginIniFile["2#Individual fix"]["Fog"].as<bool>();
        Time_Dilation_fix_enabled   = pluginIniFile["2#Individual fix"]["Time dilation"].as<bool>();
        StealthMode_fix_enabled     = pluginIniFile["2#Individual fix"]["Stealth Mode"].as<bool>();
        GodMode_fix_enabled         = pluginIniFile["2#Individual fix"]["God Mode"].as<bool>();
        worldFOVvalue               = pluginIniFile["3#Fixes tuning"]["World FOV"].as<int>();
        cameraDistanceValue         = pluginIniFile["3#Fixes tuning"]["Camera distance"].as<float>();
        HUDvalue                    = pluginIniFile["3#Fixes tuning"]["HUD scaling"].as<float>();
        fogDensityValue             = pluginIniFile["3#Fixes tuning"]["Fog density"].as<float>();
        fogMaxOpacityValue          = pluginIniFile["3#Fixes tuning"]["Fog opacity"].as<float>();
        worldTimeDilationValue      = pluginIniFile["3#Fixes tuning"]["World time dilation scale"].as<float>();
        AITimeDilationValue         = pluginIniFile["3#Fixes tuning"]["AI time dilation scale"].as<float>();
    }
    catch (const std::exception& e) {}
}

// Read plugin log file
static std::string read_log_file(const std::string& filename) {
    std::ifstream file(filename);
    std::string log;
    if (!file.is_open()) {
        log = "Impossible to open file : " + filename;
        return log;
    }

    std::ostringstream ss;
    ss << file.rdbuf();
    log = ss.str();
    return log;
}
// Reshade event callback functions
static void on_init_swapchain(reshade::api::swapchain* swapchain, bool resize) {
    reshade::api::resource backbuffer = swapchain->get_current_back_buffer();

    if (backbuffer != 0) {
        reshade::api::device* device = swapchain->get_device();
        reshade::api::resource_desc desc = device->get_resource_desc(backbuffer);
        g_width = desc.texture.width;
        g_height = desc.texture.height;
    }
}

// Initialize ImGui widgets for Reshade
static void on_overlay_draw(reshade::api::effect_runtime* runtime) {
    using namespace ImGui;
    float aspectRatio = (float)g_width / g_height;
    // Scaling factor based on screen resolution
    float scale = std::min((float)g_height / 1200,1.f);

    ImGuiStyle& style = GetStyle();
    style.ItemSpacing = ImVec2(8 * scale, 8 * scale);     // Spacing between widgets
    style.FramePadding = ImVec2(3 * scale, 3 * scale);    // Widgets padding
    style.WindowPadding = ImVec2(10 * scale, 10 * scale); // Overlay padding
    style.CellPadding = ImVec2(10 * scale, 10 * scale);   // Table cells padding

    SetNextWindowSize(ImVec2(350 * scale, 150 * scale), ImGuiCond_Once);

    PushStyleColor(ImGuiCol_Button, ImVec4(0.84f, 0.12f, 0.51f, 1.0f)); // red
    PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.84f, 0.12f, 0.51f, 1.0f)); // red
    PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.84f, 0.2f, 0.51f, 1.0f)); // red
    PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 1.f, 1.f, 1.f)); // white
    if (Button("Like my work ? Consider donation")) ShellExecuteA(NULL, "open", DONATION_URL, NULL, NULL, SW_SHOWNORMAL); // Donation
    PopStyleColor(4); // Restore color
    SameLine();
    if (Button("Fix informations")) popup_Informations = true; // Fix information
    SameLine();
    if (Button("View logs")) {
        log_content = read_log_file("SilentHillf.log");
        show_log_overlay = true; // Fix information
    }

    if (popup_Informations) {
        Begin("Informations", &popup_Informations, ImGuiWindowFlags_AlwaysAutoResize);
        Text("Version : %s", FIX_VERSION);
        Text(FIX_INFORMATIONS);
        End();
    }
    if (show_log_overlay) {
        Begin("Game log", &show_log_overlay, ImGuiWindowFlags_AlwaysAutoResize);
        TextUnformatted(log_content.c_str());
        End();
    }

    if (BeginTabBar("MainTabs")) {
        if (BeginTabItem("Fixes")) {
            if (BeginTable("FixesTable", 2, ImGuiTableFlags_SizingStretchSame)) {
                TableSetupColumn("LeftFix", ImGuiTableColumnFlags_WidthStretch, 0.3f);
                TableSetupColumn("RightFix", ImGuiTableColumnFlags_WidthStretch, 0.7f);
                TableNextRow();

                // Drawing a left column with slider and general fix
                TableSetColumnIndex(0);
                if (CollapsingHeader("Enable fixes", ImGuiTreeNodeFlags_DefaultOpen))
                    if (Checkbox("Fix enabled", &fix_enabled)) {
                        if (SetFixEnabled) SetFixEnabled(fix_enabled, false);
                        SaveSettings();
                    }

                // Individual fixes
                TableSetColumnIndex(1);
                if (CollapsingHeader("Individual fixes", ImGuiTreeNodeFlags_DefaultOpen)) {
                    if (BeginTable("IndividualFixesTable", 2, ImGuiTableFlags_SizingStretchSame)) {
                        TableSetupColumn("IndFix1", ImGuiTableColumnFlags_WidthStretch, 0.35f);
                        TableSetupColumn("IndFix2", ImGuiTableColumnFlags_WidthStretch, 0.65f);
                        TableNextRow();

                        TableSetColumnIndex(0);
                        for (int i = 0; i < 5; ++i) DrawFixCheckbox(individualFixes[i]);
                        TableSetColumnIndex(1);
                        for (int i = 5; i < IM_ARRAYSIZE(individualFixes); ++i) DrawFixCheckbox(individualFixes[i]);
                        EndTable();
                    }
                }
                EndTable();
            }

            SetCursorPosY(GetCursorPosY() - 2 * style.ItemSpacing.y);
            if (BeginTable("FixesSliders", 2, ImGuiTableFlags_SizingStretchSame)) {
                TableNextRow();
                // Sliders
                TableSetColumnIndex(0);
                for (int i = 0; i < 3; ++i) DrawSlider2(sliders[i], 200);
                TableSetColumnIndex(1);
                for (int i = 3; i < 5; ++i) DrawSlider2(sliders[i], 200);
                EndTable();
            }
            EndTabItem();
        }

        if (BeginTabItem("Cheats")){
            for (const auto& cheat : cheatFixes) DrawFixCheckbox(cheat);

            if (BeginTable("SlidersTable", 2, ImGuiTableFlags_SizingStretchSame)) {
                TableSetupColumn("LeftSliders", ImGuiTableColumnFlags_WidthStretch, 0.5f);
                TableSetupColumn("RightSliders", ImGuiTableColumnFlags_WidthStretch, 0.5f);
                TableNextRow();

                TableSetColumnIndex(0);
                DrawSlider2(sliders[5], 180);
                TableSetColumnIndex(1);
                DrawSlider2(sliders[6], 180);
                EndTable();
            }
            EndTabItem();
        }
        EndTabBar();

        // Fix status
        if (CollapsingHeader("Fix informations", ImGuiTreeNodeFlags_DefaultOpen)) {
            Text("Screen width: %d, height: %d, aspect ratio: %.2f", g_width, g_height, aspectRatio);
            if (GetGameInfos) {
                GameInfos infos{};
                GetGameInfos(&infos);
                if (infos.consoleEnabled)
                    Text("Console enabled and bound to key F2");
                TextColored(ImColor(48, 179, 25), "FOV In: %.2f, Out: %.2f", infos.FOVIn, infos.FOVOut);
                TextColored(ImColor(48, 179, 25), "Camera distance In: %.2f, Out: %.2f", infos.cameraIn, infos.cameraOut);
                TextColored(ImColor(48, 179, 25), "Default fog density: %.3f, opacity: %3.f", infos.defaultFogDensity, infos.fogMaxOpacity);
            }
        }
    }
}

static void on_reshade_present(reshade::api::effect_runtime* runtime) {
    static auto last = std::chrono::steady_clock::now();
    auto now = std::chrono::steady_clock::now();
    float dt = std::chrono::duration<float>(now - last).count();
    last = now;

    ProcessHotkeys(runtime);
    UpdateOSD(runtime, dt);
}

// Retrieve all shader uniform variables
static void on_reshade_begin_effects(reshade::api::effect_runtime* runtime, reshade::api::command_list* cmd_list,
    reshade::api::resource_view rtv, reshade::api::resource_view rtv_srgb) {
    if (!runtime) return;
    FindAllUniformVariables(runtime, OSD_SHADER_NAME); // Find all uniforms and set their handle

    if (InterlockedCompareExchange(&g_uniformReseted, 1, 0) != 0) return; // reset OSD uniforms once
    ResetAllUniformVariables(runtime, OSD_SHADER_NAME);
    runtime->save_current_preset(); // Save shader preset
}

static void InitializeHotkeys() { // Initialize hotkeys for cheats
    if (InterlockedCompareExchange(&g_hotkeysInitialized, 1, 0) != 0) return; // Initialize hotkeys once

    RegisterHotkey('1', Modifier::Alt, [] {
        ToggleOSD(Time_Dilation_fix_enabled, GameFixes::TimeDilation,
            u_td_show, u_td_enabled, OSD_duration,
            { {u_td_world, worldTimeDilationValue}, {u_td_AI, AITimeDilationValue} });
    });

    RegisterHotkey('2', Modifier::Alt, [] {
        ToggleOSD(StealthMode_fix_enabled, GameFixes::Stealth, u_Stealth_show, u_Stealth_enabled, OSD_duration);
    });

    RegisterHotkey('3', Modifier::Alt, [] {
        ToggleOSD(GodMode_fix_enabled, GameFixes::GodMode, u_GodMode_show, u_GodMode_enabled, OSD_duration);
        });
}

// Main dll intrance
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD ul_reason_for_call, LPVOID) {
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
            if (!reshade::register_addon(hinstDLL))
                return FALSE;
            LoadSettings();

            reshade::register_overlay("Silent Hill f", &on_overlay_draw);
            reshade::register_event<reshade::addon_event::init_swapchain>(on_init_swapchain);
            reshade::register_event<reshade::addon_event::init_effect_runtime>(
                [](reshade::api::effect_runtime* runtime) {
                    LoadFixDLL();
                    InitializeHotkeys();
                });
            reshade::register_event<reshade::addon_event::reshade_present>(&on_reshade_present);
            reshade::register_event<reshade::addon_event::reshade_begin_effects>(&on_reshade_begin_effects);
            break;
        case DLL_PROCESS_DETACH:
            reshade::unregister_event<reshade::addon_event::reshade_present>(&on_reshade_present);
            reshade::unregister_event<reshade::addon_event::reshade_begin_effects>(&on_reshade_begin_effects);
            reshade::unregister_event<reshade::addon_event::init_swapchain>(on_init_swapchain);
            reshade::unregister_addon(hinstDLL);
            break;
    }
    return TRUE;
}