/*
 * Copyright 2022-2024 Frederico de Oliveira Linhares
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "character.hpp"

#include "../com/command.hpp"
#include "../int/core.hpp"
#include "font.hpp"
#include "image.hpp"
#include "source_buffer.hpp"

namespace
{

struct CharacterBuilder
{
  BluCat::GRA::Character *character;
  FT_Face face;
  uint32_t character_code;

  CharacterBuilder(
    BluCat::GRA::Character *character, FT_Face face, uint32_t character_code);
};

CharacterBuilder::CharacterBuilder(
  BluCat::GRA::Character *character, FT_Face face, uint32_t character_code):
  character{character},
  face{face},
  character_code{character_code}
{
}

// TODO: creating one image with one device memory for each character is
// ineficient
void
load_image(void *obj)
{
  auto self = static_cast<CharacterBuilder*>(obj);

  FT_Error error;
  std::vector<uint8_t> source_image_raw;
  const int num_channels = 4; // all images are converted to RGBA
  auto glyph_index{FT_Get_Char_Index(self->face, self->character_code)};

  error = FT_Load_Glyph(self->face, glyph_index, FT_LOAD_DEFAULT);
  if(error) throw CommandError{"failed to load glyph"};

  error = FT_Render_Glyph(self->face->glyph, FT_RENDER_MODE_NORMAL);
  if(error) throw CommandError{"failed to render glyph"};

  self->character->bearing_x = self->face->glyph->bitmap_left;
  self->character->bearing_y = self->face->glyph->bitmap_top;
  self->character->advance = (self->face->glyph->advance.x >> 6);
  self->character->width = self->face->glyph->bitmap.width;
  self->character->height = self->face->glyph->bitmap.rows;
  self->character->mip_levels = 1;

  // Character is a white-space.
  if(self->character->width <= 0)
  {
    self->character->image = VK_NULL_HANDLE;
    self->character->device_memory = VK_NULL_HANDLE;

    return;
  }

  auto image_size{static_cast<size_t>(
      self->face->glyph->bitmap.width *
      self->face->glyph->bitmap.rows * num_channels)};

  { // Create the data for the image buffer
    source_image_raw.resize(image_size, 0);

    for(auto y{0}; y < self->face->glyph->bitmap.width; y++)
    {
      for(auto x{0}; x < self->face->glyph->bitmap.rows; x++)
      {
        auto image_coord = y * self->face->glyph->bitmap.rows * num_channels +
          x * num_channels;
        auto glyph_coord = y * self->face->glyph->bitmap.rows + x;
        // Red
        source_image_raw[image_coord] = 255;
        // Green
        source_image_raw[image_coord + 1] = 255;
        // Blue
        source_image_raw[image_coord + 2] = 255;
        // Alpha
        source_image_raw[image_coord + 3] =
          self->face->glyph->bitmap.buffer[glyph_coord];
      }
    }
  }

  BluCat::GRA::SourceBuffer source_image_buffer{
		BluCat::INT::core.vk_device_with_swapchain, source_image_raw.data(),
    image_size};

  { // Create Vulkan image.
    try
    {
      VkExtent3D vk_extent3d;
      vk_extent3d.width = self->face->glyph->bitmap.width;
      vk_extent3d.height = self->face->glyph->bitmap.rows;
      vk_extent3d.depth = 1;

      BluCat::GRA::Image::create(
        BluCat::INT::core.vk_device_with_swapchain,
	&self->character->image,
	&self->character->device_memory,
	VK_FORMAT_R8G8B8A8_UNORM,
	vk_extent3d,
	self->character->mip_levels,
	VK_IMAGE_TILING_OPTIMAL,
	VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
    }
    catch(BluCat::GRA::Image::Error error)
    {
      throw CommandError{error.what()};
    }
  }

  { // Copy image from buffer into image.
    auto queue_family{BluCat::INT::core.vk_device_with_swapchain->
      get_queue_family_with_presentation()};
    auto queue{queue_family->get_queue()};
    BluCat::GRA::CommandPool command_pool{queue_family, 1};
    VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]};

    queue.submit_one_time_command(vk_command_buffer, [&](){
      BluCat::GRA::Image::move_image_state(
        vk_command_buffer, self->character->image, VK_FORMAT_R8G8B8A8_UNORM,
        0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_HOST_BIT,
        VK_PIPELINE_STAGE_TRANSFER_BIT);

      VkBufferImageCopy image_copy;
      image_copy.bufferOffset = 0;
      image_copy.bufferRowLength = 0;
      image_copy.bufferImageHeight = 0;
      image_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
      image_copy.imageSubresource.mipLevel = 0;
      image_copy.imageSubresource.baseArrayLayer = 0;
      image_copy.imageSubresource.layerCount = 1;
      image_copy.imageOffset = {0, 0, 0};
      image_copy.imageExtent = {
        self->character->width, self->character->height, 1};

      vkCmdCopyBufferToImage(
        vk_command_buffer, source_image_buffer.buffer, self->character->image,
        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);

      BluCat::GRA::Image::move_image_state(
        vk_command_buffer, self->character->image, VK_FORMAT_R8G8B8A8_UNORM,
        VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
        VK_PIPELINE_STAGE_TRANSFER_BIT,
        VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
    });
  }
}

void
unload_image(void *obj)
{
  auto self = static_cast<CharacterBuilder*>(obj);

  vkDestroyImage(
    BluCat::INT::core.vk_device_with_swapchain->device, self->character->image,
		nullptr);
  vkFreeMemory(
    BluCat::INT::core.vk_device_with_swapchain->device,
		self->character->device_memory, nullptr);
}

const CommandChain loader{
  {&load_image, &unload_image}
};

}

namespace BluCat::GRA
{

Character::Character(FT_Face face, uint32_t character_code)
{
  CharacterBuilder character_builder(this, face, character_code);
  loader.execute(&character_builder);
}

Character::~Character()
{
  CharacterBuilder character_builder(this, nullptr, 0);
  loader.revert(&character_builder);
}

std::vector<uint32_t>
Character::str_to_unicode(const char* str)
{
  std::vector<uint32_t> unicode_text;
  int text_width{0};
  int text_height{0};

  { // Reserve memory
    int size{0};
    for(auto i{0}; str[i] != '\0'; i++)
      if((str[i] & 0b11000000) != 0b10000000) size++;
    unicode_text.reserve(size);
  }

  for(auto i{0}; str[i] != '\0'; i++)
  {
    int num_bytes;
    uint32_t codepoint{0};

    if(str[i] >= 0 && str[i] < 127)
    { // Normal ASCI character, 1-byte.
      num_bytes = 1;
      codepoint = str[i];
    }
    else if((str[i] & 0xE0) == 0xC0)
    { // 2-byte character.
      num_bytes = 2;
      codepoint += ((str[i] & 0b00011111) << 6);
    }
    else if((str[i] & 0xF0) == 0xE0)
    { // 3-byte character.
      num_bytes = 3;
      codepoint += ((str[i] & 0b00001111) << 12);
    }
    else if((str[i] & 0xF8) == 0xF0)
    { // 4-byte character.
      num_bytes = 4;
      codepoint += ((str[i] & 0b00000111) << 18);
    }
    else
    { // FIXME: Add support to 5-byte and 6-byte characters.
    }

    switch (num_bytes)
    {
    case 4:
      i++;
      codepoint += ((str[i] & 0b00111111) << 12);
    case 3:
      i++;
      codepoint += ((str[i] & 0b00111111) << 6);
    case 2:
      i++;
      codepoint += (str[i] & 0b00111111);
    case 1:
    default:
      break;
    }

    unicode_text.push_back(codepoint);
  }

  return unicode_text;
}

}