/*
 * Copyright 2022-2024 Frederico de Oliveira Linhares
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "graphics_pipeline_2d_wired.hpp"

#include <array>

#include "../int/core.hpp"
#include "rectangle.hpp"
#include "sprite.hpp"
#include "uniform_data_object.hpp"

namespace
{

void
load_indexes(void *obj)
{
  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DWired*>(obj);

  self->queue_family =
    BluCat::INT::core.vk_device_with_swapchain->get_queue_family_with_graphics();

  std::array<uint32_t, 4> indexes{0, 1, 2, 3};
  void *indexes_data{indexes.data()};
  size_t indexes_size{sizeof(indexes[0]) * indexes.size()};
  BluCat::GRA::SourceBuffer source_index_buffer{
    self->queue_family->device, indexes_data, indexes_size};
  self->index_buffer = new BluCat::GRA::DestinationBuffer{
    self->queue_family, &source_index_buffer,
    VK_BUFFER_USAGE_INDEX_BUFFER_BIT};
}

void
unload_indexes(void *obj)
{
  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DWired*>(obj);

  delete self->index_buffer;
}

void
load_pipeline(void *obj)
{
  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DWired*>(obj);

  VkPipelineShaderStageCreateInfo vert_shader_stage_info{};
  vert_shader_stage_info.sType =
      VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
  vert_shader_stage_info.pNext = nullptr;
  vert_shader_stage_info.flags = 0;
  vert_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT;
  vert_shader_stage_info.module =
    BluCat::INT::core.vk_device_with_swapchain->vert2d_wired_shader_module;
  vert_shader_stage_info.pName = "main";
  vert_shader_stage_info.pSpecializationInfo = nullptr;

  VkPipelineShaderStageCreateInfo frag_shader_stage_info{};
  frag_shader_stage_info.sType =
      VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
  frag_shader_stage_info.pNext = nullptr;
  frag_shader_stage_info.flags = 0;
  frag_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
  frag_shader_stage_info.module =
    BluCat::INT::core.vk_device_with_swapchain->frag2d_wired_shader_module;
  frag_shader_stage_info.pName = "main";
  frag_shader_stage_info.pSpecializationInfo = nullptr;

  VkPipelineShaderStageCreateInfo shader_stages[] = {
    vert_shader_stage_info,
    frag_shader_stage_info
  };

  VkPipelineVertexInputStateCreateInfo vertex_input_info = {};
  vertex_input_info.sType =
      VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
  vertex_input_info.pNext = nullptr;
  vertex_input_info.flags = 0;
  vertex_input_info.vertexBindingDescriptionCount = 0;
  vertex_input_info.pVertexBindingDescriptions = nullptr;
  vertex_input_info.vertexAttributeDescriptionCount = 0;
  vertex_input_info.pVertexAttributeDescriptions = nullptr;

  VkPipelineInputAssemblyStateCreateInfo input_assembly = {};
  input_assembly.sType =
      VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
  input_assembly.pNext = nullptr;
  input_assembly.flags = 0;
  input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
  input_assembly.primitiveRestartEnable = VK_FALSE;

  VkViewport viewport = {};
  viewport.x = 0;
  viewport.y = 0;
  viewport.width = BluCat::INT::core.display_width;
  viewport.height = BluCat::INT::core.display_height;
  viewport.minDepth = 0.0f;
  viewport.maxDepth = 1.0f;

  VkRect2D scissor = {};
  scissor.offset = {0, 0};
  scissor.extent = {BluCat::INT::core.display_width, BluCat::INT::core.display_height};

  VkPipelineViewportStateCreateInfo viewport_state = {};
  viewport_state.sType =
      VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
  viewport_state.pNext = nullptr;
  viewport_state.flags = 0;
  viewport_state.viewportCount = 1;
  viewport_state.pViewports = &viewport;
  viewport_state.scissorCount = 1;
  viewport_state.pScissors = &scissor;

  VkPipelineRasterizationStateCreateInfo rasterizer = {};
  rasterizer.sType =
      VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
  rasterizer.pNext = nullptr;
  rasterizer.flags = 0;
  rasterizer.depthClampEnable = VK_FALSE;
  rasterizer.rasterizerDiscardEnable = VK_FALSE;
  rasterizer.polygonMode = VK_POLYGON_MODE_LINE;
  rasterizer.cullMode = VK_CULL_MODE_NONE;
  rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
  rasterizer.depthBiasEnable = VK_FALSE;
  rasterizer.depthBiasConstantFactor = 0.0f;
  rasterizer.depthBiasClamp = 0.0f;
  rasterizer.depthBiasSlopeFactor = 0.0f;
  rasterizer.lineWidth = 1.0f;

  VkPipelineMultisampleStateCreateInfo multisampling = {};
  multisampling.sType =
      VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
  multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
  multisampling.sampleShadingEnable = VK_FALSE;
  multisampling.minSampleShading = 1.0f;
  multisampling.pSampleMask = nullptr;
  multisampling.alphaToCoverageEnable = VK_FALSE;
  multisampling.alphaToOneEnable = VK_FALSE;

  VkPipelineColorBlendAttachmentState color_blend_attachment = {};
  color_blend_attachment.blendEnable = VK_FALSE;
  color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
  color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
  color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
  color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
  color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
  color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
  color_blend_attachment.colorWriteMask =
      VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
      VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;

  VkPipelineColorBlendStateCreateInfo color_blending = {};
  color_blending.sType =
      VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
  color_blending.pNext = nullptr;
  color_blending.flags = 0;
  color_blending.logicOpEnable = VK_FALSE;
  color_blending.logicOp = VK_LOGIC_OP_COPY;
  color_blending.attachmentCount = 1;
  color_blending.pAttachments = &color_blend_attachment;
  color_blending.blendConstants[0] = 0.0f;
  color_blending.blendConstants[1] = 0.0f;
  color_blending.blendConstants[2] = 0.0f;
  color_blending.blendConstants[3] = 0.0f;

  VkDynamicState dynamic_states[] = {
    VK_DYNAMIC_STATE_VIEWPORT,
    VK_DYNAMIC_STATE_LINE_WIDTH
  };

  VkPipelineDynamicStateCreateInfo dynamic_state_info = {};
  dynamic_state_info.sType =
      VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
  dynamic_state_info.dynamicStateCount = 2;
  dynamic_state_info.pDynamicStates = dynamic_states;

  VkGraphicsPipelineCreateInfo pipeline_info{};
  pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
  pipeline_info.pNext = nullptr;
  pipeline_info.flags = 0;
  pipeline_info.stageCount = 2;
  pipeline_info.pStages = shader_stages;
  pipeline_info.pVertexInputState = &vertex_input_info;
  pipeline_info.pInputAssemblyState = &input_assembly;
  pipeline_info.pTessellationState = nullptr;
  pipeline_info.pViewportState = &viewport_state;
  pipeline_info.pRasterizationState = &rasterizer;
  pipeline_info.pMultisampleState = &multisampling;
  pipeline_info.pDepthStencilState = nullptr;
  pipeline_info.pColorBlendState = &color_blending;
  pipeline_info.pDynamicState = &dynamic_state_info;
  pipeline_info.layout =
    BluCat::INT::core.vk_graphics_pipeline_2d_wired_layout->pipeline;
  pipeline_info.renderPass = BluCat::INT::core.vk_render_pass->pipeline_2d;
  pipeline_info.subpass = 0;
  pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
  pipeline_info.basePipelineIndex = -1;

  if(vkCreateGraphicsPipelines(
       BluCat::INT::core.vk_device_with_swapchain->device, VK_NULL_HANDLE, 1,
       &pipeline_info, nullptr, &self->graphic_pipeline) != VK_SUCCESS)
    throw CommandError{"Failed to create graphics pipeline."};
}

void
unload_pipeline(void *obj)
{
  auto self = static_cast<BluCat::GRA::GraphicsPipeline2DWired*>(obj);

  vkDestroyPipeline(
    BluCat::INT::core.vk_device_with_swapchain->device, self->graphic_pipeline,
		nullptr);
}

const CommandChain loader{
  {&load_indexes, &unload_indexes},
  {&load_pipeline, &unload_pipeline}
};

}

namespace BluCat::GRA
{

GraphicsPipeline2DWired::GraphicsPipeline2DWired()
{
  loader.execute(this);
}

GraphicsPipeline2DWired::~GraphicsPipeline2DWired()
{
  loader.revert(this);
}

void
GraphicsPipeline2DWired::draw(
  std::shared_ptr<View2D> view, const VkCommandBuffer draw_command_buffer,
  const size_t current_frame, const size_t next_frame,
  const uint32_t image_index)
{
  // Set viewport
  {
    VkViewport vk_viewport{};
    vk_viewport.x = view->region.x;
    vk_viewport.y = view->region.y;
    vk_viewport.width = view->region.z;
    vk_viewport.height = view->region.w;
    vk_viewport.minDepth = 0.0f;
    vk_viewport.maxDepth = 1.0f;
    vkCmdSetViewport(draw_command_buffer, 0, 1, &vk_viewport);

    VkRect2D vk_scissor{};
    vk_scissor.offset.x = static_cast<int32_t>(view->region.x);
    vk_scissor.offset.y = static_cast<int32_t>(view->region.y);
    vk_scissor.extent.width = static_cast<uint32_t>(view->region.z);
    vk_scissor.extent.height = static_cast<uint32_t>(view->region.w);
    vkCmdSetScissor(draw_command_buffer, 0, 1, &vk_scissor);
  }

  // Draw rectangles
  {
    std::array<VkDescriptorSet, 1> vk_descriptor_sets{
      view->descriptor_sets_2d[image_index]};
    VkDeviceSize offsets[]{0};

    vkCmdBindDescriptorSets(
      draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
      INT::core.vk_graphics_pipeline_2d_wired_layout->pipeline, 0,
      vk_descriptor_sets.size(), vk_descriptor_sets.data(), 0, nullptr);
    vkCmdBindPipeline(
      draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
      this->graphic_pipeline);
    vkCmdBindIndexBuffer(
      draw_command_buffer, this->index_buffer->buffer, 0,
      VK_INDEX_TYPE_UINT32);

    for(auto i{0}; i < view->rectangles_to_draw[current_frame].size(); i++)
    {
      auto &rect{view->rectangles_to_draw[current_frame][i]};

      UDOVector4D position{rect.position};
      UDOVector3D color{rect.color};
      vkCmdPushConstants(
        draw_command_buffer,
        INT::core.vk_graphics_pipeline_2d_wired_layout->pipeline,
        VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(UDOVector4D), &position);
      vkCmdPushConstants(
        draw_command_buffer,
        INT::core.vk_graphics_pipeline_2d_wired_layout->pipeline,
        VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(UDOVector4D), sizeof(UDOVector3D),
        &color);
      vkCmdDrawIndexed(
        draw_command_buffer, Rectangle::VertexCount, 1, 0, 0, 0);
    }
  }

  // Prepare for the next frame.
  view->rectangles_to_draw[next_frame].clear();
}

}