#pragma once

#include <Core/Common/NonNullPtr.h>
#include <Graphics/Graphics.h>
#include <Graphics/GraphicsConfig.h>
#include <Graphics/GraphicsPipeline.h>
#include <Graphics/Texture.h>

namespace Juliet
{
    struct TextureHeader
    {
        TextureCreateInfo CreateInfo;
    };

    struct GPUPass
    {
        CommandList* CommandList;
        bool         IsInProgress : 1;
    };

    struct CommandListHeader
    {
        GraphicsDevice* Device            = nullptr;
        bool            AcquiredSwapChain = false;
        bool            Submitted         = false;

        GPUPass RenderPass;
    };

    struct GPUDriver
    {
        Arena* DriverArena = {};

        static constexpr uint8 kMaxFramesInFlight         = 3;
        static constexpr uint8 kMaxColorTargetInfo        = 4;
        static constexpr uint8 kMaxUniformBuffersPerStage = 4;
        static constexpr uint8 kMaxVertexBuffers          = 16;

        static constexpr uint32 kCBV_SRV_UAV_HeapDescriptorCount = 65536;
        static constexpr uint32 kSampler_HeapDescriptorCount     = 2048;
    };

    struct GraphicsDevice
    {
        void (*DestroyDevice)(NonNullPtr<GraphicsDevice> self);

        // Attach to window
        bool (*AttachToWindow)(NonNullPtr<GPUDriver> driver, NonNullPtr<Window> window);
        void (*DetachFromWindow)(NonNullPtr<GPUDriver> driver, NonNullPtr<Window> window);

        // SwapChain
        bool (*AcquireSwapChainTexture)(NonNullPtr<CommandList> commandList, NonNullPtr<Window> window, Texture** swapChainTexture);
        bool (*WaitAndAcquireSwapChainTexture)(NonNullPtr<CommandList> commandList, NonNullPtr<Window> window, Texture** swapChainTexture);
        bool (*WaitForSwapchain)(NonNullPtr<GPUDriver> driver, NonNullPtr<Window> window);
        TextureFormat (*GetSwapChainTextureFormat)(NonNullPtr<GPUDriver> driver, NonNullPtr<Window> window);

        // CommandLists
        CommandList* (*AcquireCommandList)(NonNullPtr<GPUDriver> driver, QueueType queueType);
        bool (*SubmitCommandLists)(NonNullPtr<CommandList> commandList);

        // RenderPass
        void (*BeginRenderPass)(NonNullPtr<CommandList> commandList, NonNullPtr<const ColorTargetInfo> colorTargetInfos,
                                uint32 colorTargetInfoCount, const DepthStencilTargetInfo* depthStencilTargetInfo);
        void (*EndRenderPass)(NonNullPtr<CommandList> commandList);

        void (*SetViewPort)(NonNullPtr<CommandList> commandList, const GraphicsViewPort& viewPort);
        void (*SetScissorRect)(NonNullPtr<CommandList> commandList, const Rectangle& viewPort);
        void (*SetBlendConstants)(NonNullPtr<CommandList> commandList, FColor blendConstants);
        void (*SetStencilReference)(NonNullPtr<CommandList> commandList, uint8 reference);
        void (*SetIndexBuffer)(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer,
                               IndexFormat format, size_t indexCount, index_t offset);

        void (*BindGraphicsPipeline)(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsPipeline> graphicsPipeline);
        void (*DrawPrimitives)(NonNullPtr<CommandList> commandList, uint32 numVertices, uint32 numInstances,
                               uint32 firstVertex, uint32 firstInstance);
        void (*DrawIndexedPrimitives)(NonNullPtr<CommandList> commandList, uint32 numIndices, uint32 numInstances,
                                      uint32 firstIndex, uint32 vertexOffset, uint32 firstInstance);

        void (*SetPushConstants)(NonNullPtr<CommandList> commandList, ShaderStage stage, uint32 rootParameterIndex,
                                 uint32 numConstants, const void* constants);

        // Fences
        bool (*WaitUntilGPUIsIdle)(NonNullPtr<GPUDriver> driver);
        bool (*QueryFence)(NonNullPtr<GPUDriver> driver, NonNullPtr<Fence> fence);
        void (*ReleaseFence)(NonNullPtr<GPUDriver> driver, NonNullPtr<Fence> fence JULIET_DEBUG_PARAM(String querier));

        // Shaders
        Shader* (*CreateShader)(NonNullPtr<GPUDriver> driver, ByteBuffer shaderByteCode, ShaderCreateInfo& shaderCreateInfo);
        void (*DestroyShader)(NonNullPtr<GPUDriver> driver, NonNullPtr<Shader> shader);

        // Pipeline
        GraphicsPipeline* (*CreateGraphicsPipeline)(NonNullPtr<GPUDriver> driver, const GraphicsPipelineCreateInfo& createInfo);
        void (*DestroyGraphicsPipeline)(NonNullPtr<GPUDriver> driver, NonNullPtr<GraphicsPipeline> pipeline);
        bool (*UpdateGraphicsPipelineShaders)(NonNullPtr<GPUDriver> driver, NonNullPtr<GraphicsPipeline> graphicsPipeline,
                                              Shader* optional_vertexShader, Shader* optional_fragmentShader);

        // Textures
        Texture* (*CreateTexture)(NonNullPtr<GPUDriver> driver, const TextureCreateInfo& createInfo);
        void (*DestroyTexture)(NonNullPtr<GPUDriver> driver, NonNullPtr<Texture> texture);

        // Buffers
        GraphicsBuffer* (*CreateGraphicsBuffer)(NonNullPtr<GPUDriver> driver, size_t size, size_t stride, BufferUsage usage, bool isDynamic);
        void (*DestroyGraphicsBuffer)(NonNullPtr<GraphicsBuffer> buffer);

        void* (*MapGraphicsBuffer)(NonNullPtr<GPUDriver> driver, NonNullPtr<GraphicsBuffer> buffer);
        void (*UnmapGraphicsBuffer)(NonNullPtr<GPUDriver> driver, NonNullPtr<GraphicsBuffer> buffer);

        GraphicsTransferBuffer* (*CreateGraphicsTransferBuffer)(NonNullPtr<GPUDriver> driver, size_t size, TransferBufferUsage usage);
        void (*DestroyGraphicsTransferBuffer)(NonNullPtr<GraphicsTransferBuffer> buffer);

        void* (*MapGraphicsTransferBuffer)(NonNullPtr<GPUDriver> driver, NonNullPtr<GraphicsTransferBuffer> buffer);
        void (*UnmapGraphicsTransferBuffer)(NonNullPtr<GPUDriver> driver, NonNullPtr<GraphicsTransferBuffer> buffer);
        void (*CopyBuffer)(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> dst,
                           NonNullPtr<GraphicsTransferBuffer> src, size_t size, size_t dstOffset, size_t srcOffset);
        void (*CopyBufferToTexture)(NonNullPtr<CommandList> commandList, NonNullPtr<Texture> dst,
                                    NonNullPtr<GraphicsTransferBuffer> src); // Assume full copy for now

        void (*TransitionBufferToReadable)(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer);
        uint32 (*GetDescriptorIndex)(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
        uint32 (*GetDescriptorIndexTexture)(NonNullPtr<GraphicsDevice> device, NonNullPtr<Texture> texture);

        const char* Name   = "Unknown";
        GPUDriver*  Driver = nullptr;
        bool        DebugEnabled : 1;
    };

    struct GraphicsDeviceFactory
    {
        const char* Name = "Unknown";
        DriverType  Type = DriverType::Any;
        bool (*CheckDriver)(void);
        GraphicsDevice* (*CreateGraphicsDevice)(bool enableDebug);
    };

    extern GraphicsDeviceFactory DX12DeviceFactory;
} // namespace Juliet
