// Copyright (c) 2022 Dominic Masters
// 
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

#include "TrueTypeFont.hpp"

#ifndef  STB_TRUETYPE_IMPLEMENTATION
  #define STB_TRUETYPE_IMPLEMENTATION
  #include <stb_truetype.h>
#endif

using namespace Dawn;

void TrueTypeFont::bakeQuad(truetypequad_t *quad,float_t *x,float_t *y,char c){
  assertNotNull(quad);
  assertNotNull(x);
  assertNotNull(y);
  assertTrue(c >= TRUETYPE_FIRST_CHAR);
  assertTrue(c < (TRUETYPE_FIRST_CHAR+TRUETYPE_NUM_CHARS));

  stbtt_GetBakedQuad(
    this->characterData,
    this->texture.getWidth(), this->texture.getHeight(),
    ((int32_t)c) - TRUETYPE_FIRST_CHAR,
    x, y, quad,
    TRUETYPE_FILL_MODE
  );
}


float_t TrueTypeFont::getScale(float_t scale) {
  assertTrue(scale > 0);
  return scale / this->fontSize;
}


float_t TrueTypeFont::getSpaceSize(float_t fontSize) {
  assertTrue(fontSize > 0);
  return this->getScale(fontSize) * 48;
}

float_t TrueTypeFont::getInitialLineHeight(float_t fontSize) {
  assertTrue(fontSize > 0);
  return 42.0f * this->getScale(fontSize);
}

float_t TrueTypeFont::getLineHeight(float_t fontSize) {
  assertTrue(fontSize > 0);
  return 128.0f * this->getScale(fontSize);
}

void TrueTypeFont::buffer(
  std::string text,
  float_t fontSize,
  float_t maxWidth,
  Mesh *mesh,
  struct FontMeasure *info
) {
  assertNotNull(mesh);
  assertNotNull(info);
  assertTrue(fontSize > 0);
  assertTrue(maxWidth == -1 || maxWidth > 0);
  assertTrue(this->isReady());

  auto stringLength = text.length();
  if(stringLength == 0) {
    info->length = 0;
    info->realLength = 0;
    info->lines.clear();
    info->height = 0;
    info->width = 0;
    info->height = 0.0f;
    info->lineHeight = 0.0f;
    mesh->createBuffers(0, 0);
    return;
  }

  auto quads = new truetypequad_t[stringLength];
  assertNotNull(quads);
  
  // Get the font scale
  auto scale = this->getScale(fontSize);

  assertTrue(scale > 0);

  // Adjust the max width to match the scale, and allow "no max width".
  maxWidth = maxWidth == -1 ? 9999999 : maxWidth * (1 / scale);

  // Which index in the original text var is the current word from
  int32_t wordStart = 0;

  // Setup Scales
  info->length = 0;
  info->realLength = 0;
  info->lines.clear();
  info->lineHeight = this->getLineHeight(fontSize) * scale;

  // Prepare the line counters
  info->addLine(0, 0);

  // Reset Dimensions
  info->width = info->height = 0;
  
  // Setup the initial loop state, and X/Y coords for the quad.
  int32_t i = 0;
  float_t x = 0;
  float_t y = 0;

  // Bake the first quad.
  this->bakeQuad(quads, &x, &y, 'D');
  y = -(quads->y0);
  x = 0;

  // y = this->getInitialLineHeight(fontSize) / scale;
  float_t wordX = 0;
  char c;
  while(c = text[i++]) {
    info->length++;

    // When space, start of new word about to begin
    if(c == FONT_SPACE) {
      x += this->getSpaceSize(fontSize) / scale;

      // Did this space cause a newline?
      if(x > maxWidth) {
        info->addLine(info->realLength, 0);
        y += this->getLineHeight(fontSize) / scale;
        x = 0;
      }
      wordX = x;
      wordStart = info->realLength;
      continue;
    }

    // New line.
    if(c == FONT_NEWLINE) {
      info->addLine(info->realLength, 0);
      wordStart = info->realLength;
      y += this->getLineHeight(fontSize) / scale;
      x = 0;
      continue;
    }

    // Generate the quad.
    auto quad = quads + info->realLength;
    this->bakeQuad(quad, &x, &y, c);
    
    // Now measure the width of the line (take the right side of that quad)
    if(quad->x1 > maxWidth) {
      // We've exceeded the edge, go back to the start of the word and newline.
      x = quad->x1 - wordX;
      for(auto j = wordStart; j <= info->realLength; j++) {
        quad = quads + j;
        quad->x0 -= wordX;
        quad->x1 -= wordX;
        quad->y0 += this->getLineHeight(fontSize) / scale;
        quad->y1 += this->getLineHeight(fontSize) / scale;
      }

      // Go back to the previous (still current) line and remove the chars
      info->lines[info->lines.size() - 1].length -= info->realLength - wordStart;

      // Next line begins with this word
      y += this->getLineHeight(fontSize) / scale;
      info->addLine(wordStart, info->realLength-wordStart);
      wordX = 0;
    }

    info->lines[info->lines.size() - 1].length++;
    info->realLength++;
  }

  // Initialize primitive
  mesh->createBuffers(
    QUAD_VERTICE_COUNT * info->realLength,
    QUAD_INDICE_COUNT * info->realLength
  );
  for(auto j = 0; j < info->realLength; j++) {
    auto quad = quads + j;

    // Scale the Quad
    if(scale != 1.0) {
      quad->x0 *= scale;
      quad->x1 *= scale;
      quad->y0 *= scale;
      quad->y1 *= scale;
    }

    // Update the dimensions.
    info->width = mathMax<float_t>(info->width, quad->x1);
    info->height = mathMax<float_t>(info->height, quad->y1);

    // Buffer the quad.
    QuadMesh::bufferQuadMesh(mesh, 
      glm::vec2(quad->x0, quad->y0), glm::vec2(quad->s0, quad->t0),
      glm::vec2(quad->x1, quad->y1), glm::vec2(quad->s1, quad->t1),
      j * QUAD_VERTICE_COUNT, j * QUAD_INDICE_COUNT
    );
  }
  
  delete quads;
}

bool_t TrueTypeFont::isReady() {
  return this->texture.isReady();
}

Texture * TrueTypeFont::getTexture() {
  return &this->texture;
}

void TrueTypeFont::draw(Mesh *mesh, int32_t startchar, int32_t length) {
  assertNotNull(mesh);

  mesh->draw(
    MESH_DRAW_MODE_TRIANGLES,
    startchar * QUAD_INDICE_COUNT,
    length == -1 ? length : length * QUAD_INDICE_COUNT
  );
}

float_t TrueTypeFont::getDefaultFontSize() {
  return (float_t)this->fontSize;
}