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

#include "volk/volk.h"
#include "vulkan/vk_enum_string_helper.h"

#include "directxmath/directxmath.h"
#include "vulkan_helper.h"
#include "check.h"
#include "file.h"

#include "font/outline.h"
#include "font/outline_types.h"
#include "renpy/script.h"

namespace font::outline {
  static const _Float16 vertexData[] = {
    // x y u v
    (_Float16)-1.0, (_Float16)-1.0, (_Float16)0.0, (_Float16)0.0,
    (_Float16)1.0, (_Float16)-1.0, (_Float16)1.0, (_Float16)0.0,
    (_Float16)-1.0,  (_Float16)1.0, (_Float16)0.0, (_Float16)1.0,
    (_Float16)1.0,  (_Float16)1.0, (_Float16)1.0, (_Float16)1.0,
  };
  static const uint32_t vertexSize = (sizeof (vertexData));

  static const uint16_t indexData[] = {
    0, 1, 2, 3,
  };
  static const uint32_t indexSize = (sizeof (indexData));

  void font::initial_state(VkInstance instance,
                           VkDevice device,
                           VkQueue queue,
                           VkCommandPool commandPool,
                           VkPhysicalDeviceProperties physicalDeviceProperties,
                           VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties,
                           VkFormat colorFormat,
                           VkFormat depthFormat,
                           VkSampler linearSampler)
  {
    this->instance = instance;
    this->device = device;
    this->queue = queue;
    this->commandPool = commandPool;

    this->physicalDeviceProperties = physicalDeviceProperties;
    this->physicalDeviceMemoryProperties = physicalDeviceMemoryProperties;

    this->colorFormat = colorFormat;
    this->depthFormat = depthFormat;

    this->linearSampler = linearSampler;
  }

  void font::init()
  {
    load_vertex_index_buffer();
    load_shader();
    create_descriptor_sets();
    loadedFont = load_font(medieval_sharp[0]);
    create_glyphs_buffer(loadedFont.font, loadedFont.glyphs);
    write_descriptor_sets(loadedFont.allocatedImage.imageView);
    create_instance_buffers();
    create_pipeline();
  }

  //////////////////////////////////////////////////////////////////////
  // vertex index buffer
  //////////////////////////////////////////////////////////////////////

  void font::load_vertex_index_buffer()
  {
    void const * vertexStart = (void const *)vertexData;
    void const * indexStart = (void const *)indexData;

    vertexIndex = createVertexIndexBuffer(device,
                                          physicalDeviceProperties,
                                          physicalDeviceMemoryProperties,
                                          vertexStart, vertexSize,
                                          indexStart, indexSize);
  }

  //////////////////////////////////////////////////////////////////////
  // shader
  //////////////////////////////////////////////////////////////////////

  void font::load_shader()
  {
    uint32_t shaderSize;
    void const * shaderStart = file::open("shader/font.spv", &shaderSize);

    VkShaderModuleCreateInfo shaderModuleCreateInfo{
      .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
      .codeSize = shaderSize,
      .pCode = (uint32_t *)shaderStart
    };
    VK_CHECK(vkCreateShaderModule(device, &shaderModuleCreateInfo, nullptr, &shaderModule));
  }

  //////////////////////////////////////////////////////////////////////
  // pipeline
  //////////////////////////////////////////////////////////////////////

  void font::create_pipeline()
  {
    VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
      .setLayoutCount = descriptorSetLayoutCount,
      .pSetLayouts = descriptorSetLayouts,
      .pushConstantRangeCount = 0,
      .pPushConstantRanges = nullptr
    };
    VK_CHECK(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));

    VkPipelineInputAssemblyStateCreateInfo inputAssemblyState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
      .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP
    };

    VkPipelineShaderStageCreateInfo shaderStages[2]{
      {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_VERTEX_BIT,
        .module = shaderModule,
        .pName = "VSMain"
      },
      {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
        .module = shaderModule,
        .pName = "PSMain"
      }
    };

    VkPipelineViewportStateCreateInfo viewportState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
      .viewportCount = 1,
      .scissorCount = 1
    };

    constexpr uint32_t dynamicStateCount = 2;
    VkDynamicState dynamicStates[dynamicStateCount]{
      VK_DYNAMIC_STATE_VIEWPORT,
      VK_DYNAMIC_STATE_SCISSOR,
    };
    VkPipelineDynamicStateCreateInfo dynamicState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
      .dynamicStateCount = dynamicStateCount,
      .pDynamicStates = dynamicStates
    };

    VkPipelineDepthStencilStateCreateInfo depthStencilState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
      .depthTestEnable = VK_FALSE,
      .depthWriteEnable = VK_FALSE,
      .depthCompareOp = VK_COMPARE_OP_ALWAYS,
      .stencilTestEnable = VK_FALSE,
      .front = {
        .failOp = VK_STENCIL_OP_REPLACE,
        .passOp = VK_STENCIL_OP_REPLACE,
        .depthFailOp = VK_STENCIL_OP_REPLACE,
        .compareOp = VK_COMPARE_OP_ALWAYS,
        .compareMask = 0x01,
        .writeMask = 0x01,
        .reference = 1,
      },
      .back = {
        .failOp = VK_STENCIL_OP_REPLACE,
        .passOp = VK_STENCIL_OP_REPLACE,
        .depthFailOp = VK_STENCIL_OP_REPLACE,
        .compareOp = VK_COMPARE_OP_ALWAYS,
        .compareMask = 0x01,
        .writeMask = 0x01,
        .reference = 1,
      },
    };

    VkPipelineRenderingCreateInfo renderingCreateInfo{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
      .colorAttachmentCount = 1,
      .pColorAttachmentFormats = &colorFormat,
      .depthAttachmentFormat = depthFormat,
      .stencilAttachmentFormat = depthFormat
    };

    VkPipelineColorBlendAttachmentState blendAttachment{
      .blendEnable = VK_TRUE,
      .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
      .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
      .colorBlendOp = VK_BLEND_OP_ADD,
      .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
      .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
      .alphaBlendOp = VK_BLEND_OP_ADD,
      .colorWriteMask = 0xF,
    };
    VkPipelineColorBlendStateCreateInfo colorBlendState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
      .attachmentCount = 1,
      .pAttachments = &blendAttachment
    };
    VkPipelineRasterizationStateCreateInfo rasterizationState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
      //.cullMode = VK_CULL_MODE_BACK_BIT,
      .cullMode = VK_CULL_MODE_NONE,
      .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
      .lineWidth = 1.0f
    };
    VkPipelineMultisampleStateCreateInfo multisampleState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
      .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
    };

    constexpr int vertexBindingDescriptionsCount = 2;
    VkVertexInputBindingDescription vertexBindingDescriptions[vertexBindingDescriptionsCount]{
      {
        .binding = 0,
        .stride = perVertexSize,
        .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
      },
      {
        .binding = 1,
        .stride = perInstanceSize,
        .inputRate = VK_VERTEX_INPUT_RATE_INSTANCE
      }
    };

    constexpr int vertexAttributeDescriptionsCount = 5;
    VkVertexInputAttributeDescription vertexAttributeDescriptions[vertexAttributeDescriptionsCount]{
      // per-vertex
      { // position
        .location = 0,
        .binding = 0,
        .format = VK_FORMAT_R16G16_SFLOAT,
        .offset = 0,
      },
      { // texture
        .location = 1,
        .binding = 0,
        .format = VK_FORMAT_R16G16_SFLOAT,
        .offset = 4,
      },
      // per-instance
      {
        .location = 2,
        .binding = 1,
        .format = VK_FORMAT_R16G16_UINT,
        .offset = 0,
      },
      {
        .location = 3,
        .binding = 1,
        .format = VK_FORMAT_R32_UINT,
        .offset = 4,
      },
      {
        .location = 4,
        .binding = 1,
        .format = VK_FORMAT_R8G8B8A8_UNORM,
        .offset = 8,
      },
    };

    VkPipelineVertexInputStateCreateInfo vertexInputState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
      .vertexBindingDescriptionCount = vertexBindingDescriptionsCount,
      .pVertexBindingDescriptions = vertexBindingDescriptions,
      .vertexAttributeDescriptionCount = vertexAttributeDescriptionsCount,
      .pVertexAttributeDescriptions = vertexAttributeDescriptions,
    };

    VkGraphicsPipelineCreateInfo pipelineCreateInfos[1]{
      {
        .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
        .pNext = &renderingCreateInfo,
        .stageCount = 2,
        .pStages = shaderStages,
        .pVertexInputState = &vertexInputState,
        .pInputAssemblyState = &inputAssemblyState,
        .pViewportState = &viewportState,
        .pRasterizationState = &rasterizationState,
        .pMultisampleState = &multisampleState,
        .pDepthStencilState = &depthStencilState,
        .pColorBlendState = &colorBlendState,
        .pDynamicState = &dynamicState,
        .layout = pipelineLayout
      },
    };

    VK_CHECK(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, pipelineCreateInfos, nullptr, &pipeline));
  }

  //////////////////////////////////////////////////////////////////////
  // load font
  //////////////////////////////////////////////////////////////////////

  LoadedFont font::load_font(font_desc const& desc)
  {
    uint32_t font_data_size;
    void const * font_data = file::open(desc.path, &font_data_size);
    assert(font_data != nullptr);

    types::font * font = (types::font *)font_data;
    types::glyph * glyphs = (types::glyph *)(((ptrdiff_t)font_data) + (sizeof (types::font)));

    void * texture_data = (void *)(((ptrdiff_t)glyphs) + (sizeof (types::glyph)) * font->glyph_count);

    ptrdiff_t font_end = ((ptrdiff_t)font_data) + font_data_size;
    int texture_size = font->texture_width * font->texture_height;
    assert(font_end - ((ptrdiff_t)texture_data) == texture_size);

    // transfer texture

    VkCommandBuffer commandBuffer{};
    VkCommandBufferAllocateInfo commandBufferAllocateInfo{
      .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
      .commandPool = commandPool,
      .commandBufferCount = 1
    };
    VK_CHECK(vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer));

    VkFenceCreateInfo fenceCreateInfo{
      .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
    };
    VkFence fence{};
    VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));

    void * imageData = texture_data;
    uint32_t imageDataSize = texture_size;
    VkFormat format = VK_FORMAT_R8_UNORM;
    uint32_t width = font->texture_width;
    uint32_t height = font->texture_height;
    uint32_t levelCount = 1;
    uint32_t levelOffset = 0;
    VkImage outImage;
    VkDeviceMemory outMemory;
    VkImageView outImageView;

    createImage(device,
                physicalDeviceProperties.limits.nonCoherentAtomSize,
                physicalDeviceMemoryProperties,
                format,
                width,
                height,
                levelCount,
                &outImage,
                &outMemory,
                &outImageView);

    textureTransfer(device,
                    queue,
                    commandBuffer,
                    fence,
                    physicalDeviceProperties.limits.nonCoherentAtomSize,
                    physicalDeviceMemoryProperties,
                    imageDataSize,
                    imageData,
                    outImage,
                    width,
                    height,
                    levelCount,
                    &levelOffset);

    vkDestroyFence(device, fence, nullptr);
    vkFreeCommandBuffers(device,
                         commandPool,
                         1,
                         &commandBuffer);

    // return
    return {
      .font = font,
      .glyphs = glyphs,
      .allocatedImage = {
        .image = outImage,
        .memory = outMemory,
        .imageView = outImageView,
      },
    };
  }

  //////////////////////////////////////////////////////////////////////
  // descriptor sets
  //////////////////////////////////////////////////////////////////////

  void font::create_descriptor_sets()
  {
    //
    // pool
    //
    constexpr int descriptorPoolSizesCount = 3;
    VkDescriptorPoolSize descriptorPoolSizes[descriptorPoolSizesCount]{
      { // linear sampler
        .type = VK_DESCRIPTOR_TYPE_SAMPLER,
        .descriptorCount = 1,
      },
      {
        .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
        .descriptorCount = 1,
      },
      {
        .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
        .descriptorCount = 1,
      },
    };
    VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{
      .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
      .maxSets = 1,
      .poolSizeCount = descriptorPoolSizesCount,
      .pPoolSizes = descriptorPoolSizes
    };
    VK_CHECK(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool));

    //
    // (set 0, constant)
    //
    {
      constexpr int bindingCount = 3;
      VkDescriptorSetLayoutBinding descriptorSetLayoutBindings[bindingCount]{
        {
          .binding = 0,
          .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
          .descriptorCount = 1,
          .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
        },
        { // font image
          .binding = 1,
          .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
          .descriptorCount = 1,
          .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
        },
        {
          .binding = 2,
          .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
          .descriptorCount = 1,
          .stageFlags = VK_SHADER_STAGE_VERTEX_BIT
        }
      };

      VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
        .bindingCount = bindingCount,
        .pBindings = descriptorSetLayoutBindings
      };
      VK_CHECK(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayouts[0]));

      VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
        .descriptorPool = descriptorPool,
        .descriptorSetCount = 1,
        .pSetLayouts = &descriptorSetLayouts[0]
      };
      VK_CHECK(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet0));
    }
  }

  //////////////////////////////////////////////////////////////////////
  // descriptor set writes
  //////////////////////////////////////////////////////////////////////

  void font::write_descriptor_sets(VkImageView fontImageView)
  {
    constexpr uint32_t writeCount = 3;
    VkWriteDescriptorSet writeDescriptorSets[writeCount];
    uint32_t writeIndex = 0;

    // set1 bindings
    VkDescriptorImageInfo samplerDescriptorImageInfo = {
      .sampler = linearSampler,
    };
    writeDescriptorSets[writeIndex++] = {
      .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
      .dstSet = descriptorSet0,
      .dstBinding = 0,
      .descriptorCount = 1,
      .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
      .pImageInfo = &samplerDescriptorImageInfo
    };
    VkDescriptorImageInfo terrainDescriptorImageInfo = {
      .imageView = fontImageView,
      .imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL
    };
    writeDescriptorSets[writeIndex++] = {
      .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
      .dstSet = descriptorSet0,
      .dstBinding = 1,
      .descriptorCount = 1,
      .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
      .pImageInfo = &terrainDescriptorImageInfo
    };
    VkDescriptorBufferInfo glyphsDescriptorBufferInfo{
      .buffer = glyphsBuffer,
      .offset = 0,
      .range = glyphsBufferSize,
    };
    writeDescriptorSets[writeIndex++] = {
      .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
      .dstSet = descriptorSet0,
      .dstBinding = 2,
      .descriptorCount = 1,
      .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
      .pBufferInfo = &glyphsDescriptorBufferInfo
    };
    assert(writeIndex == writeCount);
    vkUpdateDescriptorSets(device, writeIndex, writeDescriptorSets, 0, nullptr);
  }

  //////////////////////////////////////////////////////////////////////
  // create instance buffer
  //////////////////////////////////////////////////////////////////////

  void font::create_instance_buffers()
  {
    constexpr VkDeviceSize bufferSize{ maximumGlyphCount * (sizeof (GlyphInstance)) };
    instanceMemorySize = bufferSize * 2;
    instanceBufferOffset[0] = bufferSize * 0;
    instanceBufferOffset[1] = bufferSize * 1;

    // create buffer
    VkBufferCreateInfo bufferCreateInfo{
      .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
      .size = instanceMemorySize,
      .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
      .sharingMode = VK_SHARING_MODE_EXCLUSIVE
    };
    VK_CHECK(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &instanceBuffer));

    // allocate memory

    VkMemoryRequirements memoryRequirements;
    vkGetBufferMemoryRequirements(device, instanceBuffer, &memoryRequirements);
    VkMemoryPropertyFlags memoryPropertyFlags{ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT };
    VkMemoryAllocateFlags memoryAllocateFlags{};
    VkDeviceSize stride;
    allocateFromMemoryRequirements(device,
                                   physicalDeviceProperties.limits.nonCoherentAtomSize,
                                   physicalDeviceMemoryProperties,
                                   memoryRequirements,
                                   memoryPropertyFlags,
                                   memoryAllocateFlags,
                                   1,
                                   &instanceMemory,
                                   &stride);

    VK_CHECK(vkBindBufferMemory(device, instanceBuffer, instanceMemory, 0));

    // map memory

    VK_CHECK(vkMapMemory(device, instanceMemory, 0, VK_WHOLE_SIZE, 0, (void **)&instanceMappedData));
  }

  //////////////////////////////////////////////////////////////////////
  // create glyphs buffer
  //////////////////////////////////////////////////////////////////////

  void font::create_glyphs_buffer(types::font const * const font, types::glyph const * const glyphs)
  {
    glyphsBufferSize = (sizeof (Glyph)) * font->glyph_count;

    // create buffer
    VkBufferCreateInfo bufferCreateInfo{
      .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
      .size = glyphsBufferSize,
      .usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
      .sharingMode = VK_SHARING_MODE_EXCLUSIVE
    };
    VK_CHECK(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &glyphsBuffer));

    // allocate memory

    VkMemoryRequirements memoryRequirements;
    vkGetBufferMemoryRequirements(device, glyphsBuffer, &memoryRequirements);
    VkMemoryPropertyFlags memoryPropertyFlags{ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT };
    VkMemoryAllocateFlags memoryAllocateFlags{};
    VkDeviceSize stride;
    allocateFromMemoryRequirements(device,
                                   physicalDeviceProperties.limits.nonCoherentAtomSize,
                                   physicalDeviceMemoryProperties,
                                   memoryRequirements,
                                   memoryPropertyFlags,
                                   memoryAllocateFlags,
                                   1,
                                   &glyphsMemory,
                                   &stride);

    VK_CHECK(vkBindBufferMemory(device, glyphsBuffer, glyphsMemory, 0));

    // map memory
    Glyph * glyphsMappedData;
    VK_CHECK(vkMapMemory(device, glyphsMemory, 0, VK_WHOLE_SIZE, 0, (void **)&glyphsMappedData));

    for (int i = 0; i < font->glyph_count; i++) {
      glyphsMappedData[i].x = glyphs[i].bitmap.x;
      glyphsMappedData[i].y = glyphs[i].bitmap.y;
      glyphsMappedData[i].width = glyphs[i].bitmap.width;
      glyphsMappedData[i].height = glyphs[i].bitmap.height;
    }

    // flush
    constexpr int mappedMemoryRangesCount = 1;
    VkMappedMemoryRange mappedMemoryRanges[mappedMemoryRangesCount]{
      {
        .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
        .memory = glyphsMemory,
        .offset = 0,
        .size = VK_WHOLE_SIZE,
      }
    };
    vkFlushMappedMemoryRanges(device, mappedMemoryRangesCount, mappedMemoryRanges);

    vkUnmapMemory(device, glyphsMemory);
  }

  void font::emit_line(int frameIndex,
                       char const * const string,
                       uint32_t x, uint32_t y,
                       int& outputIndex,
                       int startIndex,
                       int endIndex,
                       uint32_t color)
  {
    for (int i = startIndex; i < endIndex; i++) {
      char c = string[i];
      types::glyph const& glyph = loadedFont.glyphs[c - 32];

      if (c != ' ' && c != '\n') {
        instanceMappedData[maximumGlyphCount * frameIndex + outputIndex++] = {
          (uint16_t)((x + glyph.metrics.horiBearingX) >> 6),
          (uint16_t)((y - glyph.metrics.horiBearingY) >> 6),
          (uint32_t)(c - 32),
          color,
        };
      }

      x += glyph.metrics.horiAdvance;
    }
  }

  void font::centered(int frameIndex, char const * const string, uint32_t& x, uint32_t& y,
                      uint32_t minX,
                      uint32_t maxWidth,
                      int& outputIndex,
                      uint32_t color)
  {
    int stringIndex = 0;
    int lineStart = stringIndex;
    int lineWidth = 0;
    uint32_t leftOffset = 0;

    while (true) {
      char c = string[stringIndex++];
      if (c == 0)
        break;

      if (c == '\n') {
        continue;
      }

      types::glyph const& glyph = loadedFont.glyphs[c - 32];

      if (lineWidth == 0) {
        leftOffset = glyph.metrics.horiBearingX;
      }

      lineWidth += glyph.metrics.horiAdvance;

      if ((lineWidth - leftOffset) + loadedFont.font->face_metrics.max_advance > (maxWidth << 6)) {
        while (string[stringIndex] != ' ') {
          char c = string[--stringIndex];
          types::glyph const& glyph = loadedFont.glyphs[c - 32];
          lineWidth -= glyph.metrics.horiAdvance;
        }
        uint32_t center = (minX + (maxWidth / 2)) << 6;
        uint32_t left = center - (lineWidth / 2);
        emit_line(frameIndex, string, left - leftOffset, y, outputIndex, lineStart, stringIndex, color);
        while (string[stringIndex] == ' ') stringIndex++;
        lineStart = stringIndex;
        lineWidth = 0;
        y += loadedFont.font->face_metrics.height * 1.2;
        x = minX << 6;
      }
    };
    if (stringIndex != lineStart) {
      uint32_t center = (minX + (maxWidth / 2)) << 6;
      uint32_t left = center - (lineWidth / 2);
      emit_line(frameIndex, string, left - leftOffset, y, outputIndex, lineStart, stringIndex, color);
      y += loadedFont.font->face_metrics.height * 1.2;
      x = minX << 6;
    }
  }

  //////////////////////////////////////////////////////////////////////
  // draw
  //////////////////////////////////////////////////////////////////////

  void font::draw(VkCommandBuffer commandBuffer,
                  uint32_t frameIndex,
                  renpy::interpreter const& state)
  {
    // transfer
    int outputIndex = 0;

    if (state.menu.count == 0) {
      if (state.say.stringIndex != -1u) {
        char const * const string = renpy::script::strings[state.say.stringIndex];
        uint32_t x = textboxLeft << 6;
        uint32_t y = 590 << 6;
        centered(frameIndex, string,
                 x, y,
                 textboxLeft, textboxWidth,
                 outputIndex,
                 0x000000);
      }

      if (state.say.characterIndex != -1u) {
        const renpy::language::character & character = renpy::script::characters[state.say.characterIndex];
        char const * const string = character.characterName;
        uint32_t x = 580 << 6;
        uint32_t y = 550 << 6;
        centered(frameIndex, string,
                 x, y,
                 580, 118,
                 outputIndex,
                 character.color);
      }
    } else {
      for (uint32_t i = 0; i < state.menu.count; i++) {
        uint32_t x = 400 << 6;
        uint32_t y = (100 * i + 130) << 6;
        centered(frameIndex, renpy::script::options[state.menu.optionIndex + i].string,
                 x, y,
                 400, 480,
                 outputIndex,
                 0x000000);
      }
    }

    // flush
    constexpr int mappedMemoryRangesCount = 1;
    VkMappedMemoryRange mappedMemoryRanges[mappedMemoryRangesCount]{
      {
        .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
        .memory = instanceMemory,
        .offset = 0,
        .size = (sizeof (GlyphInstance)) * outputIndex,
      }
    };
    alignMappedMemoryRanges(physicalDeviceProperties.limits.nonCoherentAtomSize,
                            instanceMemorySize,
                            mappedMemoryRangesCount,
                            mappedMemoryRanges);
    vkFlushMappedMemoryRanges(device, mappedMemoryRangesCount, mappedMemoryRanges);

    // bind/draw

    vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);

    VkDescriptorSet descriptorSets[1] = {
      descriptorSet0,
    };
    vkCmdBindDescriptorSets(commandBuffer,
                            VK_PIPELINE_BIND_POINT_GRAPHICS,
                            pipelineLayout,
                            0, 1, descriptorSets,
                            0, nullptr);

    vkCmdBindIndexBuffer(commandBuffer, vertexIndex.buffer, vertexIndex.indexOffset, VK_INDEX_TYPE_UINT16);

    VkDeviceSize vertexOffsets[2]{ 0, instanceBufferOffset[frameIndex] };
    VkBuffer vertexBuffers[2]{ vertexIndex.buffer, instanceBuffer };
    vkCmdBindVertexBuffers(commandBuffer, 0, 2, vertexBuffers, vertexOffsets);

    vkCmdDrawIndexed(commandBuffer, 4, outputIndex, 0, 0, 0);
  }
}
