#include <GLFW/glfw3.h>

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

namespace veng {

gsl::span<gsl::czstring> Graphics::GetSuggestedInstanceExtentions() {
  std::uint32_t glfw_extention_count = 0;
  gsl::czstring* glfw_extentions =
      glfwGetRequiredInstanceExtensions(&glfw_extention_count);
  return {glfw_extentions, glfw_extention_count};
}

std::vector<gsl::czstring> Graphics::GetRequiredInstanceExtentions() {
  gsl::span<gsl::czstring> suggested_extentions =
      GetSuggestedInstanceExtentions();
  std::vector<gsl::czstring> required_extentions(suggested_extentions.size());
  std::copy(suggested_extentions.begin(), suggested_extentions.end(),
            required_extentions.begin());

  if (validation_enabled_)
    required_extentions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);

  if (!AreAllExtensionsSupported(suggested_extentions)) std::exit(EXIT_FAILURE);

  return required_extentions;
}

std::vector<VkExtensionProperties> Graphics::GetSupprotedInstanceExtensions() {
  std::uint32_t count;
  vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr);

  if (count == 0) return {};

  std::vector<VkExtensionProperties> properties(count);
  vkEnumerateInstanceExtensionProperties(nullptr, &count, properties.data());
  return properties;
}

bool ExtentionMatchesName(gsl::czstring name,
                          const VkExtensionProperties& properties) {
  return streq(properties.extensionName, name);
}

bool IsExtensionSupported(gsl::span<VkExtensionProperties> extensions,
                          gsl::czstring name) {
  return std::any_of(extensions.begin(), extensions.end(),
                     std::bind_front(ExtentionMatchesName, name));
}

bool Graphics::AreAllExtensionsSupported(gsl::span<gsl::czstring> extensions) {
  std::vector<VkExtensionProperties> supported_extensions =
      GetSupprotedInstanceExtensions();

  return std::all_of(
      extensions.begin(), extensions.end(),
      std::bind_front(IsExtensionSupported, supported_extensions));
}

void Graphics::CreateInstance() {
  std::array<gsl::czstring, 1> validation_layers = {
      "VK_LAYER_KHRONOS_validation"};

  if (!AreAllLayersSupported(validation_layers)) validation_enabled_ = false;

  std::vector<gsl::czstring> required_extentions =
      GetRequiredInstanceExtentions();

  VkApplicationInfo app_info = {};
  app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
  app_info.pNext = nullptr;
  app_info.pApplicationName = "Udemy Course";
  app_info.applicationVersion = VK_MAKE_API_VERSION(0, 0, 0, 0);
  app_info.pEngineName = "VEng";
  app_info.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
  app_info.apiVersion = VK_API_VERSION_1_0;

  VkInstanceCreateInfo instance_creation_info = {};
  instance_creation_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
  instance_creation_info.pNext = nullptr;
  instance_creation_info.pApplicationInfo = &app_info;
  instance_creation_info.enabledExtensionCount = required_extentions.size();
  instance_creation_info.ppEnabledExtensionNames = required_extentions.data();

  VkDebugUtilsMessengerCreateInfoEXT messenger_create_info =
      GetCreateMessengerInfo();
  if (validation_enabled_) {
    instance_creation_info.pNext = &messenger_create_info;
    instance_creation_info.enabledLayerCount = validation_layers.size();
    instance_creation_info.ppEnabledLayerNames = validation_layers.data();
  } else {
    instance_creation_info.enabledLayerCount = 0;
    instance_creation_info.ppEnabledLayerNames = nullptr;
  }

  VkResult result =
      vkCreateInstance(&instance_creation_info, nullptr, &instance_);
  if (result != VK_SUCCESS) std::exit(EXIT_FAILURE);
}

}  // namespace veng
