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


#include <gmodule.h>
#include <graphene.h>

#include "gulkan-vertex-buffer.h"

struct _GulkanVertexBuffer
{
  GObject parent;

  VkDevice device;

  VkBuffer buffer;
  VkDeviceMemory memory;

  VkBuffer index_buffer;
  VkDeviceMemory index_memory;

  uint32_t count;

  GArray *array;
};

G_DEFINE_TYPE (GulkanVertexBuffer, gulkan_vertex_buffer, G_TYPE_OBJECT)

static void
gulkan_vertex_buffer_finalize (GObject *gobject);

static void
gulkan_vertex_buffer_class_init (GulkanVertexBufferClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = gulkan_vertex_buffer_finalize;
}

static void
gulkan_vertex_buffer_init (GulkanVertexBuffer *self)
{
  self->device = VK_NULL_HANDLE;
  self->buffer = VK_NULL_HANDLE;
  self->memory = VK_NULL_HANDLE;
  self->index_buffer = VK_NULL_HANDLE;
  self->index_memory = VK_NULL_HANDLE;
  self->count = 0;
  self->array = g_array_new (FALSE, FALSE, sizeof (float));
}

GulkanVertexBuffer *
gulkan_vertex_buffer_new (void)
{
  return (GulkanVertexBuffer*) g_object_new (GULKAN_TYPE_VERTEX_BUFFER, 0);
}

static void
gulkan_vertex_buffer_finalize (GObject *gobject)
{
  GulkanVertexBuffer *self = GULKAN_VERTEX_BUFFER (gobject);

  g_array_free (self->array, TRUE);

  if (self->device == VK_NULL_HANDLE)
    return;

  vkDestroyBuffer (self->device, self->buffer, NULL);
  vkFreeMemory (self->device, self->memory, NULL);

  if (self->index_buffer != VK_NULL_HANDLE)
    vkDestroyBuffer (self->device, self->index_buffer, NULL);

  if (self->index_memory != VK_NULL_HANDLE)
    vkFreeMemory (self->device, self->index_memory, NULL);
}

void
gulkan_vertex_buffer_draw (GulkanVertexBuffer *self, VkCommandBuffer cmd_buffer)
{
  VkDeviceSize offsets[1] = {0};
  vkCmdBindVertexBuffers (cmd_buffer, 0, 1, &self->buffer, &offsets[0]);
  vkCmdDraw (cmd_buffer, self->count, 1, 0, 0);
}

void
gulkan_vertex_buffer_draw_indexed (GulkanVertexBuffer *self,
                                   VkCommandBuffer cmd_buffer)
{
  VkDeviceSize offsets[1] = {0};
  vkCmdBindVertexBuffers (cmd_buffer, 0, 1, &self->buffer, &offsets[0]);
  vkCmdBindIndexBuffer (cmd_buffer, self->index_buffer, 0, VK_INDEX_TYPE_UINT16);
  vkCmdDrawIndexed (cmd_buffer, self->count, 1, 0, 0, 0);
}

void
gulkan_vertex_buffer_reset (GulkanVertexBuffer *self)
{
  g_array_free (self->array, TRUE);

  self->array = g_array_new (FALSE, FALSE, sizeof (float));
  self->count = 0;
}

static void
_append_float (GArray *array, float v)
{
  g_array_append_val (array, v);
}

static void
gulkan_vertex_buffer_append_vec2f (GulkanVertexBuffer *self, float u, float v)
{
  _append_float (self->array, u);
  _append_float (self->array, v);
}

static void
gulkan_vertex_buffer_append_vec3 (GulkanVertexBuffer *self, graphene_vec3_t *v)
{
  _append_float (self->array, graphene_vec3_get_x (v));
  _append_float (self->array, graphene_vec3_get_y (v));
  _append_float (self->array, graphene_vec3_get_z (v));
}

static void
gulkan_vertex_buffer_append_vec4 (GulkanVertexBuffer *self, graphene_vec4_t *v)
{
  _append_float (self->array, graphene_vec4_get_x (v));
  _append_float (self->array, graphene_vec4_get_y (v));
  _append_float (self->array, graphene_vec4_get_z (v));
}

void
gulkan_vertex_buffer_append_with_color (GulkanVertexBuffer *self,
                                        graphene_vec4_t *vec,
                                        graphene_vec3_t *color)
{
  gulkan_vertex_buffer_append_vec4 (self, vec);
  gulkan_vertex_buffer_append_vec3 (self, color);

  self->count++;
}

void
gulkan_vertex_buffer_append_position_uv (GulkanVertexBuffer *self,
                                         graphene_vec4_t *vec,
                                         float u, float v)
{
  gulkan_vertex_buffer_append_vec4 (self, vec);
  gulkan_vertex_buffer_append_vec2f (self, u, v);

  self->count++;
}

bool
gulkan_vertex_buffer_alloc_array (GulkanVertexBuffer *self,
                                  GulkanDevice       *device)
{
  self->device = gulkan_device_get_handle (device);
  return gulkan_device_create_buffer_from_data (
    device, self->array->data, self->array->len * sizeof (float),
    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
   &self->buffer, &self->memory);
}

bool
gulkan_vertex_buffer_alloc_data (GulkanVertexBuffer *self,
                                 GulkanDevice       *device,
                                 const void         *data,
                                 VkDeviceSize        size)
{
  self->device = gulkan_device_get_handle (device);
  return gulkan_device_create_buffer_from_data (
    device, data, size,
    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
   &self->buffer, &self->memory);
}

bool
gulkan_vertex_buffer_alloc_index_data (GulkanVertexBuffer *self,
                                       GulkanDevice       *device,
                                       const void         *data,
                                       VkDeviceSize        element_size,
                                       guint               element_count)
{
  self->device = gulkan_device_get_handle (device);
  bool ret = gulkan_device_create_buffer_from_data (
    device, data, element_size * element_count,
    VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
   &self->index_buffer, &self->index_memory);

  self->count = element_count;

  return ret;
}

bool
gulkan_vertex_buffer_alloc_empty (GulkanVertexBuffer *self,
                                  GulkanDevice       *device,
                                  uint32_t            multiplier)
{
  self->device = gulkan_device_get_handle (device);
  if (self->array->len == 0)
    {
      g_printerr ("Vertex array is empty.\n");
      return FALSE;
    }

  if (!gulkan_device_create_buffer (
    device, sizeof (float) * self->array->len * multiplier,
    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
   &self->buffer, &self->memory))
      return FALSE;

  return TRUE;
}

void
gulkan_vertex_buffer_map_array (GulkanVertexBuffer *self)
{
  if (self->buffer == VK_NULL_HANDLE || self->array->len == 0)
    {
      g_printerr ("Invalid vertex buffer array.\n");
      if (self->buffer == VK_NULL_HANDLE)
        g_printerr ("Buffer is NULL_HANDLE\n");
      if (self->array->len == 0)
        g_printerr ("Array has len 0\n");
      return;
    }

  void *data;
  VkResult res = vkMapMemory (self->device, self->memory,
                              0, VK_WHOLE_SIZE, 0, &data);
  if (res != VK_SUCCESS)
    {
      g_printerr ("vkMapMemory returned error %d\n", res);
      return;
    }
  memcpy (data, self->array->data, self->array->len * sizeof (float));
  vkUnmapMemory (self->device, self->memory);

  VkMappedMemoryRange range = {
    .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
    .memory = self->memory,
    .size = VK_WHOLE_SIZE
  };
  vkFlushMappedMemoryRanges (self->device, 1, &range);
}

gboolean
gulkan_vertex_buffer_is_initialized (GulkanVertexBuffer *self)
{
  return self->buffer != VK_NULL_HANDLE;
}
