//========================================================================
// GLFW 3.3 - www.glfw.org
//------------------------------------------------------------------------
// Copyright (c) 2002-2006 Marcus Geelnard
// Copyright (c) 2006-2016 Camilla Löwy <elmindreda@glfw.org>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would
//    be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
//    be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
//    distribution.
//
//========================================================================

#include "internal.h"

#include <assert.h>
#include <string.h>
#include <stdlib.h>

#define _GLFW_FIND_LOADER 1
#define _GLFW_REQUIRE_LOADER 2

//////////////////////////////////////////////////////////////////////////
//////                       GLFW internal API                      //////
//////////////////////////////////////////////////////////////////////////

GLFWbool _glfwInitVulkan(int mode) {
  VkResult err;
  VkExtensionProperties *ep;
  uint32_t i, count;

  if (_glfw.vk.available)
    return GLFW_TRUE;

#if !defined(_GLFW_VULKAN_STATIC)
#if defined(_GLFW_VULKAN_LIBRARY)
  _glfw.vk.handle = _glfw_dlopen(_GLFW_VULKAN_LIBRARY);
#elif defined(_GLFW_WIN32)
  _glfw.vk.handle = _glfw_dlopen("vulkan-1.dll");
#elif defined(_GLFW_COCOA)
  _glfw.vk.handle = _glfw_dlopen("libvulkan.1.dylib");
#else
  _glfw.vk.handle = _glfw_dlopen("libvulkan.so.1");
#endif
  if (!_glfw.vk.handle) {
    if (mode == _GLFW_REQUIRE_LOADER)
      _glfwInputError(GLFW_API_UNAVAILABLE, "Vulkan: Loader not found");

    return GLFW_FALSE;
  }

  _glfw.vk.GetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)_glfw_dlsym(
      _glfw.vk.handle, "vkGetInstanceProcAddr");
  if (!_glfw.vk.GetInstanceProcAddr) {
    _glfwInputError(GLFW_API_UNAVAILABLE,
                    "Vulkan: Loader does not export vkGetInstanceProcAddr");

    _glfwTerminateVulkan();
    return GLFW_FALSE;
  }

  _glfw.vk.EnumerateInstanceExtensionProperties =
      (PFN_vkEnumerateInstanceExtensionProperties)vkGetInstanceProcAddr(
          NULL, "vkEnumerateInstanceExtensionProperties");
  if (!_glfw.vk.EnumerateInstanceExtensionProperties) {
    _glfwInputError(
        GLFW_API_UNAVAILABLE,
        "Vulkan: Failed to retrieve vkEnumerateInstanceExtensionProperties");

    _glfwTerminateVulkan();
    return GLFW_FALSE;
  }
#endif // _GLFW_VULKAN_STATIC

  err = vkEnumerateInstanceExtensionProperties(NULL, &count, NULL);
  if (err) {
    // NOTE: This happens on systems with a loader but without any Vulkan ICD
    if (mode == _GLFW_REQUIRE_LOADER) {
      _glfwInputError(GLFW_API_UNAVAILABLE,
                      "Vulkan: Failed to query instance extension count: %s",
                      _glfwGetVulkanResultString(err));
    }

    _glfwTerminateVulkan();
    return GLFW_FALSE;
  }

  ep = calloc(count, sizeof(VkExtensionProperties));

  err = vkEnumerateInstanceExtensionProperties(NULL, &count, ep);
  if (err) {
    _glfwInputError(GLFW_API_UNAVAILABLE,
                    "Vulkan: Failed to query instance extensions: %s",
                    _glfwGetVulkanResultString(err));

    free(ep);
    _glfwTerminateVulkan();
    return GLFW_FALSE;
  }

  for (i = 0; i < count; i++) {
    if (strcmp(ep[i].extensionName, "VK_KHR_surface") == 0)
      _glfw.vk.KHR_surface = GLFW_TRUE;
#if defined(_GLFW_WIN32)
    else if (strcmp(ep[i].extensionName, "VK_KHR_win32_surface") == 0)
      _glfw.vk.KHR_win32_surface = GLFW_TRUE;
#elif defined(_GLFW_COCOA)
    else if (strcmp(ep[i].extensionName, "VK_MVK_macos_surface") == 0)
      _glfw.vk.MVK_macos_surface = GLFW_TRUE;
#elif defined(_GLFW_X11)
    else if (strcmp(ep[i].extensionName, "VK_KHR_xlib_surface") == 0)
      _glfw.vk.KHR_xlib_surface = GLFW_TRUE;
    else if (strcmp(ep[i].extensionName, "VK_KHR_xcb_surface") == 0)
      _glfw.vk.KHR_xcb_surface = GLFW_TRUE;
#elif defined(_GLFW_WAYLAND)
    else if (strcmp(ep[i].extensionName, "VK_KHR_wayland_surface") == 0)
      _glfw.vk.KHR_wayland_surface = GLFW_TRUE;
#elif defined(_GLFW_MIR)
    else if (strcmp(ep[i].extensionName, "VK_KHR_mir_surface") == 0)
      _glfw.vk.KHR_mir_surface = GLFW_TRUE;
#endif
  }

  free(ep);

  _glfw.vk.available = GLFW_TRUE;

  _glfwPlatformGetRequiredInstanceExtensions(_glfw.vk.extensions);

  return GLFW_TRUE;
}

void _glfwTerminateVulkan(void) {
#if !defined(_GLFW_VULKAN_STATIC)
  if (_glfw.vk.handle)
    _glfw_dlclose(_glfw.vk.handle);
#endif
}

const char *_glfwGetVulkanResultString(VkResult result) {
  switch (result) {
  case VK_SUCCESS:
    return "Success";
  case VK_NOT_READY:
    return "A fence or query has not yet completed";
  case VK_TIMEOUT:
    return "A wait operation has not completed in the specified time";
  case VK_EVENT_SET:
    return "An event is signaled";
  case VK_EVENT_RESET:
    return "An event is unsignaled";
  case VK_INCOMPLETE:
    return "A return array was too small for the result";
  case VK_ERROR_OUT_OF_HOST_MEMORY:
    return "A host memory allocation has failed";
  case VK_ERROR_OUT_OF_DEVICE_MEMORY:
    return "A device memory allocation has failed";
  case VK_ERROR_INITIALIZATION_FAILED:
    return "Initialization of an object could not be completed for "
           "implementation-specific reasons";
  case VK_ERROR_DEVICE_LOST:
    return "The logical or physical device has been lost";
  case VK_ERROR_MEMORY_MAP_FAILED:
    return "Mapping of a memory object has failed";
  case VK_ERROR_LAYER_NOT_PRESENT:
    return "A requested layer is not present or could not be loaded";
  case VK_ERROR_EXTENSION_NOT_PRESENT:
    return "A requested extension is not supported";
  case VK_ERROR_FEATURE_NOT_PRESENT:
    return "A requested feature is not supported";
  case VK_ERROR_INCOMPATIBLE_DRIVER:
    return "The requested version of Vulkan is not supported by the driver or "
           "is otherwise incompatible";
  case VK_ERROR_TOO_MANY_OBJECTS:
    return "Too many objects of the type have already been created";
  case VK_ERROR_FORMAT_NOT_SUPPORTED:
    return "A requested format is not supported on this device";
  case VK_ERROR_SURFACE_LOST_KHR:
    return "A surface is no longer available";
  case VK_SUBOPTIMAL_KHR:
    return "A swapchain no longer matches the surface properties exactly, but "
           "can still be used";
  case VK_ERROR_OUT_OF_DATE_KHR:
    return "A surface has changed in such a way that it is no longer "
           "compatible with the swapchain";
  case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:
    return "The display used by a swapchain does not use the same presentable "
           "image layout";
  case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
    return "The requested window is already connected to a VkSurfaceKHR, or to "
           "some other non-Vulkan API";
  case VK_ERROR_VALIDATION_FAILED_EXT:
    return "A validation layer found an error";
  default:
    return "ERROR: UNKNOWN VULKAN ERROR";
  }
}

//////////////////////////////////////////////////////////////////////////
//////                        GLFW public API                       //////
//////////////////////////////////////////////////////////////////////////

GLFWAPI int glfwVulkanSupported(void) {
  _GLFW_REQUIRE_INIT_OR_RETURN(GLFW_FALSE);
  return _glfwInitVulkan(_GLFW_FIND_LOADER);
}

GLFWAPI const char **glfwGetRequiredInstanceExtensions(uint32_t *count) {
  assert(count != NULL);

  *count = 0;

  _GLFW_REQUIRE_INIT_OR_RETURN(NULL);

  if (!_glfwInitVulkan(_GLFW_REQUIRE_LOADER))
    return NULL;

  if (!_glfw.vk.extensions[0])
    return NULL;

  *count = 2;
  return (const char **)_glfw.vk.extensions;
}

GLFWAPI GLFWvkproc
glfwGetInstanceProcAddress(VkInstance instance, const char *procname) {
  GLFWvkproc proc;
  assert(procname != NULL);

  _GLFW_REQUIRE_INIT_OR_RETURN(NULL);

  if (!_glfwInitVulkan(_GLFW_REQUIRE_LOADER))
    return NULL;

  proc = (GLFWvkproc)vkGetInstanceProcAddr(instance, procname);
#if defined(_GLFW_VULKAN_STATIC)
  if (!proc) {
    if (strcmp(procname, "vkGetInstanceProcAddr") == 0)
      return (GLFWvkproc)vkGetInstanceProcAddr;
  }
#else
  if (!proc)
    proc = (GLFWvkproc)_glfw_dlsym(_glfw.vk.handle, procname);
#endif

  return proc;
}

GLFWAPI int glfwGetPhysicalDevicePresentationSupport(VkInstance instance,
                                                     VkPhysicalDevice device,
                                                     uint32_t queuefamily) {
  assert(instance != VK_NULL_HANDLE);
  assert(device != VK_NULL_HANDLE);

  _GLFW_REQUIRE_INIT_OR_RETURN(GLFW_FALSE);

  if (!_glfwInitVulkan(_GLFW_REQUIRE_LOADER))
    return GLFW_FALSE;

  if (!_glfw.vk.extensions[0]) {
    _glfwInputError(GLFW_API_UNAVAILABLE,
                    "Vulkan: Window surface creation extensions not found");
    return GLFW_FALSE;
  }

  return _glfwPlatformGetPhysicalDevicePresentationSupport(instance, device,
                                                           queuefamily);
}

GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance,
                                         GLFWwindow *handle,
                                         const VkAllocationCallbacks *allocator,
                                         VkSurfaceKHR *surface) {
  _GLFWwindow *window = (_GLFWwindow *)handle;
  assert(instance != VK_NULL_HANDLE);
  assert(window != NULL);
  assert(surface != NULL);

  *surface = VK_NULL_HANDLE;

  _GLFW_REQUIRE_INIT_OR_RETURN(VK_ERROR_INITIALIZATION_FAILED);

  if (!_glfwInitVulkan(_GLFW_REQUIRE_LOADER))
    return VK_ERROR_INITIALIZATION_FAILED;

  if (!_glfw.vk.extensions[0]) {
    _glfwInputError(GLFW_API_UNAVAILABLE,
                    "Vulkan: Window surface creation extensions not found");
    return VK_ERROR_EXTENSION_NOT_PRESENT;
  }

  if (window->context.client != GLFW_NO_API) {
    _glfwInputError(GLFW_INVALID_VALUE, "Vulkan: Window surface creation "
                                        "requires the window to have the "
                                        "client API set to GLFW_NO_API");
    return VK_ERROR_NATIVE_WINDOW_IN_USE_KHR;
  }

  return _glfwPlatformCreateWindowSurface(instance, window, allocator, surface);
}
