/*
 * Copyright © 2024 Joel Winarske
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

// Danil, 2021+ Vulkan shader launcher, self
// https://github.com/danilw/vulkan-shadertoy-launcher The MIT License

#include "vk_error_print.h"

void vk_error_data_set_vkresult(struct vk_error_data* error,
                                VkResult vkresult,
                                const char* file,
                                unsigned int line) {
  if (vkresult == 0)
    return;

  if (error->type != VK_ERROR_SUCCESS &&
      !(error->type == VK_ERROR_VKRESULT_WARNING && vkresult < 0))
    return;

  *error = (struct vk_error_data){
      .type = vkresult < 0 ? VK_ERROR_VKRESULT : VK_ERROR_VKRESULT_WARNING,
      .vkresult = vkresult,
      .file = file,
      .line = line,
  };
}

void vk_error_data_set_errno(struct vk_error_data* error,
                             int err_no,
                             const char* file,
                             unsigned int line) {
  if (err_no == 0)
    return;

  if (error->type != VK_ERROR_SUCCESS &&
      error->type != VK_ERROR_VKRESULT_WARNING)
    return;

  *error = (struct vk_error_data){
      .type = VK_ERROR_ERRNO,
      .err_no = err_no,
      .file = file,
      .line = line,
  };
}

bool vk_error_data_merge(struct vk_error_data* error,
                         struct vk_error_data* other) {
  if (other->type == VK_ERROR_SUCCESS)
    return false;

  if (error->type != VK_ERROR_SUCCESS &&
      !(error->type == VK_ERROR_VKRESULT_WARNING &&
        (other->type == VK_ERROR_VKRESULT || other->type == VK_ERROR_ERRNO)))
    return false;

  *error = *other;
  return true;
}

bool vk_error_is_success(struct vk_error* error) {
  return error->error.type == VK_ERROR_SUCCESS;
}

bool vk_error_is_warning(struct vk_error* error) {
  return error->error.type == VK_ERROR_VKRESULT_WARNING;
}

bool vk_error_is_error(struct vk_error* error) {
  return !vk_error_is_success(error) && !vk_error_is_warning(error);
}

static const char* VkResult_string(VkResult res) {
  switch (res) {
    case VK_SUCCESS:
      return "Success";
    case VK_NOT_READY:
      return "Not ready";
    case VK_TIMEOUT:
      return "Timeout";
    case VK_EVENT_SET:
      return "Event set";
    case VK_EVENT_RESET:
      return "Event reset";
    case VK_INCOMPLETE:
      return "Incomplete";
    case VK_ERROR_OUT_OF_HOST_MEMORY:
      return "Out of host memory";
    case VK_ERROR_OUT_OF_DEVICE_MEMORY:
      return "Out of device memory";
    case VK_ERROR_INITIALIZATION_FAILED:
      return "Initialization failed";
    case VK_ERROR_DEVICE_LOST:
      return "Device lost";
    case VK_ERROR_MEMORY_MAP_FAILED:
      return "Memory map failed";
    case VK_ERROR_LAYER_NOT_PRESENT:
      return "Layer not present";
    case VK_ERROR_EXTENSION_NOT_PRESENT:
      return "Extension not present";
    case VK_ERROR_FEATURE_NOT_PRESENT:
      return "Feature not present";
    case VK_ERROR_INCOMPATIBLE_DRIVER:
      return "Incompatible driver";
    case VK_ERROR_TOO_MANY_OBJECTS:
      return "Too many objects";
    case VK_ERROR_FORMAT_NOT_SUPPORTED:
      return "Format not supported";
    case VK_ERROR_SURFACE_LOST_KHR:
      return "Surface lost";
    case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
      return "Native window is in use";
    case VK_SUBOPTIMAL_KHR:
      return "Suboptimal";
    case VK_ERROR_OUT_OF_DATE_KHR:
      return "Surface is out of date";
    case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:
      return "Incompatible display";
    case VK_ERROR_VALIDATION_FAILED_EXT:
      return "Validation failed";
    default:
      return "Unrecognized error";
  }
}

#if defined(VK_USE_PLATFORM_WIN32_KHR)
void win_error(char* iout, char* iout2) {
  int msgboxID = MessageBox(NULL, (LPCSTR)iout, (LPCSTR)iout2,
                            MB_ICONHAND | MB_DEFBUTTON1);
}

#endif

void print_error(FILE* fout,
                 struct vk_error_data* error_data,
                 const char* prefix) {
  fprintf(fout, "%s:%u: %s", error_data->file, error_data->line, prefix);
  switch (error_data->type) {
    case VK_ERROR_VKRESULT_WARNING:
    case VK_ERROR_VKRESULT:
      fprintf(fout, "%s (VkResult %d)\n", VkResult_string(error_data->vkresult),
              error_data->vkresult);
      break;
    case VK_ERROR_ERRNO:
      fprintf(fout, "%s (errno %d)\n", std::strerror(error_data->err_no),
              error_data->err_no);
      break;
    default:
      fprintf(fout, "<internal error>\n");
      break;
  }
}

void vk_error_fprintf(FILE* fout,
                      struct vk_error* error,
                      const char* fmt,
                      ...) {
  if (error->error.type == VK_ERROR_SUCCESS)
    return;

  va_list args;
  va_start(args, fmt);
  vfprintf(fout, fmt, args);
  va_end(args);

  print_error(fout, &error->error, "");
  if (error->sub_error.type != VK_ERROR_SUCCESS) {
    print_error(fout, &error->sub_error, "    Resulting from this error: ");
  }
}

/* The following functions get a readable string out of the Vulkan standard
 * enums */
const char* vk_VkPhysicalDeviceType_string(VkPhysicalDeviceType type) {
  switch (type) {
    case VK_PHYSICAL_DEVICE_TYPE_OTHER:
      return "Neither GPU nor CPU";
    case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
      return "Integrated GPU";
    case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
      return "Discrete GPU";
    case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
      return "Virtual GPU";
    case VK_PHYSICAL_DEVICE_TYPE_CPU:
      return "CPU";
    default:
      return "Unrecognized device type";
  }
}
