/*
 * 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 "swapchain.hpp"

#include "../int/core.hpp"

#include <vector>

namespace
{

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

  // Surface formats.
  uint32_t vk_surface_format_count;
  std::vector<VkSurfaceFormatKHR> vk_surface_formats;
  vkGetPhysicalDeviceSurfaceFormatsKHR(
    BluCat::INT::core.vk_device_with_swapchain->physical_device,
		BluCat::INT::core.window_surface, &vk_surface_format_count, nullptr);
  vk_surface_formats.resize(vk_surface_format_count);
  vkGetPhysicalDeviceSurfaceFormatsKHR(
    BluCat::INT::core.vk_device_with_swapchain->physical_device,
		BluCat::INT::core.window_surface, &vk_surface_format_count,
		vk_surface_formats.data());

  VkSwapchainCreateInfoKHR swapchain_create_info = {};
  swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
  swapchain_create_info.pNext = nullptr;
  swapchain_create_info.flags = 0;
  swapchain_create_info.surface = BluCat::INT::core.window_surface;
  swapchain_create_info.minImageCount = 3; // triple buffering.

  self->image_format = vk_surface_formats[0].format;
  swapchain_create_info.imageFormat = self->image_format;
  swapchain_create_info.imageColorSpace = vk_surface_formats[0].colorSpace;

  swapchain_create_info.imageExtent = {
    BluCat::INT::core.display_width, BluCat::INT::core.display_height};
  swapchain_create_info.imageArrayLayers = 1;
  swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
  swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
  swapchain_create_info.queueFamilyIndexCount = 0;
  swapchain_create_info.pQueueFamilyIndices = nullptr;
  swapchain_create_info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
  swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
  swapchain_create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR;
  swapchain_create_info.clipped = VK_FALSE;
  swapchain_create_info.oldSwapchain = VK_NULL_HANDLE;

  if(vkCreateSwapchainKHR(
       BluCat::INT::core.vk_device_with_swapchain->device, &swapchain_create_info,
       nullptr, &self->swapchain) != VK_SUCCESS)
    throw CommandError{"Vulkan failed to create swapchain."};

  vkGetSwapchainImagesKHR(
    BluCat::INT::core.vk_device_with_swapchain->device, self->swapchain,
    &self->images_count, nullptr);
  self->images = new VkImage[self->images_count];
  vkGetSwapchainImagesKHR(
    BluCat::INT::core.vk_device_with_swapchain->device, self->swapchain,
    &self->images_count, self->images);
}

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

  delete[] self->images;
  vkDestroySwapchainKHR(
    BluCat::INT::core.vk_device_with_swapchain->device, self->swapchain, nullptr);
}

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

  self->image_views = new VkImageView[self->images_count];
  for(auto i{0}; i < self->images_count; i++)
  {
    VkImageViewCreateInfo create_info = {};
    create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    create_info.image = self->images[i];
    create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
    create_info.format = self->image_format;
    create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
    create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
    create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
    create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
    create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    create_info.subresourceRange.baseMipLevel = 0;
    create_info.subresourceRange.levelCount = 1;
    create_info.subresourceRange.baseArrayLayer = 0;
    create_info.subresourceRange.layerCount = 1;

    if(vkCreateImageView(
	 BluCat::INT::core.vk_device_with_swapchain->device, &create_info, nullptr,
	 &self->image_views[i]))
      throw CommandError{"Could no create Image View for swapchain."};
  }
}

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

  for(auto i{0}; i < self->images_count; i++)
    vkDestroyImageView(
      BluCat::INT::core.vk_device_with_swapchain->device, self->image_views[i],
			nullptr);
}

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

  self->image_available_semaphores.resize(self->max_frames_in_flight);
  self->render_finished_semaphores.resize(self->max_frames_in_flight);
  self->in_flight_fences.resize(self->max_frames_in_flight);

  VkSemaphoreCreateInfo semaphore_info = {};
  semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
  semaphore_info.pNext = nullptr;
  semaphore_info.flags = 0;

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

  // FIXME: if this loop fails, it will not destroy the semaphores.
  for(auto i{0}; i < self->max_frames_in_flight; i++)
  {
    if(vkCreateSemaphore(
	 BluCat::INT::core.vk_device_with_swapchain->device, &semaphore_info,
	 nullptr, &self->image_available_semaphores[i]) != VK_SUCCESS ||
       vkCreateSemaphore(
	 BluCat::INT::core.vk_device_with_swapchain->device, &semaphore_info,
	 nullptr, &self->render_finished_semaphores[i]) != VK_SUCCESS ||
       vkCreateFence(
				 BluCat::INT::core.vk_device_with_swapchain->device, &fence_info,
				 nullptr, &self->in_flight_fences[i]) != VK_SUCCESS)
      throw CommandError{"Failed to create semaphores."};
  }
}

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

  vkDeviceWaitIdle(BluCat::INT::core.vk_device_with_swapchain->device);

  for(auto i{0}; i < self->max_frames_in_flight; i++)
  {
    vkDestroySemaphore(BluCat::INT::core.vk_device_with_swapchain->device,
                       self->render_finished_semaphores[i], nullptr);
    vkDestroySemaphore(BluCat::INT::core.vk_device_with_swapchain->device,
                       self->image_available_semaphores[i], nullptr);
    vkDestroyFence(BluCat::INT::core.vk_device_with_swapchain->device,
									 self->in_flight_fences[i], nullptr);
  }
}

const CommandChain loader{
  {&load_swapchain, &unload_swapchain},
  {&load_image_view, &unload_image_view},
  {&load_frame_sync, &unload_frame_sync}
};

}

namespace BluCat::GRA
{

Swapchain::Swapchain():
  current_frame{0}
{
  loader.execute(this);
}

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

}