#include <GLFW/glfw3.h>

#include "precomp.h"
#include "vulkan/graphics.h"

namespace veng {

void Graphics::CreateFramebuffers() {
  swap_chain_framebuffers_.resize(swap_chain_image_views_.size());

  for (std::uint32_t i = 0; i < swap_chain_image_views_.size(); i++) {
    VkFramebufferCreateInfo info = {};
    info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
    info.renderPass = render_pass_;
    info.attachmentCount = 1;
    info.pAttachments = &swap_chain_image_views_[i];
    info.width = extent_.width;
    info.height = extent_.height;
    info.layers = 1;

    VkResult result = vkCreateFramebuffer(logical_device_, &info, nullptr,
                                          &swap_chain_framebuffers_[i]);
    if (result != VK_SUCCESS) std::exit(EXIT_FAILURE);
  }
}

void Graphics::CreateCommandPool() {
  QueueFamilyIndices indices = FindQueueFamilies(physical_device_);
  VkCommandPoolCreateInfo pool_info = {};
  pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
  pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
  pool_info.queueFamilyIndex = indices.graphics_family.value();

  VkResult result =
      vkCreateCommandPool(logical_device_, &pool_info, nullptr, &command_pool_);
  if (result != VK_SUCCESS) std::exit(EXIT_FAILURE);
}

void Graphics::CreateCommandBuffer() {
  VkCommandBufferAllocateInfo info = {};
  info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
  info.commandPool = command_pool_;
  info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
  info.commandBufferCount = 1;

  VkResult result =
      vkAllocateCommandBuffers(logical_device_, &info, &command_buffer_);
  if (result != VK_SUCCESS) std::exit(EXIT_FAILURE);
}

void Graphics::BeginCommands() {
  vkResetCommandBuffer(command_buffer_, 0);

  VkCommandBufferBeginInfo begin_info = {};
  begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

  VkResult result = vkBeginCommandBuffer(command_buffer_, &begin_info);
  if (result != VK_SUCCESS)
    throw std::runtime_error("Failed to begin command buffer!");

  VkRenderPassBeginInfo render_pass_begin_info = {};
  render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
  render_pass_begin_info.renderPass = render_pass_;
  render_pass_begin_info.framebuffer =
      swap_chain_framebuffers_[current_image_index_];
  render_pass_begin_info.renderArea.offset = {0, 0};
  render_pass_begin_info.renderArea.extent = extent_;

  VkClearValue clear_color = {{{0.f, 0.f, 0.f, 1.f}}};
  render_pass_begin_info.clearValueCount = 1;
  render_pass_begin_info.pClearValues = &clear_color;

  vkCmdBeginRenderPass(command_buffer_, &render_pass_begin_info,
                       VK_SUBPASS_CONTENTS_INLINE);
  vkCmdBindPipeline(command_buffer_, VK_PIPELINE_BIND_POINT_GRAPHICS,
                    pipeline_);
  VkViewport viewport = GetViewport();
  VkRect2D scissor = GetScissor();

  vkCmdSetViewport(command_buffer_, 0, 1, &viewport);
  vkCmdSetScissor(command_buffer_, 0, 1, &scissor);
}

void Graphics::EndCommands() {
  vkCmdEndRenderPass(command_buffer_);
  VkResult result = vkEndCommandBuffer(command_buffer_);
  if (result != VK_SUCCESS)
    throw std::runtime_error("Failed to record command buffer!");
}

void Graphics::CreateSignals() {
  VkSemaphoreCreateInfo semafore_info = {};
  semafore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

  if (vkCreateSemaphore(logical_device_, &semafore_info, nullptr,
                        &image_available_signal_) != VK_SUCCESS)
    std::exit(EXIT_FAILURE);
  if (vkCreateSemaphore(logical_device_, &semafore_info, nullptr,
                        &render_finished_signal_) != VK_SUCCESS)
    std::exit(EXIT_FAILURE);

  VkFenceCreateInfo fence_info = {};
  fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
  fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;

  if (vkCreateFence(logical_device_, &fence_info, nullptr,
                    &still_rendering_fence_) != VK_SUCCESS)
    std::exit(EXIT_FAILURE);
}

bool Graphics::BeginFrame() {
  vkWaitForFences(logical_device_, 1, &still_rendering_fence_, VK_TRUE,
                  UINT64_MAX);
  VkResult image_acquire_result = vkAcquireNextImageKHR(
      logical_device_, swap_chain_, UINT64_MAX, image_available_signal_,
      VK_NULL_HANDLE, &current_image_index_);

  if (image_acquire_result == VK_ERROR_OUT_OF_DATE_KHR) {
    RecreateSwapChain();
    return false;
  }

  if (image_acquire_result != VK_SUCCESS &&
      image_acquire_result != VK_SUBOPTIMAL_KHR)
    throw std::runtime_error("Couldn't acquire render image!");

  vkResetFences(logical_device_, 1, &still_rendering_fence_);

  BeginCommands();
  SetModelMatrix(glm::mat4(1.f));

  return true;
}

void veng::Graphics::EndFrame() {
  EndCommands();
  VkSubmitInfo submit_info = {};
  submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

  VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
  submit_info.waitSemaphoreCount = 1;
  submit_info.pWaitSemaphores = &image_available_signal_;
  submit_info.pWaitDstStageMask = &wait_stage;
  submit_info.commandBufferCount = 1;
  submit_info.pCommandBuffers = &command_buffer_;
  submit_info.signalSemaphoreCount = 1;
  submit_info.pSignalSemaphores = &render_finished_signal_;

  VkResult submit_result =
      vkQueueSubmit(graphics_queue_, 1, &submit_info, still_rendering_fence_);
  if (submit_result != VK_SUCCESS)
    throw std::runtime_error("failed to submit draw commands!");

  VkPresentInfoKHR present_info = {};
  present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
  present_info.waitSemaphoreCount = 1;
  present_info.pWaitSemaphores = &render_finished_signal_;
  present_info.swapchainCount = 1;
  present_info.pSwapchains = &swap_chain_;
  present_info.pImageIndices = &current_image_index_;

  VkResult result = vkQueuePresentKHR(present_queue_, &present_info);
  if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
    RecreateSwapChain();
  else if (result != VK_SUCCESS)
    throw std::runtime_error("Failed to present swap chain image!");
}

}  // namespace veng
