#include "copium/buffer/Framebuffer.h"

#include "copium/asset/AssetManager.h"
#include "copium/buffer/CommandBuffer.h"
#include "copium/core/Vulkan.h"
#include "copium/sampler/Image.h"

namespace Copium
{
  Framebuffer::Framebuffer(const MetaFile& metaFile)
  {
    const MetaFileClass& metaClass = metaFile.GetMetaClass("Framebuffer");
    ColorAttachment& attachment = AssetManager::LoadAsset<ColorAttachment>(UUID{metaClass.GetValue("rendertexture-uuid")});
    colorAttachment = attachment;
    width = attachment.GetWidth();
    height = attachment.GetHeight();
    InitializeDepthBuffer();
    InitializeRenderPass();
    InitializeFramebuffers();
  }

  Framebuffer::Framebuffer(uint32_t width, uint32_t height)
    : width{width}, height{height}
  {
    InitializeImage();
    InitializeDepthBuffer();
    InitializeRenderPass();
    InitializeFramebuffers();
  }

  Framebuffer::~Framebuffer()
  {
    for (auto& framebuffer : framebuffers)
      vkDestroyFramebuffer(Vulkan::GetDevice(), framebuffer, nullptr);
    vkDestroyRenderPass(Vulkan::GetDevice(), renderPass, nullptr);
    AssetManager::UnloadAsset(colorAttachment);
  }

  void Framebuffer::Resize(uint32_t width, uint32_t height)
  {
    vkDeviceWaitIdle(Vulkan::GetDevice());
    this->width = width;
    this->height = height;
    for (auto&& framebuffer : framebuffers)
      vkDestroyFramebuffer(Vulkan::GetDevice(), framebuffer, nullptr);
    AssetManager::GetAsset<ColorAttachment>(colorAttachment).Resize(width, height);
    depthAttachment->Resize(width, height);
    InitializeFramebuffers();
  }

  void Framebuffer::Bind(const CommandBuffer& commandBuffer)
  {
    std::vector<VkClearValue> clearValues{2};
    clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
    clearValues[1].depthStencil = {1.0f, 0};

    VkRenderPassBeginInfo renderPassBeginInfo{};
    renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
    renderPassBeginInfo.renderPass = renderPass;
    renderPassBeginInfo.framebuffer = framebuffers[Vulkan::GetSwapChain().GetFlightIndex()];
;
    renderPassBeginInfo.renderArea.offset = {0, 0};
    renderPassBeginInfo.renderArea.extent = {width, height};
    renderPassBeginInfo.clearValueCount = clearValues.size();
    renderPassBeginInfo.pClearValues = clearValues.data();
    vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);

    VkViewport viewport{};
    viewport.x = 0.0f;
    viewport.y = 0.0f;
    viewport.width = width;
    viewport.height = height;
    viewport.minDepth = 0.0f;
    viewport.maxDepth = 1.0f;
    vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
    VkRect2D scissor{};
    scissor.offset = {0, 0};
    scissor.extent = {width, height};
    vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
  }

  void Framebuffer::Unbind(const CommandBuffer& commandBuffer)
  {
    vkCmdEndRenderPass(commandBuffer);
  }

  VkRenderPass Framebuffer::GetRenderPass() const
  {
    return renderPass;
  }

  VkFramebuffer Framebuffer::GetFramebuffer() const
  {
    return framebuffers[Vulkan::GetSwapChain().GetFlightIndex()];
  }

  const ColorAttachment& Framebuffer::GetColorAttachment() const
  {
    return AssetManager::GetAsset<ColorAttachment>(colorAttachment);
  }

  uint32_t Framebuffer::GetWidth() const
  {
    return width;
  }

  uint32_t Framebuffer::GetHeight() const
  {
    return height;
  }

  void Framebuffer::InitializeImage()
  {
    colorAttachment = AssetManager::RegisterRuntimeAsset("Framebuffer::ColorAttachment", std::make_unique<ColorAttachment>(width, height));
  }

  void Framebuffer::InitializeDepthBuffer()
  {
    depthAttachment  = std::make_unique<DepthAttachment>(width, height);
  }

  void Framebuffer::InitializeRenderPass()
  {
    VkAttachmentDescription colorAttachment{};
    colorAttachment.format = VK_FORMAT_R8G8B8A8_SRGB;
    colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
    colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    colorAttachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

    VkAttachmentDescription depthAttachment{};
    depthAttachment.format = Image::SelectDepthFormat();
    depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
    depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

    VkAttachmentReference colorAttachmentRef{};
    colorAttachmentRef.attachment = 0;
    colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    VkAttachmentReference depthAttachmentRef{};
    depthAttachmentRef.attachment = 1;
    depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

    VkSubpassDescription subpass{};
    subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    subpass.colorAttachmentCount = 1;
    subpass.pColorAttachments = &colorAttachmentRef;
    subpass.pDepthStencilAttachment = &depthAttachmentRef;

    std::vector<VkSubpassDependency> dependencies{2};
    dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
    dependencies[0].dstSubpass = 0;
    dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
    dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;

    dependencies[1].srcSubpass = 0;
    dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
    dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

    std::vector<VkAttachmentDescription> attachments{colorAttachment, depthAttachment};
    VkRenderPassCreateInfo renderPassCreateInfo{};
    renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    renderPassCreateInfo.attachmentCount = attachments.size();
    renderPassCreateInfo.pAttachments = attachments.data();
    renderPassCreateInfo.subpassCount = 1;
    renderPassCreateInfo.pSubpasses = &subpass;
    renderPassCreateInfo.dependencyCount = dependencies.size();
    renderPassCreateInfo.pDependencies = dependencies.data();

    CP_VK_ASSERT(vkCreateRenderPass(Vulkan::GetDevice(), &renderPassCreateInfo, nullptr, &renderPass), "Failed to initialze render pass");
  }

  void Framebuffer::InitializeFramebuffers()
  {
    framebuffers.resize(SwapChain::MAX_FRAMES_IN_FLIGHT);
    const ColorAttachment& attachment = AssetManager::GetAsset<ColorAttachment>(colorAttachment);
    for (size_t i = 0; i < framebuffers.size(); ++i)
    {
      std::vector<VkImageView> attachments{attachment.GetImageView(i), depthAttachment->GetImageView()};

      VkFramebufferCreateInfo createInfo{};
      createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
      createInfo.renderPass = renderPass;
      createInfo.attachmentCount = attachments.size();
      createInfo.pAttachments = attachments.data();
      createInfo.width = width;
      createInfo.height = height;
      createInfo.layers = 1;

      CP_VK_ASSERT(vkCreateFramebuffer(Vulkan::GetDevice(), &createInfo, nullptr, &framebuffers[i]), "Failed to initialize framebuffer");
    }
  }
}