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

#include <array>

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

namespace
{

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

  std::array<VkDescriptorSetLayoutBinding, 2> set_layouts{};
  set_layouts[0].binding = 0;
  set_layouts[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
  set_layouts[0].descriptorCount = 1;
  set_layouts[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
  set_layouts[0].pImmutableSamplers = nullptr;

  set_layouts[1].binding = 1;
  set_layouts[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
  set_layouts[1].descriptorCount = 1;
  set_layouts[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
  set_layouts[1].pImmutableSamplers = nullptr;

  VkDescriptorSetLayoutCreateInfo layout_info{};
  layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
  layout_info.pNext = nullptr;
  layout_info.flags = 0;
  layout_info.bindingCount = set_layouts.size();
  layout_info.pBindings = set_layouts.data();

  if(vkCreateDescriptorSetLayout(
       BluCat::INT::core.vk_device_with_swapchain->device, &layout_info, nullptr,
       &self->world) != VK_SUCCESS)
    throw CommandError{
      "Failed to create Vulkan descriptor set layout for world view."};
}

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

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

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

  std::array<VkDescriptorSetLayoutBinding, 1> layout_bindings{};

  layout_bindings[0].binding = 0;
  layout_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
  layout_bindings[0].descriptorCount = 1;
  layout_bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
  layout_bindings[0].pImmutableSamplers = nullptr;

  VkDescriptorSetLayoutCreateInfo layout_info{};
  layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
  layout_info.pNext = nullptr;
  layout_info.flags = 0;
  layout_info.bindingCount = static_cast<uint32_t>(layout_bindings.size());
  layout_info.pBindings = layout_bindings.data();

  if(vkCreateDescriptorSetLayout(
       BluCat::INT::core.vk_device_with_swapchain->device, &layout_info, nullptr,
       &self->view) != VK_SUCCESS)
    throw CommandError{
      "Failed to create Vulkan descriptor set layout for view."};
}

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

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

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

  std::array<VkDescriptorSetLayoutBinding, 1> layout_bindings{};

  layout_bindings[0].binding = 0;
  layout_bindings[0].descriptorType =
    VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
  layout_bindings[0].descriptorCount = 1;
  layout_bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
  layout_bindings[0].pImmutableSamplers = nullptr;

  VkDescriptorSetLayoutCreateInfo layout_info{};
  layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
  layout_info.pNext = nullptr;
  layout_info.flags = 0;
  layout_info.bindingCount = static_cast<uint32_t>(layout_bindings.size());
  layout_info.pBindings = layout_bindings.data();

  if(vkCreateDescriptorSetLayout(
       BluCat::INT::core.vk_device_with_swapchain->device, &layout_info, nullptr,
       &self->texture) != VK_SUCCESS)
    throw CommandError{
      "Failed to create Vulkan descriptor set layout for textures."};
}

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

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

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

  std::array<VkDescriptorSetLayoutBinding, 1> layout_bindings;
  layout_bindings[0].binding = 0;
  layout_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
  layout_bindings[0].descriptorCount = 1;
  layout_bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
  layout_bindings[0].pImmutableSamplers = nullptr;

  VkDescriptorSetLayoutCreateInfo layout_info{};
  layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
  layout_info.pNext = nullptr;
  layout_info.flags = 0;
  layout_info.bindingCount = static_cast<uint32_t>(layout_bindings.size());
  layout_info.pBindings = layout_bindings.data();

  if(vkCreateDescriptorSetLayout(
       BluCat::INT::core.vk_device_with_swapchain->device, &layout_info, nullptr,
       &self->model) != VK_SUCCESS)
    throw CommandError{
      "Failed to create Vulkan descriptor set layout for model instance."};
}

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

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

const CommandChain loader{
  {&load_world, &unload_world},
  {&load_view, &unload_view},
  {&load_texture, &unload_texture},
  {&load_model, &unload_model},
};

}

namespace BluCat::GRA
{

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

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

}