#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++) {
    std::array<VkImageView, 2> attachments = {swap_chain_image_views_[i],
                                              depth_texture_.image_view};

    VkFramebufferCreateInfo info = {};
    info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
    info.renderPass = render_pass_;
    info.attachmentCount = attachments.size();
    info.pAttachments = attachments.data();
    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;

  for (Frame& frame : frames_) {
    VkResult result =
        vkAllocateCommandBuffers(logical_device_, &info, &frame.command_buffer);
    if (result != VK_SUCCESS) std::exit(EXIT_FAILURE);
  }
}

void Graphics::BeginCommands() {
  vkResetCommandBuffer(frames_[current_frame_].command_buffer, 0);

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

  VkResult result = vkBeginCommandBuffer(
      frames_[current_frame_].command_buffer, &begin_info);
  if (result != VK_SUCCESS)
    throw std::runtime_error("Failed to begin command buffer!");

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

  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_;
  render_pass_begin_info.clearValueCount = clear_values.size();
  render_pass_begin_info.pClearValues = clear_values.data();

  vkCmdBeginRenderPass(frames_[current_frame_].command_buffer,
                       &render_pass_begin_info,
                       VK_SUBPASS_CONTENTS_INLINE);
  vkCmdBindPipeline(frames_[current_frame_].command_buffer,
                    VK_PIPELINE_BIND_POINT_GRAPHICS,
                    pipeline_);
  VkViewport viewport = GetViewport();
  VkRect2D scissor = GetScissor();

  vkCmdSetViewport(frames_[current_frame_].command_buffer, 0, 1, &viewport);
  vkCmdSetScissor(frames_[current_frame_].command_buffer, 0, 1, &scissor);
}

void Graphics::EndCommands() {
  vkCmdEndRenderPass(frames_[current_frame_].command_buffer);
  VkResult result = vkEndCommandBuffer(frames_[current_frame_].command_buffer);
  if (result != VK_SUCCESS)
    throw std::runtime_error("Failed to record command buffer!");
}

void Graphics::CreateSignals() {
  for (Frame& frame : frames_) {
    VkSemaphoreCreateInfo semafore_info = {};
    semafore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

    if (vkCreateSemaphore(logical_device_, &semafore_info, nullptr,
                          &frame.image_available_signal) !=
        VK_SUCCESS)
      std::exit(EXIT_FAILURE);
    if (vkCreateSemaphore(logical_device_, &semafore_info, nullptr,
                          &frame.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,
                      &frame.still_rendering_fence) !=
        VK_SUCCESS)
      std::exit(EXIT_FAILURE);
  }
}

bool Graphics::BeginFrame() {
  vkWaitForFences(logical_device_, 1,
                  &frames_[current_frame_].still_rendering_fence, VK_TRUE,
                  UINT64_MAX);
  VkResult image_acquire_result = vkAcquireNextImageKHR(logical_device_, swap_chain_, UINT64_MAX,
                            frames_[current_frame_].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,
                &frames_[current_frame_].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 = &frames_[current_frame_].image_available_signal;
  submit_info.pWaitDstStageMask = &wait_stage;
  submit_info.commandBufferCount = 1;
  submit_info.pCommandBuffers = &frames_[current_frame_].command_buffer;
  submit_info.signalSemaphoreCount = 1;
  submit_info.pSignalSemaphores =
      &frames_[current_frame_].render_finished_signal;

  VkResult submit_result =
      vkQueueSubmit(graphics_queue_, 1, &submit_info,
                    frames_[current_frame_].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 =
      &frames_[current_frame_].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!");

  current_frame_ = (current_frame_++) % MAX_BUFFERED_FRAMES;
}

}  // namespace veng
