/*
 * gulkan
 * Copyright 2018 Collabora Ltd.
 * Author: Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
 * SPDX-License-Identifier: MIT
 */

#include <glib.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

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

#include "gulkan.h"

typedef struct Example
{
  GulkanTexture *texture;
  GLFWwindow *window;
  GMainLoop *loop;
  VkSurfaceKHR surface;
  GulkanRenderer *renderer;
  bool should_quit;
} Example;

static GdkPixbuf *
load_gdk_pixbuf ()
{
  GError *error = NULL;
  GdkPixbuf * pixbuf_no_alpha =
    gdk_pixbuf_new_from_resource ("/res/cat.jpg", &error);

  if (error != NULL) {
    g_printerr ("Unable to read file: %s\n", error->message);
    g_error_free (error);
    return NULL;
  } else {
    GdkPixbuf *pixbuf = gdk_pixbuf_add_alpha (pixbuf_no_alpha, false, 0, 0, 0);
    g_object_unref (pixbuf_no_alpha);
    return pixbuf;
  }
}

static void
key_callback (GLFWwindow* window, int key,
              int scancode, int action, int mods)
{
  (void) scancode;
  (void) mods;

  if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
    {
      Example *self = (Example*) glfwGetWindowUserPointer (window);
      self->should_quit = true;
    }
}

static void
init_glfw (Example *self, int width, int height)
{
  glfwInit();

  glfwWindowHint (GLFW_CLIENT_API, GLFW_NO_API);
  glfwWindowHint (GLFW_RESIZABLE, false);

  self->window = glfwCreateWindow (width, height, "Vulkan Pixbuf", NULL, NULL);

  glfwSetKeyCallback (self->window, key_callback);

  glfwSetWindowUserPointer (self->window, self);
}

static gboolean
draw_cb (gpointer data)
{
  Example *self = (Example*) data;

  glfwPollEvents ();
  if (glfwWindowShouldClose (self->window) || self->should_quit)
    {
      g_main_loop_quit (self->loop);
      return FALSE;
    }

  gulkan_renderer_draw (self->renderer);

  GulkanClient *client = GULKAN_CLIENT (self->renderer);
  gulkan_device_wait_idle (gulkan_client_get_device (client));

  return TRUE;
}

int
main () {
  Example example = {
    .should_quit = false
  };

  GdkPixbuf * pixbuf = load_gdk_pixbuf ();
  if (pixbuf == NULL)
    return -1;

  gint width = gdk_pixbuf_get_width (pixbuf);
  gint height = gdk_pixbuf_get_height (pixbuf);

  init_glfw (&example, width / 2, height / 2);

  example.loop = g_main_loop_new (NULL, FALSE);

  example.renderer = gulkan_renderer_new ();

  uint32_t num_glfw_extensions = 0;
  const char** glfw_extensions;
  glfw_extensions = glfwGetRequiredInstanceExtensions (&num_glfw_extensions);

  GSList *instance_ext_list = NULL;
  for (uint32_t i = 0; i < num_glfw_extensions; i++)
    {
      g_print ("Instance extensions: %s\n", glfw_extensions[i]);
      char *instance_ext = g_strdup (glfw_extensions[i]);
      instance_ext_list = g_slist_append (instance_ext_list, instance_ext);
    }

  const gchar *instance_extensions[] =
  {
    VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME
  };
  for (uint32_t i = 0; i < G_N_ELEMENTS (instance_extensions); i++)
    {
      char *instance_ext = g_strdup (instance_extensions[i]);
      instance_ext_list = g_slist_append (instance_ext_list, instance_ext);
    }

  const gchar *device_extensions[] =
  {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME,
  };

  GSList *device_ext_list = NULL;
  for (uint64_t i = 0; i < G_N_ELEMENTS (device_extensions); i++)
    {
      char *device_ext = g_strdup (device_extensions[i]);
      device_ext_list = g_slist_append (device_ext_list, device_ext);
    }

  GulkanClient *client = GULKAN_CLIENT (example.renderer);
  if (!gulkan_client_init_vulkan (client,
                                  instance_ext_list,
                                  device_ext_list))
  {
    g_printerr ("Unable to initialize Vulkan!\n");
    return -1;
  }

  VkInstance instance = gulkan_client_get_instance_handle (client);
  if (glfwCreateWindowSurface (instance,
                               example.window, NULL,
                              &example.surface) != VK_SUCCESS)
    {
      g_printerr ("Unable to create surface.\n");
      return -1;
    }

  example.texture =
    gulkan_client_texture_new_from_pixbuf (client, pixbuf,
                                           VK_FORMAT_R8G8B8A8_UNORM,
                                           VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
                                           true);

  if (!gulkan_renderer_init_rendering (example.renderer,
                                       example.surface,
                                       example.texture))
    return -1;

  g_timeout_add (20, draw_cb, &example);
  g_main_loop_run (example.loop);
  g_main_loop_unref (example.loop);

  g_slist_free (instance_ext_list);
  g_slist_free (device_ext_list);

  g_object_unref (pixbuf);
  g_object_unref (example.texture);
  g_object_unref (example.renderer);

  glfwDestroyWindow (example.window);
  glfwTerminate ();

  return 0;
}
