#include "data/rasterizer.h"
#include "data/blob.h"
#include "data/image.h"
#include "util.h"
#include "VarelaRound.ttf.h"
#include "lib/stb/stb_truetype.h"
#include <msdfgen-c.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

struct Rasterizer {
  uint32_t ref;
  stbtt_fontinfo font;
  struct Blob* blob;
  float size;
  float scale;
  int glyphCount;
  int height;
  int advance;
  int ascent;
  int descent;
};

Rasterizer* lovrRasterizerCreate(Blob* blob, float size) {
  Rasterizer* rasterizer = calloc(1, sizeof(Rasterizer));
  lovrAssert(rasterizer, "Out of memory");
  rasterizer->ref = 1;

  stbtt_fontinfo* font = &rasterizer->font;
  const unsigned char* data = blob ? blob->data : etc_VarelaRound_ttf;
  if (!stbtt_InitFont(font, data, stbtt_GetFontOffsetForIndex(data, 0))) {
    lovrThrow("Problem loading font");
  }

  lovrRetain(blob);
  rasterizer->blob = blob;
  rasterizer->size = size;
  rasterizer->scale = stbtt_ScaleForMappingEmToPixels(font, size);
  rasterizer->glyphCount = font->numGlyphs;

  int ascent, descent, linegap;
  stbtt_GetFontVMetrics(font, &ascent, &descent, &linegap);
  rasterizer->ascent = roundf(ascent * rasterizer->scale);
  rasterizer->descent = roundf(descent * rasterizer->scale);
  rasterizer->height = roundf((ascent - descent + linegap) * rasterizer->scale);

  int x0, y0, x1, y1;
  stbtt_GetFontBoundingBox(font, &x0, &y0, &x1, &y1);
  rasterizer->advance = roundf(x1 * rasterizer->scale);

  return rasterizer;
}

void lovrRasterizerDestroy(void* ref) {
  Rasterizer* rasterizer = ref;
  lovrRelease(rasterizer->blob, lovrBlobDestroy);
  free(rasterizer);
}

float lovrRasterizerGetSize(Rasterizer* rasterizer) {
  return rasterizer->size;
}

int lovrRasterizerGetGlyphCount(Rasterizer* rasterizer) {
  return rasterizer->glyphCount;
}

int lovrRasterizerGetHeight(Rasterizer* rasterizer) {
  return rasterizer->height;
}

int lovrRasterizerGetAdvance(Rasterizer* rasterizer) {
  return rasterizer->advance;
}

int lovrRasterizerGetAscent(Rasterizer* rasterizer) {
  return rasterizer->ascent;
}

int lovrRasterizerGetDescent(Rasterizer* rasterizer) {
  return rasterizer->descent;
}

bool lovrRasterizerHasGlyph(Rasterizer* rasterizer, uint32_t character) {
  return stbtt_FindGlyphIndex(&rasterizer->font, character) != 0;
}

bool lovrRasterizerHasGlyphs(Rasterizer* rasterizer, const char* str) {
  const char* end = str + strlen(str);
  unsigned int codepoint;
  size_t bytes;

  bool hasGlyphs = true;
  while ((bytes = utf8_decode(str, end, &codepoint)) > 0) {
    hasGlyphs &= lovrRasterizerHasGlyph(rasterizer, codepoint);
    str += bytes;
  }
  return hasGlyphs;
}

void lovrRasterizerLoadGlyph(Rasterizer* rasterizer, uint32_t character, uint32_t padding, double spread, Glyph* glyph) {
  int glyphIndex = stbtt_FindGlyphIndex(&rasterizer->font, character);

  int advance, bearing;
  stbtt_GetGlyphHMetrics(&rasterizer->font, glyphIndex, &advance, &bearing);

  if (stbtt_IsGlyphEmpty(&rasterizer->font, glyphIndex)) {
    memset(glyph, 0, sizeof(Glyph));
    glyph->advance = roundf(advance * rasterizer->scale);
    return;
  }

  // Trace glyph outline
  stbtt_vertex* vertices;
  int vertexCount = stbtt_GetGlyphShape(&rasterizer->font, glyphIndex, &vertices);
  msShape* shape = msShapeCreate();
  msContour* contour = NULL;
  float x = 0.f;
  float y = 0.f;

  for (int i = 0; i < vertexCount; i++) {
    stbtt_vertex vertex = vertices[i];
    float x2 = vertex.x * rasterizer->scale;
    float y2 = vertex.y * rasterizer->scale;

    switch (vertex.type) {
      case STBTT_vmove:
        contour = msShapeAddContour(shape);
        break;

      case STBTT_vline:
        msContourAddLinearEdge(contour, x, y, x2, y2);
        break;

      case STBTT_vcurve: {
        float cx = vertex.cx * rasterizer->scale;
        float cy = vertex.cy * rasterizer->scale;
        msContourAddQuadraticEdge(contour, x, y, cx, cy, x2, y2);
        break;
      }

      case STBTT_vcubic: {
        float cx1 = vertex.cx * rasterizer->scale;
        float cy1 = vertex.cy * rasterizer->scale;
        float cx2 = vertex.cx1 * rasterizer->scale;
        float cy2 = vertex.cy1 * rasterizer->scale;
        msContourAddCubicEdge(contour, x, y, cx1, cy1, cx2, cy2, x2, y2);
        break;
      }
    }

    x = x2;
    y = y2;
  }

  stbtt_FreeShape(&rasterizer->font, vertices);

  int x0, y0, x1, y1;
  stbtt_GetGlyphBox(&rasterizer->font, glyphIndex, &x0, &y0, &x1, &y1);

  // Initialize glyph data
  glyph->x = 0;
  glyph->y = 0;
  glyph->w = ceilf((x1 - x0) * rasterizer->scale);
  glyph->h = ceilf((y1 - y0) * rasterizer->scale);
  glyph->tw = glyph->w + 2 * padding;
  glyph->th = glyph->h + 2 * padding;
  glyph->dx = roundf(bearing * rasterizer->scale);
  glyph->dy = roundf(y1 * rasterizer->scale);
  glyph->advance = roundf(advance * rasterizer->scale);
  glyph->data = lovrImageCreate(glyph->tw, glyph->th, NULL, 0, FORMAT_RGBA32F);

  // Render SDF
  float tx = (float) padding + -glyph->dx;
  float ty = (float) padding + (float) glyph->h - glyph->dy;
  msShapeNormalize(shape);
  msEdgeColoringSimple(shape, 3., 0);
  msGenerateMTSDF(glyph->data->blob->data, glyph->tw, glyph->th, shape, spread, 1.f, 1.f, tx, ty);
  msShapeDestroy(shape);
}

int32_t lovrRasterizerGetKerning(Rasterizer* rasterizer, uint32_t left, uint32_t right) {
  return stbtt_GetCodepointKernAdvance(&rasterizer->font, left, right) * rasterizer->scale;
}

void lovrRasterizerMeasure(Rasterizer* rasterizer, const char* str, size_t length, float wrap, float* width, float* lastLineWidth, float* height, uint32_t* lineCount, uint32_t* glyphCount) {
  float x = 0.f;
  const char* end = str + length;
  size_t bytes;
  unsigned int previous = '\0';
  unsigned int codepoint;
  *width = 0.f;
  *lastLineWidth = 0.f;
  *lineCount = 0;
  *glyphCount = 0;

  while ((bytes = utf8_decode(str, end, &codepoint)) > 0) {
    if (codepoint == '\n' || (wrap && x > wrap && (codepoint == ' ' || previous == ' '))) {
      *width = MAX(*width, x);
      (*lineCount)++;
      x = 0.f;
      previous = '\0';
      if (codepoint == ' ' || codepoint == '\n') {
        str += bytes;
        continue;
      }
    }

    // Tabs
    if (codepoint == '\t') {
      int glyphIndex = stbtt_FindGlyphIndex(&rasterizer->font, ' ');
      int advance, bearing;
      stbtt_GetGlyphHMetrics(&rasterizer->font, glyphIndex, &advance, &bearing);
      x += advance * 4.f;
      str += bytes;
      continue;
    }

    int glyphIndex = stbtt_FindGlyphIndex(&rasterizer->font, codepoint);
    int advance, bearing;
    stbtt_GetGlyphHMetrics(&rasterizer->font, glyphIndex, &advance, &bearing);
    int x0, y0, x1, y1;
    if (stbtt_GetGlyphBox(&rasterizer->font, glyphIndex, &x0, &y0, &x1, &y1)) {
      float w = ceilf((x1 - x0) * rasterizer->scale);
      float h = ceilf((y1 - y0) * rasterizer->scale);
      if (w > 0 && h > 0) {
        (*glyphCount)++;
      }
    }

    x += roundf(advance * rasterizer->scale) + lovrRasterizerGetKerning(rasterizer, previous, codepoint);
    previous = codepoint;
    str += bytes;
  }

  *width = MAX(*width, x);
  *lastLineWidth = x;
  *height = ((*lineCount + 1) * lovrRasterizerGetHeight(rasterizer));
}
