#define IMGUI_DISABLE_INCLUDE_IMCONFIG_H
#define IMGUI_HAS_DOCK 1

#include "CommonHeaders.h"
#include "HotkeysManager.h"
#include "DirectX.h"
#include <thread>

// Constants
constexpr ULONGLONG DECIMA_ENGINE_INIT_DELAY = 6000; // Use to wait for Decima engine to be ready
// Screen informations
static int screenHeight = GetSystemMetrics(SM_CYSCREEN);
// Core game dll functions declarations
typedef void (*SetBoolFn)(bool, bool);
typedef void (*SetFixesFn)(GameFixes, bool);
typedef void (*SetFloatFn)(GameSetting, float);

static HMODULE fixLib = nullptr;
static LONG g_coreInitialized = 0;
static SetBoolFn SetFixEnabled = nullptr;
static SetFixesFn SetFixes = nullptr;
static SetFloatFn SetValues = nullptr;
void SetFixesEnabled(GameFixes fix, bool value) { if (SetFixes) SetFixes(fix, value); }

static GetGameInfosStruct GetGameInfos = nullptr;

// Plugin variables for checkboxes and sliders
static bool fov_fix_enabled = false;
static bool ultrawide_fix_enabled = false;
static bool camera_fix_enabled = false;
static bool DOF_fix_enabled = false;
static bool vignetting_fix_enabled = false;
static bool fix_enabled = false;
static int worldFOVvalue = 0;
static float cameraDistancevalue = 1.f;
static int triggerDelayMs = 2000; // Default delay between shortcuts

// 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.3";
const char* FIX_INFORMATIONS = "This fix allows to:\n - Control FOV in game.\n - Control camera distance.\n - Enable ultrawide.\n - Disable depth of field in cutscenes.\n - Disable vignetting.";
const char* DONATION_URL = "https://buymeacoffee.com/k4sh44";

// Scaling factor based on screen resolution
float scale = (float)screenHeight / 1200;

// Prepare arrays of checkboxes for ImGui
static FixToggle individualFixes[] = {
    { "FOV", &fov_fix_enabled, GameFixes::FOV },
    { "Depth of field", &DOF_fix_enabled, GameFixes::DOF },
    { "Vignetting", &vignetting_fix_enabled, GameFixes::Vignetting },
    { "Ultrawide", &ultrawide_fix_enabled, GameFixes::UltraWide, "This fix targets aspect ratios wider than 32:9.\n"
                                                                 "It forces the engine to redraw the aspect by simulating shortcuts.\n"
                                                                 "Use the threshold slider below to fine tune the delay between simulated shortcuts.\n"
                                                                 "Increase the delay on slower PCs, decrease it on faster ones."},
    { "Camera", &camera_fix_enabled, GameFixes::Camera } };

// Prepare array of sliders for ImGui
static SliderFix2 sliders[3];

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

    if (GetModuleHandleA("DeathStranding2Core.dll") == nullptr) {
        fixLib = LoadLibraryA("DeathStranding2Core.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);
        }
        if (SetFixEnabled) SetFixEnabled(fix_enabled, true);
        if (SetFixes) {
            SetFixes(GameFixes::FOV, fov_fix_enabled);
            SetFixes(GameFixes::Camera, camera_fix_enabled);
            SetFixes(GameFixes::DOF, DOF_fix_enabled);
            SetFixes(GameFixes::Vignetting, vignetting_fix_enabled);
        }

        sliders[0] = { "In game additional FOV", "##FOVValue", SliderType::Int, &worldFOVvalue, -20, 50, GameSetting::FOV, SetValues };
        sliders[1] = { "Camera distance", "##CamValue", SliderType::Float, &cameraDistancevalue, 0, 5, GameSetting::CameraDistance, SetValues, "%.1f",
                       "Value is a multiplier.\nFinal value reported below is in meters."};
        sliders[2] = { "Ultrawide enforce delay", "##AltEnterValue", SliderType::Int, &triggerDelayMs, 1000, 5000, GameSetting::Threshold, SetValues, "",
                       "This value is a threshold in ms between the simulated alt + enter." };
    }
}

// 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" });
    pluginIniFile["1#General fix"]["Enabled"] = fix_enabled;
    pluginIniFile["1#General fix"]["TriggerDelayMs"] = triggerDelayMs;
    pluginIniFile["2#Individual fix"].setComment("Controls each fix individually");
    pluginIniFile["2#Individual fix"]["FOV"] = fov_fix_enabled;
    pluginIniFile["2#Individual fix"]["UltraWide"] = ultrawide_fix_enabled;
    pluginIniFile["2#Individual fix"]["Camera"] = camera_fix_enabled;
    pluginIniFile["2#Individual fix"]["DOF"] = DOF_fix_enabled;
    pluginIniFile["2#Individual fix"]["Vignetting"] = vignetting_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.save(SETTINGS_FILE);
}

static void LoadSettings() {
    ini::IniFile pluginIniFile;
    try {
        pluginIniFile.load(SETTINGS_FILE);
        fix_enabled                 = pluginIniFile["1#General fix"]["Enabled"].as<bool>();
        triggerDelayMs              = pluginIniFile["1#General fix"]["TriggerDelayMs"].as<int>();
        fov_fix_enabled             = pluginIniFile["2#Individual fix"]["FOV"].as<bool>();
        ultrawide_fix_enabled       = pluginIniFile["2#Individual fix"]["UltraWide"].as<bool>();
        camera_fix_enabled          = pluginIniFile["2#Individual fix"]["Camera"].as<bool>();
        DOF_fix_enabled             = pluginIniFile["2#Individual fix"]["DOF"].as<bool>();
        vignetting_fix_enabled      = pluginIniFile["2#Individual fix"]["Vignetting"].as<bool>();
        worldFOVvalue               = pluginIniFile["3#Fixes tuning"]["World FOV"].as<int>();
        cameraDistancevalue         = pluginIniFile["3#Fixes tuning"]["Camera distance"].as<float>();
    }
    catch (const std::exception& e) {}
}

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

    std::ostringstream ss;
    ss << file.rdbuf();
    log_content = ss.str();
}

// Initialize ImGui widgets for Reshade
static void on_overlay_draw(reshade::api::effect_runtime* runtime) {
    ImGui::SetNextWindowSize(ImVec2(350 * scale, 150 * scale), ImGuiCond_Once);

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

    if (popup_Informations) {
        ImGui::Begin("Informations", &popup_Informations, ImGuiWindowFlags_AlwaysAutoResize);
        ImGui::Text("Version : %s", FIX_VERSION);
        ImGui::Text(FIX_INFORMATIONS);
        ImGui::End();
    }

    if (show_log_overlay) {
        ImGui::Begin("Game log", &show_log_overlay, ImGuiWindowFlags_AlwaysAutoResize);
        ImGui::TextUnformatted(log_content.c_str());
        ImGui::End();
    }

    if (ImGui::BeginTabBar("MainTabs")) {
        if (ImGui::BeginTabItem("Fixes")) {
            ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(10 * scale, 10 * scale));
            if (ImGui::BeginTable("FixesTable", 2, ImGuiTableFlags_SizingStretchSame)) {
                ImGui::TableSetupColumn("LeftFix", ImGuiTableColumnFlags_WidthStretch, 0.4f);
                ImGui::TableSetupColumn("RightFix", ImGuiTableColumnFlags_WidthStretch, 0.6f);
                ImGui::TableNextRow();

                // Drawing a left column with slider and general fix
                ImGui::TableSetColumnIndex(0);
                if (ImGui::CollapsingHeader("Enable fixes", ImGuiTreeNodeFlags_DefaultOpen))
                    if (ImGui::Checkbox("Fix enabled", &fix_enabled)) {
                        if (SetFixEnabled) SetFixEnabled(fix_enabled, false);
                        SaveSettings();
                    }
                // Individual fixes
                ImGui::TableSetColumnIndex(1);
                if (ImGui::CollapsingHeader("Individual fixes", ImGuiTreeNodeFlags_DefaultOpen)) {
                    if (ImGui::BeginTable("IndividualFixesTable", 2, ImGuiTableFlags_SizingStretchSame)) {
                        ImGui::TableSetupColumn("IndFix1", ImGuiTableColumnFlags_WidthStretch, 0.4f);
                        ImGui::TableSetupColumn("IndFix2", ImGuiTableColumnFlags_WidthStretch, 0.6f);
                        ImGui::TableNextRow();

                        ImGui::TableSetColumnIndex(0);
                        DrawFixCheckbox(individualFixes[0]);
                        DrawFixCheckbox(individualFixes[1]);
                        DrawFixCheckbox(individualFixes[2]);
                        ImGui::TableSetColumnIndex(1);
                        DrawFixCheckbox(individualFixes[3]);
                        DrawFixCheckbox(individualFixes[4]);

                        ImGui::EndTable();
                    }
                }
                ImGui::EndTable();
            }

            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 2);
            if (ImGui::BeginTable("FixesSliders", 2, ImGuiTableFlags_SizingStretchSame)) {
                ImGui::TableNextRow();
                ImGui::TableSetColumnIndex(0);
                DrawSlider2(sliders[0], 200);
                ImGui::TableSetColumnIndex(1);
                DrawSlider2(sliders[1], 200);
                DrawSlider2(sliders[2], 200);
                ImGui::EndTable();
            }
            ImGui::PopStyleVar();
            ImGui::EndTabItem();
        }

        // Fix status
        if (ImGui::CollapsingHeader("Fix informations", ImGuiTreeNodeFlags_DefaultOpen)) {
            if (GetGameInfos) {
                GameInfos infos{};
                GetGameInfos(&infos);
                ImGui::Text("Screen width: %d, height: %d, aspect ratio: %.2f", infos.screenWidth, infos.screenHeight, infos.aspectRatio);
                ImGui::TextColored(ImColor(48, 179, 25), "FOV In: %.2f, Out: %.2f", infos.FOVIn, infos.FOVOut);
                ImGui::TextColored(ImColor(48, 179, 25), "Camera distance In: %.2f, Out: %.2f", infos.cameraIn, infos.cameraOut);
            }
        }
    }
    ImGui::PopStyleVar();
}

static std::atomic<bool> g_Started{ false };
static std::atomic<bool> g_Done{ false };
static void on_present(reshade::api::command_queue* queue, reshade::api::swapchain* swapchain,
    const reshade::api::rect*, const reshade::api::rect*, uint32_t, const reshade::api::rect*) {
    if (!ultrawide_fix_enabled || g_Done || !SetFixes || g_Started.exchange(true)) return;

    void* hwnd = swapchain->get_hwnd(); // Retrieve DX12 swapchain
    HWND hwnd_local = reinterpret_cast<HWND>(hwnd);
    bool exclusive = IsExclusiveFullscreen(swapchain); // Retrieve display mode (fullscreen or exclusive fullscreen)

    SetFixes(GameFixes::UltraWide, ultrawide_fix_enabled);
    SetFixes(GameFixes::None, false);

    std::thread([hwnd_local, exclusive]() {
        std::this_thread::sleep_for(std::chrono::milliseconds(DECIMA_ENGINE_INIT_DELAY)); // Wait for Decima engine to be ready
        if (!exclusive) {
            SimulateAltEnter(); // 1st Alt+Enter
            std::this_thread::sleep_for(std::chrono::milliseconds(triggerDelayMs)); // Give enough time to swapchain rebuild
            SimulateAltEnter(); // 2nd Alt+Enter
        }
        else {
            SimulateAltTab(); // Simulate ALT+Tab
            std::this_thread::sleep_for(std::chrono::milliseconds(triggerDelayMs));
            ShowWindow(hwnd_local, SW_RESTORE); // Restore game window
        }
        g_Done = true;
    }).detach();
}

// 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("Death Stranding 2: On The Beach", &on_overlay_draw);
            reshade::register_event<reshade::addon_event::present>(&on_present);
            reshade::register_event<reshade::addon_event::init_effect_runtime>(&LoadFixDLL);
            break;
        case DLL_PROCESS_DETACH:
            reshade::unregister_overlay("Death Stranding 2: On The Beach", &on_overlay_draw);
            reshade::unregister_event<reshade::addon_event::present>(&on_present);
            reshade::unregister_event<reshade::addon_event::init_effect_runtime>(&LoadFixDLL);
            reshade::unregister_addon(hinstDLL);
            break;
    }
    return TRUE;
}