#pragma once

#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Core/HAL/Display/Display.h>
#include <Core/Math/Shape.h>
#include <Graphics/GraphicsBuffer.h>
#include <Graphics/GraphicsConfig.h>
#include <Graphics/GraphicsPipeline.h>
#include <Graphics/RenderPass.h>
#include <Graphics/Shader.h>
#include <Juliet.h>

#if JULIET_DEBUG
#define ALLOW_SHADER_HOT_RELOAD 1
#else
#define ALLOW_SHADER_HOT_RELOAD 0
#endif

// Graphics Interface
namespace Juliet
{
    // Opaque types
    struct CommandList;
    struct GraphicsDevice;
    struct Fence;

    // Parameters of an indirect draw command
    struct IndirectDrawCommand
    {
        uint32 VertexCount;   // Number of vertices to draw
        uint32 InstanceCount; // Number of instanced to draw
        uint32 FirstVertex;   // Index of the first vertex to draw
        uint32 FirstInstance; // ID of the first instance to draw
    };

    // Parameters of an INDEXED indirect draw command
    struct IndexedIndirectDrawCommand
    {
        uint32 VertexCount;   // Number of vertices to draw
        uint32 InstanceCount; // Number of instanced to draw
        uint32 FirstIndex;    // Base Index within the index buffer
        int32  VertexOffset;  // Offset the vertex index into the buffer
        uint32 FirstInstance; // ID of the first instance to draw
    };

    // Parameters of an INDEXED Indirect Dispatch Command
    struct IndirectDispatchCommand
    {
        uint32 X_WorkGroupCount; // Number of Workgroup to dispatch on dimension X
        uint32 Y_WorkGroupCount; // Number of Workgroup to dispatch on dimension Y
        uint32 Z_WorkGroupCount; // Number of Workgroup to dispatch on dimension Z
    };

    enum class QueueType : uint8
    {
        Graphics = 0,
        Compute,
        Copy,
        Count
    };

    enum class IndexFormat : uint8
    {
        UInt16,
        UInt32
    };

    enum struct SwapChainComposition : uint8
    {
        SDR,
        SDR_LINEAR,
        HDR_EXTENDED_LINEAR,
        HDR10_ST2084
    };

    // PresentMode from highest to lowest latency
    // Vsync prevents tearing. Enqueue ready images.
    // Mailbox prevents tearing. When image is ready, replace any pending image
    // Immediate replace current image as soon as possible. Can cause tearing
    enum struct PresentMode : uint8
    {
        VSync,
        Mailbox,
        Immediate
    };

    struct GraphicsViewPort
    {
        float X;
        float Y;
        float Width;
        float Height;
        float MinDepth;
        float MaxDepth;
    };

    extern JULIET_API GraphicsDevice* CreateGraphicsDevice(GraphicsConfig config);
    extern JULIET_API void            DestroyGraphicsDevice(NonNullPtr<GraphicsDevice> device);

    // Attach To Window
    extern JULIET_API bool AttachToWindow(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
    extern JULIET_API void DetachFromWindow(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);

    // SwapChain
    extern JULIET_API bool AcquireSwapChainTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Window> window,
                                                   Texture** swapChainTexture);
    extern JULIET_API bool WaitAndAcquireSwapChainTexture(NonNullPtr<CommandList> commandList,
                                                          NonNullPtr<Window> window, Texture** swapChainTexture);
    extern JULIET_API bool WaitForSwapchain(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
    extern JULIET_API TextureFormat GetSwapChainTextureFormat(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);

    // Textures
    extern JULIET_API Texture* CreateTexture(NonNullPtr<GraphicsDevice> device, const TextureCreateInfo& createInfo);
    extern JULIET_API void     DestroyTexture(NonNullPtr<GraphicsDevice> device, NonNullPtr<Texture> texture);

    // Command List
    extern JULIET_API CommandList* AcquireCommandList(NonNullPtr<GraphicsDevice> device, QueueType queueType = QueueType::Graphics);
    extern JULIET_API void SubmitCommandLists(NonNullPtr<CommandList> commandList);

    // RenderPass
    extern JULIET_API RenderPass* BeginRenderPass(NonNullPtr<CommandList> commandList, ColorTargetInfo& colorTargetInfo,
                                                  DepthStencilTargetInfo* depthStencilTargetInfo = nullptr);
    extern JULIET_API RenderPass* BeginRenderPass(NonNullPtr<CommandList> commandList,
                                                  NonNullPtr<const ColorTargetInfo> colorTargetInfos, uint32 colorTargetInfoCount,
                                                  DepthStencilTargetInfo* depthStencilTargetInfo = nullptr);
    extern JULIET_API void        EndRenderPass(NonNullPtr<RenderPass> renderPass);

    extern JULIET_API void SetGraphicsViewPort(NonNullPtr<RenderPass> renderPass, const GraphicsViewPort& viewPort);
    extern JULIET_API void SetScissorRect(NonNullPtr<RenderPass> renderPass, const Rectangle& rectangle);
    extern JULIET_API void SetBlendConstants(NonNullPtr<RenderPass> renderPass, FColor blendConstants);
    extern JULIET_API void SetStencilReference(NonNullPtr<RenderPass> renderPass, uint8 reference);

    extern JULIET_API void BindGraphicsPipeline(NonNullPtr<RenderPass> renderPass, NonNullPtr<GraphicsPipeline> graphicsPipeline);
    extern JULIET_API void DrawPrimitives(NonNullPtr<RenderPass> renderPass, uint32 numVertices, uint32 numInstances,
                                          uint32 firstVertex, uint32 firstInstance);
    extern JULIET_API void DrawIndexedPrimitives(NonNullPtr<RenderPass> renderPass, uint32 numIndices, uint32 numInstances,
                                                 uint32 firstIndex, uint32 vertexOffset, uint32 firstInstance);

    extern JULIET_API void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer,
                                          IndexFormat format, size_t indexCount, index_t offset);

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

    // Fences
    extern JULIET_API bool WaitUntilGPUIsIdle(NonNullPtr<GraphicsDevice> device);

    // Shaders
    extern JULIET_API Shader* CreateShader(NonNullPtr<GraphicsDevice> device, String filename, ShaderCreateInfo& shaderCreateInfo);
    extern JULIET_API void DestroyShader(NonNullPtr<GraphicsDevice> device, NonNullPtr<Shader> shader);

    // Pipelines
    extern JULIET_API GraphicsPipeline* CreateGraphicsPipeline(NonNullPtr<GraphicsDevice>        device,
                                                               const GraphicsPipelineCreateInfo& createInfo);
    extern JULIET_API void DestroyGraphicsPipeline(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsPipeline> graphicsPipeline);
#if ALLOW_SHADER_HOT_RELOAD
    // Allows updating the graphics pipeline shaders. Can update either one or both shaders.
    extern JULIET_API bool UpdateGraphicsPipelineShaders(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsPipeline> graphicsPipeline,
                                                         Shader* optional_vertexShader, Shader* optional_fragmentShader);
#endif

    // Buffers
    extern JULIET_API GraphicsBuffer* CreateGraphicsBuffer(NonNullPtr<GraphicsDevice> device, const BufferCreateInfo& createInfo);
    extern JULIET_API GraphicsTransferBuffer* CreateGraphicsTransferBuffer(NonNullPtr<GraphicsDevice>      device,
                                                                           const TransferBufferCreateInfo& createInfo);
    extern JULIET_API void* MapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
    extern JULIET_API void  UnmapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
    extern JULIET_API void* MapGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer);
    extern JULIET_API void UnmapGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer);
    extern JULIET_API void CopyBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> dst,
                                      NonNullPtr<GraphicsTransferBuffer> src, size_t size, size_t dstOffset = 0,
                                      size_t srcOffset = 0);
    extern JULIET_API void CopyBufferToTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Texture> dst,
                                               NonNullPtr<GraphicsTransferBuffer> src);

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

    extern JULIET_API void DestroyGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
    extern JULIET_API void DestroyGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer);
} // namespace Juliet
