/*
	GameEngine and Editor
	by John Ryland
	Copyright (c) 2023
*/

////////////////////////////////////////////////////////////////////////////////////
//	Render View

// 1459
// 400

#include "RenderView.h"
#include "Image.h"
#include "imgui.h"
#include "VulkanRenderer.h"
#include "Vulkan.h"
#include "Model.h"

////////////////////////////////////////////////////////////////////////////////////
// Begin Rendering Code

#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/hash.hpp>

#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader.h>

#include <cstddef>
#include <array>
#include <chrono>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <unordered_map>
#include <vector>

const uint32_t WIDTH  = 800;
const uint32_t HEIGHT = 600;

// #define RESOURCES_PATH        "../vulkan/"
//#define MODEL_PATH            "models/viking_room.obj"
//#define TEXTURE_PATH          "textures/viking_room.png"

#define RESOURCES_PATH        "../GameEngine/resources/"
#define MODEL_PATH            "models/cube.obj"
#define TEXTURE_PATH          "textures/cube.png"
#define VERTEX_SHADER_PATH    "shaders/vert.spv"
#define FRAGMENT_SHADER_PATH  "shaders/frag.spv"

const int MAX_FRAMES_IN_FLIGHT = 3;

//struct Mat4 { alignas(16) glm::highp_f64mat4 value; };
struct Mat4 { alignas(16) glm::mat4 value; };

struct UniformBufferObject
{
    Mat4 m_model;
    Mat4 m_view;
    Mat4 m_projection;
};

template <size_t SIZE>
struct InFlightFrameData
{
    Vulkan::Buffer        m_uniformBuffers[SIZE];
    UniformBufferObject*  m_mappedUniformBuffers[SIZE];
    VkDescriptorSet       m_descriptorSets[SIZE];
    VkCommandBuffer       m_commandBuffers[SIZE];
    VkFence               m_fences[SIZE];
};

class ExampleRenderer
{
public:
    void Initialize(Vulkan::Device* device)
    {
        m_device = device;

        // We are rendering to an offscreen texture so we need to say that we want to also sample the color buffer after we have rendered in to it
        VkImageUsageFlags colorBufferUsage = /* VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | */ VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
        VkFormat candidateDepthFormats[] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT };
        VkFormat depthFormat = m_device->FindFormat(candidateDepthFormats, std::size(candidateDepthFormats), VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
        uint32_t width = 512;
        uint32_t height = 512;
        Vulkan::Shader vertShader(m_device, RESOURCES_PATH VERTEX_SHADER_PATH);
        Vulkan::Shader fragShader(m_device, RESOURCES_PATH FRAGMENT_SHADER_PATH);

        m_model.Load(RESOURCES_PATH MODEL_PATH);
        m_renderPass.Initialize(m_device, VK_FORMAT_R8G8B8A8_UNORM, depthFormat, VK_SAMPLE_COUNT_1_BIT);
        m_pipeline.m_pipelineFlags = 0;
        m_pipeline.m_descriptorSetLayout = &m_device->m_descriptorSetLayout;
        m_pipeline.m_renderPass = m_renderPass.m_renderPass;
        m_pipeline.m_subpass = 0;
        m_pipeline.m_vertexBindingDescriptions = m_model.GetVertexBindingDescriptions();
        m_pipeline.m_vertexAttributeDescriptions = m_model.GetVertexAttributeDescriptions();
        m_pipeline.CreateLayout(m_device);
        m_pipeline.Initialize(m_device, vertShader.m_module, fragShader.m_module, VK_SAMPLE_COUNT_1_BIT);
        m_colorBuffer.Initialize(m_device, width, height, colorBufferUsage);
        m_depthBuffer.Initialize(m_device, width, height, depthFormat);
        m_framebuffer.Initialize(m_device, m_renderPass.m_renderPass, &m_colorBuffer, &m_depthBuffer);
        m_texture.Initialize(m_device, RESOURCES_PATH TEXTURE_PATH);

        createVertexBuffer();
        createIndexBuffer();
        createCommandBuffers();
        createUniformBuffers();
        createDescriptorSets();
        createSyncObjects();
    }

    void Shutdown()
    {
        vkDeviceWaitIdle(m_device->m_device);

        // The color buffer is managed by the main Renderer now so don't delete it
        m_colorBuffer.m_view = nullptr;

        m_colorBuffer.Destroy();
        m_depthBuffer.Destroy();
        m_pipeline.Destroy();
        m_renderPass.Destroy();

        for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
            inFlightData.m_uniformBuffers[i].Destroy();

        m_texture.Destroy();
        m_indexBuffer.Destroy();
        m_vertexBuffer.Destroy();

        for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
            vkDestroyFence(m_device->m_device, inFlightData.m_fences[i], nullptr);
    }

    void Update()
    {
        vkWaitForFences(m_device->m_device, 1, &inFlightData.m_fences[currentFrame], VK_TRUE, UINT64_MAX);
        updateUniformBuffer();
        vkResetFences(m_device->m_device, 1, &inFlightData.m_fences[currentFrame]);
        vkResetCommandBuffer(inFlightData.m_commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0);
        recordCommandBuffer(inFlightData.m_commandBuffers[currentFrame]);

        VkSubmitInfo submitInfo {};
        submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
        submitInfo.commandBufferCount = 1;
        submitInfo.pCommandBuffers    = &inFlightData.m_commandBuffers[currentFrame];
        m_device->CheckResult(vkQueueSubmit(m_device->m_graphicsQueue, 1, &submitInfo, inFlightData.m_fences[currentFrame]), "failed to submit draw command buffer!");

        currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
    }

    VkImageView ImageView()
    {
        return m_colorBuffer.m_view;
    }

private:
    // Context
    Vulkan::Device*              m_device;       // TODO: rename Device -> Context
    Vulkan::Pipeline             m_pipeline;
    Vulkan::RenderPass           m_renderPass;

    // Framebuffer
    Vulkan::ImageBuffer          m_colorBuffer;
    Vulkan::DepthBuffer          m_depthBuffer;
    Vulkan::Framebuffer          m_framebuffer;

    // Scene
    Vulkan::Texture              m_texture;
    Vulkan::Buffer               m_vertexBuffer;
    Vulkan::Buffer               m_indexBuffer;

    // Data
    Render::Model                m_model;

    // Original - per swap-chain frame data
    uint32_t                                 currentFrame = 0;
    InFlightFrameData<MAX_FRAMES_IN_FLIGHT>  inFlightData;

    void createVertexBuffer()
    {
        VkDeviceSize bufferSize;
        const uint8_t* data;
        m_model.GetVertexData(data, bufferSize);
        m_vertexBuffer.Initialize(m_device, bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
        m_vertexBuffer.UploadDataUsingStagingBuffer(data);
    }

    void createIndexBuffer()
    {
        VkDeviceSize bufferSize;
        const uint8_t* data;
        m_model.GetIndexData(data, bufferSize);
        m_indexBuffer.Initialize(m_device, bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
        m_indexBuffer.UploadDataUsingStagingBuffer(data);
    }

    void createCommandBuffers()
    {
        VkCommandBufferAllocateInfo allocInfo {};
        allocInfo.sType              = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
        allocInfo.commandPool        = m_device->m_commandPool;
        allocInfo.level              = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
        allocInfo.commandBufferCount = std::size(inFlightData.m_commandBuffers);
        m_device->CheckResult(vkAllocateCommandBuffers(m_device->m_device, &allocInfo, inFlightData.m_commandBuffers), "failed to allocate command buffers!");
    }

    void createUniformBuffers()
    {
        VkDeviceSize bufferSize = sizeof(UniformBufferObject);
        for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
        {
            inFlightData.m_uniformBuffers[i].Initialize(m_device, bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT);
            vkMapMemory(m_device->m_device, inFlightData.m_uniformBuffers[i].m_memory, 0, bufferSize, 0, (void**)&inFlightData.m_mappedUniformBuffers[i]);
        }
    }

    void createSyncObjects()
    {
        VkFenceCreateInfo fenceInfo {};
        fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
        fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
        for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
            m_device->CheckResult(vkCreateFence(m_device->m_device, &fenceInfo, nullptr, &inFlightData.m_fences[i]), "failed to create synchronization objects for a frame!");
    }

    void createDescriptorSets()
    {
        VkDescriptorSetLayout layouts[MAX_FRAMES_IN_FLIGHT];
        for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
            layouts[i] = m_device->m_descriptorSetLayout;

        VkDescriptorSetAllocateInfo allocInfo {};
        allocInfo.sType              = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
        allocInfo.descriptorPool     = m_device->m_descriptorPool;
        allocInfo.descriptorSetCount = std::size(layouts);
        allocInfo.pSetLayouts        = layouts;

        m_device->CheckResult(vkAllocateDescriptorSets(m_device->m_device, &allocInfo, inFlightData.m_descriptorSets), "failed to allocate descriptor sets!");

        for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
        {
            VkDescriptorBufferInfo bufferInfo {};
            bufferInfo.buffer = inFlightData.m_uniformBuffers[i].m_buffer;
            bufferInfo.offset = 0;
            bufferInfo.range  = sizeof(UniformBufferObject);

            VkDescriptorImageInfo imageInfo {};
            imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
            imageInfo.imageView   = m_texture.m_view;
            imageInfo.sampler     = m_texture.m_sampler;

            std::array<VkWriteDescriptorSet, 2> descriptorWrites {};

            descriptorWrites[0].sType           = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
            descriptorWrites[0].dstSet          = inFlightData.m_descriptorSets[i];
            descriptorWrites[0].dstBinding      = 0;
            descriptorWrites[0].dstArrayElement = 0;
            descriptorWrites[0].descriptorType  = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
            descriptorWrites[0].descriptorCount = 1;
            descriptorWrites[0].pBufferInfo     = &bufferInfo;

            descriptorWrites[1].sType           = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
            descriptorWrites[1].dstSet          = inFlightData.m_descriptorSets[i];
            descriptorWrites[1].dstBinding      = 1;
            descriptorWrites[1].dstArrayElement = 0;
            descriptorWrites[1].descriptorType  = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
            descriptorWrites[1].descriptorCount = 1;
            descriptorWrites[1].pImageInfo      = &imageInfo;

            vkUpdateDescriptorSets(m_device->m_device,
                                   static_cast<uint32_t>(descriptorWrites.size()),
                                   descriptorWrites.data(),
                                   0,
                                   nullptr);
        }
    }

    void recordCommandBuffer(VkCommandBuffer commandBuffer)
    {
        VkCommandBufferBeginInfo beginInfo {};
        beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        m_device->CheckResult(vkBeginCommandBuffer(commandBuffer, &beginInfo), "failed to begin recording command buffer!");

        std::array<VkClearValue, 2> clearValues {};
        clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
        clearValues[1].depthStencil = { 1.0f, 0 };

        VkRenderPassBeginInfo renderPassInfo {};
        renderPassInfo.sType             = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
        renderPassInfo.renderPass        = m_renderPass.m_renderPass;
        renderPassInfo.framebuffer       = m_framebuffer.m_framebuffer;
        renderPassInfo.renderArea.offset = { 0, 0 };
        renderPassInfo.renderArea.extent.width  = m_framebuffer.m_width;
        renderPassInfo.renderArea.extent.height = m_framebuffer.m_height;
        renderPassInfo.clearValueCount   = static_cast<uint32_t>(clearValues.size());
        renderPassInfo.pClearValues      = clearValues.data();
        vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
        vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline.m_pipeline);

        VkViewport viewport {};
        viewport.x        = 0.0f;
        viewport.y        = 0.0f;
        viewport.width    = (float)m_framebuffer.m_width;
        viewport.height   = (float)m_framebuffer.m_height;
        viewport.minDepth = 0.0f;
        viewport.maxDepth = 1.0f;
        vkCmdSetViewport(commandBuffer, 0, 1, &viewport);

        VkRect2D scissor {};
        scissor.offset = { 0, 0 };
        scissor.extent.width  = m_framebuffer.m_width;
        scissor.extent.height = m_framebuffer.m_height;
        vkCmdSetScissor(commandBuffer, 0, 1, &scissor);

        VkBuffer     vertexBuffers[] = { m_vertexBuffer.m_buffer };
        VkDeviceSize offsets[]       = { 0 };
        vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
        vkCmdBindIndexBuffer(commandBuffer, m_indexBuffer.m_buffer, 0, VK_INDEX_TYPE_UINT32);
        vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline.m_pipelineLayout, 0, 1, &inFlightData.m_descriptorSets[currentFrame], 0, nullptr);
        vkCmdDrawIndexed(commandBuffer, m_model.GetIndexCount(), 1, 0, 0, 0);
        vkCmdEndRenderPass(commandBuffer);
        m_device->CheckResult(vkEndCommandBuffer(commandBuffer), "failed to record command buffer!");
    }

    void updateUniformBuffer()
    {
        static auto startTime = std::chrono::high_resolution_clock::now();
        auto  currentTime = std::chrono::high_resolution_clock::now();

        // TODO : float is bad for this
        float time        = std::chrono::duration<float, std::chrono::seconds::period>(currentTime - startTime).count();

        UniformBufferObject ubo {};
        ubo.m_model.value      = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));
        ubo.m_view.value       = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
        ubo.m_projection.value = glm::perspective(glm::radians(45.0f),
                                            m_framebuffer.m_width / (float)m_framebuffer.m_height,
                                            0.1f,
                                            10.0f);
        ubo.m_projection.value[1][1] *= -1;
        memcpy(inFlightData.m_mappedUniformBuffers[currentFrame], &ubo, sizeof(ubo));
    }
};

// End Rendering Code
////////////////////////////////////////////////////////////////////////////////////



namespace GameEngine {

RenderView::RenderView(ApplicationFramework::IApplication& app)
    : m_renderer(new ExampleRenderer())
    , m_application(app)
{
}

RenderView::~RenderView()
{
}

void RenderView::AddShowMenuItem()
{
    ImGui::MenuItem("Show Render View", NULL, &m_open);
}

void RenderView::Update()
{
    if (m_open)
    {
        if (ImGui::Begin("Render View", &m_open))
        {
            ImGui::Text("pointer = %llu", (ImTextureID)m_texture);
            ImGui::Text("size = %d x %d", m_width, m_height);
            ImGui::Image((ImTextureID)m_texture, ImVec2(m_width, m_height));
            m_renderer->Update();
        }
        ImGui::End();
    }
}

void RenderView::Initialize()
{
    m_renderer->Initialize(((ApplicationFramework::VulkanRenderer*)m_application.RenderSystem())->Details());
    m_texture = m_application.RenderSystem()->LoadTexture(m_renderer->ImageView(), 512, 512);
    m_width = 512;
    m_height = 512;
}

void RenderView::Shutdown()
{
    m_application.RenderSystem()->DestroyTexture(m_texture);
    m_renderer->Shutdown();
    delete m_renderer;
    m_renderer = nullptr;
}

} // GameEngine namespace
