#include "Memory.hpp";
#include "Maths.hpp";
#include "ObfuscateString.h"
#include <string>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <safetyhook.hpp>

// Constants
const std::string PLUGIN_NAME = "Starfield";
const std::string PLUGIN_LOG = "Starfield.log"; 
const std::string gameExecutable = "Starfield.exe";

// Logger
std::shared_ptr<spdlog::logger> logger;

// Screen informations
static int screenWidth = GetSystemMetrics(SM_CXSCREEN);
static int screenHeight = GetSystemMetrics(SM_CYSCREEN);
static float aspectRatio = (float)screenWidth / screenHeight;

// Plugin states
static bool AOBScanDone = false;
static bool g_fix_enabled = false;
static bool g_dialog_fov_fix_enabled = false;
static bool g_weapon_fov_fix_enabled = false;
static bool g_HUD_fix_enabled = false;
static bool g_photomode_fix_enabled = false;
static int g_AdditionalDialogFOVValue = 0;
static int g_AdditionalWeaponFOVValue = 0;
static float g_HUDXValue = 0;
static float g_HUDYValue = 0;

// Shared values
static float g_Dialog_FOV_In = 0;
static float g_Dialog_FOV_Out = 0;
static float g_Weapon_FOV_In = 0;
static float g_Weapon_FOV_Out = 0;

// AOB Scan pointers
static uint8_t* DialogFOVAddress = nullptr;
static uint8_t* WeaponFOVAddress = nullptr;
static uint8_t* HUDAddress = nullptr;
static uint8_t* PhotomodeAddress = nullptr;

// Hooking
static SafetyHookMid DialogFOVHook{};
static SafetyHookMid WeaponFOVHook{};
static SafetyHookMid HUDHook{};

// Prototypes 
static void DialogFOVFixEnabled(bool fix_enabled);
static void WeaponFOVFixEnabled(bool fix_enabled);
static void HUDFixEnabled(bool fix_enabled);
static void PhotomodeFixEnabled(bool fix_enabled);

extern "C" __declspec(dllexport) void SetFixEnabled(bool enabled)
{
    g_fix_enabled = enabled;
    if (g_fix_enabled && !AOBScanDone) {
        logger->info("--------------- AOB scan started ---------------");
        // === AOB Scans ===
        if (!DialogFOVAddress) {
            constexpr auto DialogFOVPattern = make_obfuscated<0x4A>("E9 ?? ?? ?? ?? 49 ?? ?? E8 ?? ?? ?? ?? 84 ?? 0F 84 ?? ?? ?? ?? 48 8B ?? ?? ?? ?? ?? 48 89");
            DialogFOVAddress = Memory::AOBScan(gameExecutable, DialogFOVPattern.decrypt(), PAGE_EXECUTE_READ);
            //Starfield.exe + BBA13E - A8 01                   - test al, 01
            //Starfield.exe + BBA140 - 74 0D                   - je Starfield.exe + BBA14F
            //Starfield.exe + BBA142 - C5 FA 10 35 FE 15 23 05 - vmovss xmm6, [Starfield.exe + 5DEB748]
            //Starfield.exe + BBA14A - E9 01 01 00 00          - jmp Starfield.exe + BBA250
            //Starfield.exe + BBA14F - 49 8B CF                - mov rcx, r15
            if (DialogFOVAddress)
            {
                logger->info("Dialog FOV signature found at address: 0x{:X}.", reinterpret_cast<uintptr_t>(DialogFOVAddress));
            }
            else logger->warn("Dialog FOV signature not found. Maybe the game was updated.");
        }
        if (!WeaponFOVAddress) {
            constexpr auto WeaponFOVPattern = make_obfuscated<0x4A>("C5 FA ?? ?? ?? ?? ?? ?? 80 3D ?? ?? ?? ?? ?? 48 8B ?? ?? ?? ?? ?? 74");
            WeaponFOVAddress = Memory::AOBScan(gameExecutable, WeaponFOVPattern.decrypt(), PAGE_EXECUTE_READ);
            //Starfield.exe + 12D9F27 - C5 F8 28 C2             - vmovaps xmm0, xmm2
            //Starfield.exe + 12D9F2B - 48 8B 05 5E C4 BD 04    - mov rax, [Starfield.exe + 5EB6390]
            //Starfield.exe + 12D9F32 - C5 FA 11 80 E0 02 00 00 - vmovss[rax + 000002E0], xmm0
            //Starfield.exe + 12D9F3A - 80 3D EB A0 AD 04 00    - cmp byte ptr[Starfield.exe + 5DB402C], 00
            //Starfield.exe + 12D9F41 - 48 8B 05 10 32 BB 04    - mov rax, [Starfield.exe + 5E8D158]
            if (WeaponFOVAddress)
            {
                logger->info("Weapon FOV signature found at address: 0x{:X}.", reinterpret_cast<uintptr_t>(WeaponFOVAddress));
            }
            else logger->warn("Weapon FOV signature not found. Maybe the game was updated.");
        }
        if (!HUDAddress) {
            constexpr auto HUDPattern = make_obfuscated<0x4A>("C4 61 ?? ?? ?? ?? ?? ?? ?? C4 61 ?? ?? ?? ?? ?? ?? ?? 45 ?? ?? 74");
            HUDAddress = Memory::AOBScan(gameExecutable, HUDPattern.decrypt(), PAGE_EXECUTE_READ);
            //Starfield.exe + 24D6D3B - C4 61 FA 2C 05 3C 28 38 03 - vcvttss2si r8, [Starfield.exe + 5859580]
            //Starfield.exe + 24D6D44 - C4 61 FA 2C 0D 13 28 38 03 - vcvttss2si r9, [Starfield.exe + 5859560]
            //Starfield.exe + 24D6D4D - 45 84 DB                   - test r11b, r11b
            //Starfield.exe + 24D6D50 - 74 15                      - je Starfield.exe + 24D6D67
            //Starfield.exe + 24D6D52 - 8B 42 38                   - mov eax, [rdx + 38]
            if (HUDAddress)
            {
                logger->info("HUD safe zone signature found at address: 0x{:X}.", reinterpret_cast<uintptr_t>(HUDAddress));
                HUDAddress += 0x12;
            }
            else logger->warn("HUD safe zone signature not found. Maybe the game was updated.");
        }
        if (!PhotomodeAddress) {
            constexpr auto PhotomodePattern = make_obfuscated<0x4A>("74 ?? 0F BA ?? ?? 89 87 ?? ?? ?? ?? C6 87 ?? ?? ?? ?? ?? E8");
            PhotomodeAddress = Memory::AOBScan(gameExecutable, PhotomodePattern.decrypt(), PAGE_EXECUTE_READ);
            //Starfield.exe + 159D76C - C6 87 D2 00 00 00 01 - mov byte ptr[rdi + 000000D2], 01
            //Starfield.exe + 159D773 - 40 38 35 96 1F 87 04 - cmp[Starfield.exe + 5E0F710], sil
            //Starfield.exe + 159D77A - 74 11                - je Starfield.exe + 159D78D
            //Starfield.exe + 159D77C - 0F BA E8 1B          - bts eax, 1B
            //Starfield.exe + 159D780 - 89 87 C0 00 00 00    - mov[rdi + 000000C0], eax
            if (PhotomodeAddress)
            {
                logger->info("Photo mode signature found at address: 0x{:X}.", reinterpret_cast<uintptr_t>(PhotomodeAddress));
            }
            else logger->warn("Photo mode signature not found. Maybe the game was updated.");
            if (DialogFOVAddress && WeaponFOVAddress && HUDAddress && PhotomodeAddress) {
                logger->info("All AOB signatures found. Ready to patch...");
                logger->info("--------------- AOB scan finished ---------------");
                AOBScanDone = true;
            }
            else
                logger->warn("Some AOB signatures could not be found. Fixes may be partially unavailable.");
        }
    }
    // === Activer ou désactiver les patchs ===
    if (DialogFOVAddress && WeaponFOVAddress && HUDAddress && PhotomodeAddress)
    {
        if (g_fix_enabled)
        {
            DialogFOVFixEnabled(g_dialog_fov_fix_enabled);
            WeaponFOVFixEnabled(g_weapon_fov_fix_enabled);
            HUDFixEnabled(g_HUD_fix_enabled);
            PhotomodeFixEnabled(g_photomode_fix_enabled);
        }
        else
        {
            DialogFOVFixEnabled(false);
            WeaponFOVFixEnabled(false);
            HUDFixEnabled(false);
            PhotomodeFixEnabled(false);
            logger->info("All fixes disabled.");
        }
    }
}

// Setters for Reshade addon call
extern "C" __declspec(dllexport) void SetDialogFOVFixEnabled(bool enabled, bool init)
{
    g_dialog_fov_fix_enabled = enabled;
    if (!init) DialogFOVFixEnabled(g_dialog_fov_fix_enabled);
}

extern "C" __declspec(dllexport) void SetWeaponFOVFixEnabled(bool enabled, bool init)
{
    g_weapon_fov_fix_enabled = enabled;
    if (!init) WeaponFOVFixEnabled(g_weapon_fov_fix_enabled);
}

extern "C" __declspec(dllexport) void SetHUDFixEnabled(bool enabled, bool init)
{
    g_HUD_fix_enabled = enabled;
    if (!init) HUDFixEnabled(g_HUD_fix_enabled);
}

extern "C" __declspec(dllexport) void SetPhotoModeFixEnabled(bool enabled, bool init)
{
    g_photomode_fix_enabled = enabled;
    if (!init) PhotomodeFixEnabled(g_photomode_fix_enabled);
}

extern "C" __declspec(dllexport) void SetDialogFOV(int fov)
{
    g_AdditionalDialogFOVValue = fov;
}

extern "C" __declspec(dllexport) void SetWeaponFOV(int fov)
{
    g_AdditionalWeaponFOVValue = fov;
}

extern "C" __declspec(dllexport) void SetHUDX(int HUDX)
{
    g_HUDXValue = HUDX;
}

extern "C" __declspec(dllexport) void SetHUDY(int HUDY)
{
    g_HUDYValue = HUDY;
}

// Getters for Reshade addon call
extern "C" __declspec(dllexport) float GetDialogFOVIn() {
    return g_Dialog_FOV_In;
}

extern "C" __declspec(dllexport) float GetDialogFOVOut() {
    return g_Dialog_FOV_Out;
}

extern "C" __declspec(dllexport) float GetWeaponFOVIn() {
    return g_Weapon_FOV_In;
}

extern "C" __declspec(dllexport) float GetWeaponFOVOut() {
    return g_Weapon_FOV_Out;
}

// Assembly code injections functions
static void DialogFOVFixEnabled(bool fix_enabled) {
    if (g_fix_enabled && fix_enabled && DialogFOVAddress != nullptr) {
        if (!DialogFOVHook) { // Hook only once
            DialogFOVHook = safetyhook::create_mid(DialogFOVAddress,
                [](SafetyHookContext& ctx) {
                    g_Dialog_FOV_In = ctx.xmm6.f32[0];
                    g_Dialog_FOV_Out = ctx.xmm6.f32[0] += (g_dialog_fov_fix_enabled ? g_AdditionalDialogFOVValue : 0); // World FOV
                });
        }
        else DialogFOVHook.enable();
        logger->info("FOV fix enabled");
    }
    if (!fix_enabled && DialogFOVHook) {
        DialogFOVHook.disable();
        logger->info("FOV fix disabled");
    }
}

static void WeaponFOVFixEnabled(bool fix_enabled) {
    if (g_fix_enabled && fix_enabled && WeaponFOVAddress != nullptr) {
        if (!WeaponFOVHook) {
            WeaponFOVHook = safetyhook::create_mid(WeaponFOVAddress,
                [](SafetyHookContext& ctx) {
                    g_Weapon_FOV_In = ctx.xmm0.f32[0];
                    g_Weapon_FOV_Out = ctx.xmm0.f32[0] += (g_weapon_fov_fix_enabled ? g_AdditionalWeaponFOVValue : 0);
                });
        }
        else WeaponFOVHook.enable();
        logger->info("Weapon FOV fix enabled");
    }
    if (!fix_enabled && WeaponFOVHook) {
        WeaponFOVHook.disable();
        logger->info("Weapon FOV fix disabled");
    }
}

static void HUDFixEnabled(bool fix_enabled) {
    if (g_fix_enabled && fix_enabled && HUDAddress) {
        if (!HUDHook) {
            HUDHook = safetyhook::create_mid(HUDAddress,
                [](SafetyHookContext& ctx) {
                    int safeZoneX = (screenWidth / 2) * (g_HUDXValue / 100);
                    int safeZoneY = (screenHeight / 2) * (g_HUDYValue / 100);
                    ctx.r9 = *reinterpret_cast<uint32_t*>(&safeZoneX); // X axis
                    ctx.r8 = *reinterpret_cast<uint32_t*>(&safeZoneY); // Y axis
                });
        }
        else HUDHook.enable();
        logger->info("HUD fix enabled");
    }
    if (!fix_enabled && HUDHook) {
        HUDHook.disable();
        logger->info("HUD fix disabled");
    }
}

static void PhotomodeFixEnabled(bool fix_enabled) {
    if (g_fix_enabled && fix_enabled && PhotomodeAddress != nullptr) {
        Memory::PatchBytes(PhotomodeAddress, "\xEB", 1); // je ==> jmp
        logger->info("Photomode fix enabled");
    }
    if (!fix_enabled && PhotomodeAddress) {
        Memory::RestoreBytes(PhotomodeAddress);
        logger->info("Photomode fix disabled");
    }
}

// spdlog init with specific format
static void InitializeLogger()
{
    try
    {
        logger = spdlog::basic_logger_mt("Fixlib", PLUGIN_LOG, true);
        spdlog::set_default_logger(logger);
        // Format : [YYYY-MM-DD HH:MM:SS] [INFO] message
        spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%^%l%$] %v");
        spdlog::set_level(spdlog::level::debug);
        logger->flush_on(spdlog::level::debug); // Flush automatically
    }
    catch (const spdlog::spdlog_ex& ex)
    {
        std::string plugin_error_message = "Could not open " + PLUGIN_LOG;
        MessageBoxA(nullptr, plugin_error_message.c_str(), "Logger Error", MB_ICONERROR | MB_OK);
    }
}

// Entry point
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID)
{
    if (reason == DLL_PROCESS_ATTACH)
    {
        InitializeLogger();
        logger->info("Plugin {} loaded.", PLUGIN_NAME);
    }
    else if (reason == DLL_PROCESS_DETACH)
    {
        logger->info("Plugin {} unloaded.", PLUGIN_NAME);
        spdlog::drop_all();
    }
    return TRUE;
}