#include <cstdlib>
#include <fmt/base.h>
#include <fmt/format.h>
#include <iostream>

#define GLFW_INCLUDE_NONE
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include "util/types.h"

constexpr u32 WIDTH  = 800;
constexpr u32 HEIGHT = 600;

constexpr std::array<const char*, 1> validationLayers = { "VK_LAYER_KHRONOS_validation" };

#if defined(NDEBUG)
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif

class Application {
 public:
  fn run() -> void {
    initWindow();
    initVulkan();
    mainLoop();
    cleanup();
  }

 private:
  VkInstance  mInstance;
  GLFWwindow* mWindow;

  static fn checkValidationLayerSupport() -> bool {
    u32 layerCount = 0;
    vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

    std::vector<VkLayerProperties> availableLayers(layerCount);
    vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

    fmt::println("Available layers:");

    for (const auto& layerProperties : availableLayers)
      fmt::println("\t{}", layerProperties.layerName);

    for (const char* layerName : validationLayers) {
      bool layerFound = false;

      for (const auto& layerProperties : availableLayers) {
        if (strcmp(
              static_cast<const char*>(layerName),
              static_cast<const char*>(layerProperties.layerName)
            ) == 0) {
          layerFound = true;
          break;
        }
      }

      if (!layerFound)
        return false;
    }

    return true;
  }

  static fn getAvailableExtensions() -> std::vector<VkExtensionProperties> {
    u32 extensionCount = 0;
    vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);

    std::vector<VkExtensionProperties> extensions(extensionCount);
    vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());

    return extensions;
  }

  fn createInstance() -> void {
    fmt::println("Available extensions:");

    for (const fn& extension : getAvailableExtensions())
      fmt::println("\t{}", extension.extensionName);

    if (enableValidationLayers && !checkValidationLayerSupport())
      throw std::runtime_error("Validation layers requested, but not available!");

    VkApplicationInfo appInfo {};
    appInfo.sType              = VK_STRUCTURE_TYPE_APPLICATION_INFO; // Used for pNext
    appInfo.pApplicationName   = "Hello Triangle";
    appInfo.pEngineName        = "No Engine";
    appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.engineVersion      = VK_MAKE_VERSION(1, 0, 0);
    appInfo.apiVersion         = VK_API_VERSION_1_0;

    uint32_t     glfwExtensionCount = 0;
    const char** glfwExtensions     = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

    VkInstanceCreateInfo createInfo {};
    createInfo.sType                   = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    createInfo.pApplicationInfo        = &appInfo;
    createInfo.enabledExtensionCount   = glfwExtensionCount;
    createInfo.ppEnabledExtensionNames = glfwExtensions;
    createInfo.enabledLayerCount       = 0;

    std::vector<const char*> requiredExtensions;

    requiredExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);

    createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;

    createInfo.enabledExtensionCount   = static_cast<u32>(requiredExtensions.size());
    createInfo.ppEnabledExtensionNames = requiredExtensions.data();

    if (vkCreateInstance(&createInfo, nullptr, &mInstance) != VK_SUCCESS) {
      throw std::runtime_error("failed to create instance!");
    }
  }

  fn initWindow() -> void {
    // Initialize GLFW
    glfwInit();

    // Don't create an OpenGL context
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);

    // Disable Resizing
    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

    mWindow = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
  }

  fn initVulkan() -> void { createInstance(); }

  fn mainLoop() -> void {
    while (!glfwWindowShouldClose(mWindow)) glfwPollEvents();
  }

  fn cleanup() -> void {
    vkDestroyInstance(mInstance, nullptr);
    glfwDestroyWindow(mWindow);
    glfwTerminate();
  }
};

fn main() -> int {
  Application app;

  try {
    app.run();
  } catch (const std::exception& e) {
    std::cerr << e.what() << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}