#pragma once
#include "upscalers/IFeature.h"

#include "misc/Quirks.h"
#include "framegen/IFGFeature_Dx12.h"
#include <inputs/FG/Streamline_Inputs_Dx12.h>
#include <inputs/FG/Streamline_Inputs_Sl1_Dx12.h>

#include <set>
#include <deque>
#include <mutex>
#include <sl_dlss_g.h>
#include <vulkan/vulkan.h>
#include <ankerl/unordered_dense.h>

enum class FGPreset : uint32_t
{
    NoFG,
    OptiFG,
    Nukems,
};

enum class FrameTimeSource : uint32_t
{
    Input,
    Opti,
    Zero,
};

enum class FGInput : uint32_t
{
    NoFG,
    NvngxFG,
    FSRFG,
    DLSSG, // technically Streamline inputs
    XeFG,
    Upscaler, // OptiFG
    FSRFG30,

    ForceXeLL, // Do not expose this option
};

enum class FGOutput : uint32_t
{
    NoFG,
    NvngxFG,
    FSRFG,
    DLSSG,
    XeFG,
    DLSSGWithNvngx,
};

enum class WorkingMode : uint32_t
{
    Dxgi,
    D3d12,
    Other,
};

enum class PostCode : uint32_t
{
    SlPluginsAlreadyInMemory,
    TryingFsr4Fp8OnUnsupported,
    _
};

enum class GameEngineType : uint32_t
{
    Unity,
    Unreal,
    Katana,
    Other,
};

enum class SwapchainInteropApi : uint32_t
{
    None,
    Dx11wDx12,
};

typedef struct CapturedHudlessInfo
{
    UINT64 usageCount = 1;
    UINT captureInfo = 0;
    bool enabled = true;
} captured_hudless_info;

class State
{
  public:
    static State& Instance()
    {
        static State instance;
        return instance;
    }

    std::string gameExe;
    std::string gameName;
    std::string gameVersion;
    GameEngineType gameEngine = GameEngineType::Other;

    bool nvngxDx11Inited = false;
    bool nvngxDx12Inited = false;
    bool nvngxVkInited = false;

    flag_set<GameQuirk> gameQuirks;
    bool isOptiPatcherSucceed = false;

    // Reseting on creation of new feature
    std::optional<bool> autoExposure;

    // FG
    uint64_t fgLastFrame = 0;

    // Nvngx FG, uses streamline swapchain
    bool nvngxFgFilesAvailable = false;
    bool dlssgDebugView = false;
    bool dlssgInterpolatedOnly = false;
    uint64_t dlssgLastFrame = 0;
    uint32_t delayMenuRenderBy = 0;

    // FSR Common
    float lastFsrCameraNear = 0.0f;
    float lastFsrCameraFar = 0.0f;

    // Frame Generation
    FGInput activeFgInput = FGInput::NoFG;
    FGOutput activeFgOutput = FGOutput::NoFG;

    // Streamline FG inputs
    Sl_Inputs_Dx12 slFGInputs = {};
    Sl1_Inputs_Dx12 s_sl1FGInputs {};

    // OptiFG
    bool fgPresentIsCalled = false;
    bool fgOnlyGenerated = false;
    bool fgHudlessCompare = false;
    bool fgChanged = false;
    bool scChanged = false;
    bool skipHeapCapture = false;

    bool fgCaptureResources = false;
    size_t fgCapturedResourceCount = 0;
    bool fgResetCapturedResources = false;
    bool fgOnlyUseCapturedResources = false;

    bool fsrfgFramePaceTuningChanged = false;
    bool fsrfgInputActive = false;

    ankerl::unordered_dense::map<void*, CapturedHudlessInfo> capturedHudlesses;
    bool clearCapturedHudlesses = false;

    // NVNGX init parameters
    uint64_t NVNGX_ApplicationId = 1337;
    std::wstring NVNGX_ApplicationDataPath;
    std::string NVNGX_ProjectId;
    NVSDK_NGX_Version NVNGX_Version {};
    const NVSDK_NGX_FeatureCommonInfo* NVNGX_FeatureInfo = nullptr;
    std::vector<std::wstring> NVNGX_FeatureInfo_Paths;
    NVSDK_NGX_LoggingInfo NVNGX_Logger { nullptr, NVSDK_NGX_LOGGING_LEVEL_OFF, false };
    NVSDK_NGX_EngineType NVNGX_Engine = NVSDK_NGX_ENGINE_TYPE_CUSTOM;
    std::string NVNGX_EngineVersion;
    std::optional<std::wstring> NVNGX_DLSS_Path;
    std::optional<std::wstring> NVNGX_DLSSD_Path;
    std::optional<std::wstring> NVNGX_DLSSG_Path;

    // optis dlls
    HMODULE optiSlInterposer = nullptr;
    HMODULE optiSlCommon = nullptr;
    HMODULE optiSlDLSSG = nullptr;
    HMODULE optiSlReflex = nullptr;
    HMODULE optiSlPCL = nullptr;
    HMODULE optiDLSSG = nullptr;

    // NGX OTA
    std::string NGX_OTA_Dlss;
    std::string NGX_OTA_Dlssd;

    feature_version streamlineVersion = { 0, 0, 0 };

    // Has value when Opti was able to hook sl and the game set DLSSG options
    std::optional<int> dlssgMfgMax = std::nullopt;

    API api = API::NotSelected;
    API swapchainApi = API::NotSelected;
    SwapchainInteropApi swapchainInteropApi = SwapchainInteropApi::None;

    // Framerate
    bool reflexLimitsFps = false;
    bool reflexShowWarning = false;
    bool rtssReflexInjection = false;
    bool fakenvapiReloadLowLatency = false;
    UINT64 reflexFrameId = 0;
    UINT64 frameCount = 0;
    bool vkAntiLagSupported = false;

    // for realtime changes
    ankerl::unordered_dense::map<unsigned int, bool> changeBackend;
    Upscaler newBackend = Upscaler::Reset;

    // XeSS debug stuff
    bool xessDebug = false;
    int xessDebugFrames = 5;
    float lastMipBias = 100.0f;
    float lastMipBiasMax = -100.0f;

    int xefgMaxInterpolationCount = 1;
    bool WAR_xefgRequestFGToggle = false;

    int dlssgMaxInterpolationCount = 1;
    bool dlssgGameDMFGSupported = false;
    bool dlssgOptiDMFGSupported = false;
    sl::DLSSGMode dlssgLastSetMode = sl::DLSSGMode::eOff;
    int dlssgDetectedInterpolationCount = 0;

    // DLSS
    bool dlssPresetsOverriddenExternally = false;
    bool dlssPresetsOverridenByOpti = false;
    uint32_t dlssRenderPresetExternal = 0;
    uint32_t dlssRenderPresetDLAA = 0;
    uint32_t dlssRenderPresetUltraQuality = 0;
    uint32_t dlssRenderPresetQuality = 0;
    uint32_t dlssRenderPresetBalanced = 0;
    uint32_t dlssRenderPresetPerformance = 0;
    uint32_t dlssRenderPresetUltraPerformance = 0;

    // DLSSD
    bool dlssdPresetsOverriddenExternally = false;
    bool dlssdPresetsOverridenByOpti = false;
    uint32_t dlssdRenderPresetExternal = 0;
    uint32_t dlssdRenderPresetDLAA = 0;
    uint32_t dlssdRenderPresetUltraQuality = 0;
    uint32_t dlssdRenderPresetQuality = 0;
    uint32_t dlssdRenderPresetBalanced = 0;
    uint32_t dlssdRenderPresetPerformance = 0;
    uint32_t dlssdRenderPresetUltraPerformance = 0;

    // Spoofing
    bool skipSpoofing = false;
    // For DXVK, it calls DXGI which cause softlock
    bool skipDxgiLoadChecks = false;
    bool skipParentWrapping = false;

    // quirks
    std::vector<std::string> detectedQuirks {};

    // FFX
    std::vector<const char*> ffxUpscalerVersionNames {};
    std::vector<uint64_t> ffxUpscalerVersionIds {};
    std::vector<const char*> ffxFGVersionNames {};
    std::vector<uint64_t> ffxFGVersionIds {};
    std::optional<uint32_t> currentFsr4Preset {};

    // Linux checks
    bool isRunningOnLinux = false;

    // Other checks
    WorkingMode workingMode = WorkingMode::Other;

    // Vulkan stuff
    bool vulkanCreatingSC = false;
    bool creatingD3DDevice = false;
    bool vulkanSkipHooks = false;
    VkInstance VulkanInstance = nullptr;

    // Framegraph
    std::deque<double> upscaleTimes;
    std::deque<double> frameTimes;
    double lastFGFrameTime = 0.0;
    double presentFrameTime = 0.0;
    std::mutex frameTimeMutex;

    // Opti checking if everything is setup correctly on game launch
    // Takes effect up to the first time Opti can show anything on the screen
    // Beyond that use notifications
    bool postDone = false;
    flag_set<PostCode> postCodes;

    // Version check
    std::mutex versionCheckMutex;
    bool versionCheckInProgress = false;
    bool versionCheckCompleted = false;
    bool updateAvailable = false;
    std::string latestVersionTag;
    std::string latestVersionUrl;
    std::string versionCheckError;

    // Swapchain info
    float screenWidth = 800.0;
    float screenHeight = 450.0;
    bool realExclusiveFullscreen = false;
    bool SCExclusiveFullscreen = false;
    bool SCAllowTearing = false;
    UINT SCLastFlags = 0;

    // HDR
    std::vector<IUnknown*> scBuffers;
    bool isHdrActive = false;

    std::optional<ApiUpscalerInput> setInputApiName;
    ApiUpscalerInput currentInputApiName;

    bool isShuttingDown = false;
    std::set<PVOID> modulesToFree;

    // menu warnings
    bool fgSettingsChanged = false;
    bool nvngxIniDetected = false;

    bool nvngxExists = false;
    std::optional<std::wstring> nvngxReplacement = std::nullopt;
    bool libxessExists = false;
    bool fsrHooks = false;

    IFeature* currentFeature = nullptr;
    IFGFeature_Dx12* currentFG = nullptr;
    IDXGISwapChain* currentSwapchain = nullptr;
    IDXGISwapChain* currentWrappedSwapchain = nullptr;
    IDXGISwapChain* currentRealSwapchain = nullptr;
    IDXGISwapChain* currentFGSwapchain = nullptr;
    ID3D12Device* currentD3D12Device = nullptr;
    ID3D11Device* currentD3D11Device = nullptr;
    ID3D12CommandQueue* currentCommandQueue = nullptr;
    VkDevice currentVkDevice = nullptr;
    DXGI_SWAP_CHAIN_DESC currentSwapchainDesc {};

    std::vector<ID3D12Device*> d3d12Devices;
    std::vector<ID3D11Device*> d3d11Devices;

    static UINT GetOwner()
    {
        _lastOwner++;
        return _lastOwner;
    }

    // Moved checks here to prevent circular includes
    /// <summary>
    /// Enables skipping of LoadLibrary checks
    /// </summary>
    /// <param name="dllName">Lower case dll name without `.dll` at the end. Leave blank for skipping all dll's</param>
    static void DisableChecks(UINT owner, std::string dllName = "")
    {
        // if (_skipOwner == 0 || _skipOwner < owner)
        //{
        _skipOwner = owner;
        _skipChecks = true;
        _skipDllName[_skipOwner] = dllName;
        //}
    };

    static void EnableChecks(UINT owner)
    {
        _skipDllName.erase(_skipOwner);

        // if (_skipOwner == owner)
        //{
        if (_skipDllName.size() > 0)
        {
            // loop in reverse to get the last added owner
            _skipOwner = _skipDllName.rbegin()->first;
        }
        else
        {
            _skipOwner = 0;
        }

        _skipChecks = (_skipOwner != 0);
        //}
    };

    static void DisableServeOriginal(UINT owner)
    {
        if (_serveOwner == 0 || _serveOwner == owner)
        {
            _serveOriginal = false;
            _skipOwner = 0;
        }
    };

    static void EnableServeOriginal(UINT owner)
    {
        if (_serveOwner == 0 || _serveOwner == owner)
        {
            _serveOriginal = true;
            _skipOwner = owner;
        }
    };

    static bool SkipDllChecks() { return _skipChecks; }
    static std::string SkipDllName()
    {
        return _skipOwner == 0 ? "" : (_skipDllName.contains(_skipOwner) ? _skipDllName[_skipOwner] : "");
    }
    static bool ServeOriginal() { return _serveOriginal; }

  private:
    inline static bool _skipChecks = false;
    inline static std::map<UINT, std::string> _skipDllName;
    inline static UINT _skipOwner = 0;
    inline static UINT _lastOwner = 0;

    inline static bool _serveOriginal = false;
    inline static UINT _serveOwner = 0;

    State() = default;
};

class ScopedSkipSpoofing
{
  private:
    bool previousState;

  public:
    ScopedSkipSpoofing()
    {
        previousState = State::Instance().skipSpoofing;
        State::Instance().skipSpoofing = true;
    }

    ~ScopedSkipSpoofing() { State::Instance().skipSpoofing = previousState; }
};

class ScopedSkipDxgiLoadChecks
{
  private:
    bool previousState;

  public:
    ScopedSkipDxgiLoadChecks()
    {
        previousState = State::Instance().skipDxgiLoadChecks;
        State::Instance().skipDxgiLoadChecks = true;
    }

    ~ScopedSkipDxgiLoadChecks() { State::Instance().skipDxgiLoadChecks = previousState; }
};

class ScopedSkipParentWrapping
{
  private:
    bool previousState;

  public:
    ScopedSkipParentWrapping()
    {
        previousState = State::Instance().skipParentWrapping;
        State::Instance().skipParentWrapping = true;
    }

    ~ScopedSkipParentWrapping() { State::Instance().skipParentWrapping = previousState; }
};

class ScopedSkipHeapCapture
{
  private:
    bool previousState;

  public:
    ScopedSkipHeapCapture()
    {
        previousState = State::Instance().skipHeapCapture;
        State::Instance().skipHeapCapture = true;
    }

    ~ScopedSkipHeapCapture() { State::Instance().skipHeapCapture = previousState; }
};

class ScopedSkipVulkanHooks
{
  private:
    bool previousState;

  public:
    ScopedSkipVulkanHooks()
    {
        previousState = State::Instance().vulkanSkipHooks;
        State::Instance().vulkanSkipHooks = true;
    }
    ~ScopedSkipVulkanHooks() { State::Instance().vulkanSkipHooks = previousState; }
};

class ScopedVulkanCreatingSC
{
  private:
    bool previousState;

  public:
    ScopedVulkanCreatingSC()
    {
        previousState = State::Instance().vulkanCreatingSC;
        State::Instance().vulkanCreatingSC = true;
    }
    ~ScopedVulkanCreatingSC() { State::Instance().vulkanCreatingSC = previousState; }
};

class ScopedCreatingD3DDevice
{
  private:
    bool previousState;

  public:
    ScopedCreatingD3DDevice()
    {
        previousState = State::Instance().creatingD3DDevice;
        State::Instance().creatingD3DDevice = true;
    }
    ~ScopedCreatingD3DDevice() { State::Instance().creatingD3DDevice = previousState; }
};
