#include "TerrainRenderer.h"
#include "Asset/AssetRegistry.h"
#include "Component/TerrainTile.h"
#include "Graphics/Graphics.h"
#include "Graphics/Pipeline.h"
#include "Graphics/Shader.h"

using namespace Seele;

TerrainRenderer::TerrainRenderer(Gfx::PGraphics graphics, PScene scene, Gfx::PDescriptorLayout viewParamsLayout,
                                 Gfx::PDescriptorSet viewParamsSet)
    : graphics(graphics), scene(scene) {
    Gfx::OPipelineLayout test = graphics->createPipelineLayout();
    graphics->beginShaderCompilation(ShaderCompilationInfo{
        .modules = {"CompileTest"},
        .entryPoints = {{"TestHeap", "CompileTest"}},
        .rootSignature = test,
    });
    graphics->createComputeShader({0});
    meshUpdater.init(graphics, viewParamsLayout);
    lebCache.init(graphics, 5);
    CBT<18> cbt;
    CPUMesh cpuMesh = generateCPUMesh(cbt.numElements());
    plainMesh.gpuCBT.lastLevelSize = cbt.lastLevelSize();
    plainMesh.gpuCBT.bufferCount = 2;
    for (uint32 i = 0; i < 2; ++i) {
        uint32 bufferSize = cbt.bufferSize(i);
        //uint32 elementSize = cbt.elementSize(i);
        plainMesh.gpuCBT.bufferArray[i] = graphics->createShaderBuffer(ShaderBufferCreateInfo{
            .sourceData =
                {
                    .size = bufferSize,
                    .data = (uint8*)cbt.rawBuffer(i),
                },
            .name = fmt::format("GPUCBT{0}", i).c_str(),
        });
        plainMesh.gpuCBT.bufferArray[i]->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT,
                                                         Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT,
                                                         Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
    }
    plainMesh.totalNumElements = cpuMesh.totalNumElements;
    plainMesh.numBaseVertices = (uint32)cpuMesh.basePoints.size();
    plainMesh.baseDepth = cpuMesh.minimalDepth;

    plainMesh.heapIDBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint64) * cpuMesh.totalNumElements,
                .data = (uint8*)cpuMesh.heapIDArray.data(),
            },
        .name = "HeapID",
    });
    plainMesh.heapIDBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT,
                                            Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT,
                                            Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT);

    plainMesh.currentNeighborsBufferIdx = 0;
    plainMesh.neighborsBuffers[0] = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(UVector4) * cpuMesh.totalNumElements,
                .data = (uint8*)cpuMesh.neighborsArray.data(),
            },
        .name = "Neighbours0",
    });
    plainMesh.neighborsBuffers[0]->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT,
                                                   Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT,
                                                   Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
    plainMesh.neighborsBuffers[1] = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(UVector4) * cpuMesh.totalNumElements,
            },
        .name = "Neighbours1",
    });
    plainMesh.neighborsBuffers[1]->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT,
                                                   Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT,
                                                   Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT);

    plainMesh.updateBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(BisectorData) * cpuMesh.totalNumElements,
            },
        .name = "UpdateBuffer",
    });
    plainMesh.classificationBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32) * (2 + cpuMesh.totalNumElements * 2),
            },
        .name = "Classification",
    });
    plainMesh.simplificationBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32) * (1 + cpuMesh.totalNumElements),
            },
        .name = "Simplification",
    });
    plainMesh.allocateBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32) * (1 + cpuMesh.totalNumElements),
            },
        .name = "Allocation",
    });
    plainMesh.propagateBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32) * (2 + cpuMesh.totalNumElements),
            },
        .name = "Propagate",
    });

    plainMesh.indirectDrawBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32) * (4 * 2 + 2),
            },
        .usage = Gfx::SE_BUFFER_USAGE_INDIRECT_BUFFER_BIT,
        .name = "IndirectDraw",
    });
    plainMesh.indirectDispatchBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32) * 3 * 3,
            },
        .usage = Gfx::SE_BUFFER_USAGE_INDIRECT_BUFFER_BIT,
        .name = "IndirectDispatch",
    });
    plainMesh.indexedBisectorBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32) * cpuMesh.totalNumElements,
            },
        .name = "IndexedBisector",
    });
    plainMesh.visibleIndexedBisectorBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32) * cpuMesh.totalNumElements,
            },
        .name = "VisibleIndexedBisector",
    });
    plainMesh.modifiedIndexedBisectorBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32) * cpuMesh.totalNumElements,
            },
        .name = "ModifiedIndexedBisector",
    });

    plainMesh.lebVertexBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(Vector4) * plainMesh.totalNumElements * 4,
            },
        .name = "LebVertexBuffer",
    });
    plainMesh.lebVertexBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT,
                                               Gfx::SE_ACCESS_SHADER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
    plainMesh.currentVertexBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(Vector4) * plainMesh.totalNumElements * 4,
            },
        .name = "CurrentVertexBuffer",
    });
    plainMesh.currentDisplacementBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(Vector4) * plainMesh.totalNumElements * 3,
            },
        .name = "CurrentDisplacementBuffer",
    });
    uint32 numBaseVertex = (uint32)cpuMesh.basePoints.size();
    baseMesh.numVertices = numBaseVertex;
    baseMesh.numElements = numBaseVertex / 3;
    uint32 baseVertexBufferSize = sizeof(Vector4) * numBaseVertex;
    baseMesh.vertexBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = baseVertexBufferSize,
                .data = (uint8*)cpuMesh.basePoints.data(),
            },
        .name = "VertexBuffer",
    });
    baseMesh.vertexBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT,
                                           Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT,
                                           Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
    //uint32 indexBufferSize = cpuMesh.totalNumElements * sizeof(UVector);
    Array<uint32> indices;
    for (uint32 i = 0; i < cpuMesh.totalNumElements * 3; ++i)
        indices.add(i);
    baseMesh.indexBuffer = graphics->createIndexBuffer(IndexBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32) * indices.size(),
                .data = (uint8*)indices.data(),
            },
        .indexType = Gfx::SE_INDEX_TYPE_UINT32,
        .name = "IndexBuffer",
    });
    baseMesh.indexBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT,
                                          Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT,
                                          Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
    GeometryCB geometryCB = {
        .totalNumElements = plainMesh.totalNumElements,
        .baseDepth = plainMesh.baseDepth,
        .totalNumVertices = plainMesh.totalNumElements * 3,
    };
    geometryBuffer = graphics->createUniformBuffer(UniformBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(GeometryCB),
                .data = (uint8*)&geometryCB,
            },
        .name = "GeometryCB",
    });
    geometryBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, Gfx::SE_ACCESS_UNIFORM_READ_BIT,
                                    Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
    UpdateCB updateCB = {
        .triangleSize = 60.0f,
        .maxSubdivisionDepth = 63,
        .fov = 1.22173f,
        .farPlaneDistance = 1000.0f,
    };
    updateBuffer = graphics->createUniformBuffer(UniformBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(UpdateCB),
                .data = (uint8*)&updateCB,
            },
        .name = "UpdateCB",
    });
    updateBuffer->pipelineBarrier(Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, Gfx::SE_ACCESS_UNIFORM_READ_BIT,
                                  Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
    graphics->beginShaderCompilation(ShaderCompilationInfo{
        .modules = {"TerrainPass"},
        .entryPoints =
            {
                {"vert", "TerrainPass"},
                {"frag", "TerrainPass"},
                {"EvaluateDeformation", "TerrainPass"},
            },
        .rootSignature = meshUpdater.pipelineLayout,
    });
    vert = graphics->createVertexShader({0});
    frag = graphics->createFragmentShader({1});
    deformCS = graphics->createComputeShader({2});
    deform = graphics->createComputePipeline(Gfx::ComputePipelineCreateInfo{
        .computeShader = deformCS,
        .pipelineLayout = meshUpdater.pipelineLayout,
    });
    sampler = graphics->createSampler({});
    displacementAsset = AssetRegistry::findTexture("", "wgen")->getTexture().cast<Gfx::Texture2D>();
    meshUpdater.resetBuffers(plainMesh);
    meshUpdater.prepareIndirection(plainMesh, geometryBuffer);
    meshUpdater.evaluateLeb(baseMesh, plainMesh, viewParamsSet, geometryBuffer, updateBuffer, lebCache.getLebMatrixBuffer(), true, true);
    applyDeformation(viewParamsSet);
}

TerrainRenderer::~TerrainRenderer() {}

static bool first = true;

void TerrainRenderer::beginFrame(Gfx::PDescriptorSet viewParamsSet, const Component::Camera& cam) {
    meshUpdater.update(plainMesh, viewParamsSet, geometryBuffer, updateBuffer);
    meshUpdater.evaluateLeb(baseMesh, plainMesh, viewParamsSet, geometryBuffer, updateBuffer, lebCache.getLebMatrixBuffer(), false, false);
    applyDeformation(viewParamsSet);
}

Gfx::ORenderCommand TerrainRenderer::render(Gfx::PDescriptorSet viewParamsSet) {
    Gfx::PDescriptorSet set = meshUpdater.layout->allocateDescriptorSet();
    /*set->updateBuffer(CURRENT_VERTEX_BUFFER, 0, plainMesh.currentVertexBuffer);
    set->updateBuffer(INDEXED_BISECTOR_BUFFER, 0, plainMesh.indexedBisectorBuffer);
    set->updateSampler(SAMPLER_LOCATION, 0, sampler);
    set->updateTexture(DISPLACEMENT_MAP, 0, displacementAsset);*/
    set->writeChanges();
    Gfx::ORenderCommand command = graphics->createRenderCommand("TerrainRender");
    command->setViewport(viewport);
    command->bindPipeline(pipeline);
    command->bindDescriptor({viewParamsSet, set});
    command->drawIndirect(plainMesh.indirectDrawBuffer, 0, 1, 0);
    return command;
}

void TerrainRenderer::setViewport(Gfx::PViewport _viewport, Gfx::PRenderPass renderPass) {
    viewport = _viewport;
    inp = graphics->createVertexInput({});
    Gfx::LegacyPipelineCreateInfo pipelineInfo = {
        .vertexInput = inp,
        .vertexShader = vert,
        .fragmentShader = frag,
        .renderPass = renderPass,
        .pipelineLayout = meshUpdater.pipelineLayout,
        .multisampleState =
            {
                .samples = Gfx::SE_SAMPLE_COUNT_1_BIT,
            },
        .rasterizationState =
            {
                .polygonMode = Gfx::SE_POLYGON_MODE_LINE,
                .cullMode = Gfx::SE_CULL_MODE_NONE,
            },
        .colorBlend =
            {
                .attachmentCount = 1,
                .blendAttachments =
                    {
                        Gfx::ColorBlendState::BlendAttachment{
                            .blendEnable = false,
                        },
                    },
            },

    };
    pipeline = graphics->createGraphicsPipeline(std::move(pipelineInfo));
}

void TerrainRenderer::applyDeformation(Gfx::PDescriptorSet viewParamsSet) {
    graphics->beginDebugRegion("ApplyDeformation");
    Gfx::PDescriptorSet set = meshUpdater.layout->allocateDescriptorSet();
    /*set->updateBuffer(GEOMETRY_CB, 0, geometryBuffer);
    set->updateBuffer(INDIRECT_DRAW_BUFFER, 0, plainMesh.indirectDrawBuffer);
    set->updateBuffer(INDEXED_BISECTOR_BUFFER, 0, plainMesh.indexedBisectorBuffer);
    set->updateBuffer(LEB_POSITION_BUFFER, 0, plainMesh.lebVertexBuffer);
    set->updateBuffer(CURRENT_VERTEX_BUFFER, 0, plainMesh.currentVertexBuffer);*/
    set->writeChanges();
    Gfx::OComputeCommand command = graphics->createComputeCommand("Deform");
    command->bindPipeline(deform);
    command->bindDescriptor({viewParamsSet, set});
    command->dispatchIndirect(plainMesh.indirectDispatchBuffer, 3 * sizeof(uint32));
    graphics->executeCommands(std::move(command));
    plainMesh.currentVertexBuffer->pipelineBarrier(Gfx::SE_ACCESS_SHADER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
                                                   Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_VERTEX_SHADER_BIT);
    graphics->endDebugRegion();
}
