#include <Graphics/SkyboxRenderer.h>

#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Graphics/GraphicsDevice.h>
#include <Graphics/PushConstants.h>

namespace Juliet
{
    namespace
    {
        SkyboxRenderer g_SkyboxRenderer;
    } // namespace

    bool InitializeSkyboxRenderer(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window)
    {
        bool result = true;

        GraphicsDevice* graphicsDevice = g_SkyboxRenderer.Device = device.Get();

        String skyboxVSEntry = WrapString("main");
        ShaderCreateInfo skyboxVSCI = {};
        skyboxVSCI.EntryPoint = skyboxVSEntry;
        skyboxVSCI.Stage = ShaderStage::Vertex;
        String vsPath = GetAssetPath(WrapString("Skybox.vert.dxil"));
        Shader* skyboxVS = CreateShader(graphicsDevice, vsPath, skyboxVSCI);

        String skyboxFSEntry = WrapString("main");
        ShaderCreateInfo skyboxFSCI = {};
        skyboxFSCI.EntryPoint = skyboxFSEntry;
        skyboxFSCI.Stage = ShaderStage::Fragment;
        String fsPath = GetAssetPath(WrapString("Skybox.frag.dxil"));
        Shader* skyboxFS = CreateShader(graphicsDevice, fsPath, skyboxFSCI);

        ColorTargetDescription colorTargetDesc = {};
        colorTargetDesc.Format = GetSwapChainTextureFormat(graphicsDevice, window);

        GraphicsPipelineCreateInfo skyboxPipelineCI = {};
        skyboxPipelineCI.VertexShader = skyboxVS;
        skyboxPipelineCI.FragmentShader = skyboxFS;
        skyboxPipelineCI.PrimitiveType = PrimitiveType::TriangleList;
        skyboxPipelineCI.TargetInfo.ColorTargetDescriptions = &colorTargetDesc;
        skyboxPipelineCI.TargetInfo.NumColorTargets = 1;
        skyboxPipelineCI.TargetInfo.DepthStencilFormat = TextureFormat::D32_FLOAT;
        skyboxPipelineCI.TargetInfo.HasDepthStencilTarget = true;
        skyboxPipelineCI.RasterizerState.FillMode = FillMode::Solid;
        skyboxPipelineCI.RasterizerState.CullMode = CullMode::None;
        skyboxPipelineCI.RasterizerState.FrontFace = FrontFace::Clockwise;
        skyboxPipelineCI.DepthStencilState.EnableDepthTest = true;
        skyboxPipelineCI.DepthStencilState.EnableDepthWrite = false;
        skyboxPipelineCI.DepthStencilState.CompareOperation = CompareOperation::LessOrEqual;

        g_SkyboxRenderer.Pipeline = CreateGraphicsPipeline(graphicsDevice, skyboxPipelineCI);
        if (g_SkyboxRenderer.Pipeline == nullptr)
        {
            LogError(LogCategory::Graphics, "Failed to create skybox pipeline!");
            result = false;
        }

        if (skyboxVS) DestroyShader(graphicsDevice, skyboxVS);
        if (skyboxFS) DestroyShader(graphicsDevice, skyboxFS);

        return result;
    }

    void ShutdownSkyboxRenderer()
    {
        if (g_SkyboxRenderer.Pipeline)
        {
            DestroyGraphicsPipeline(g_SkyboxRenderer.Device, g_SkyboxRenderer.Pipeline);
            g_SkyboxRenderer.Pipeline = nullptr;
        }

        g_SkyboxRenderer = {};
    }

    void RenderSkybox(NonNullPtr<RenderPass> pass, NonNullPtr<CommandList> cmdList, const Matrix& viewProjection)
    {
        if (!g_SkyboxRenderer.Pipeline)
        {
            return;
        }

        PushData pushData              = {};
        pushData.ViewProjection        = viewProjection;
        pushData.MeshIndex             = 0;
        pushData.TransformsBufferIndex = 0;
        pushData.BufferIndex           = 0;

        BindGraphicsPipeline(pass, g_SkyboxRenderer.Pipeline);
        SetPushConstants(cmdList, ShaderStage::Vertex, 0, sizeof(pushData) / 4, &pushData);
        SetPushConstants(cmdList, ShaderStage::Fragment, 0, sizeof(pushData) / 4, &pushData);
        DrawPrimitives(pass, 3, 1, 0, 0);
    }

#if ALLOW_SHADER_HOT_RELOAD
    void ReloadSkyboxShaders()
    {
        // TODO
    }
#endif

} // namespace Juliet
