#include "api.h"
#include "graphics/graphics.h"
#include "data/rasterizer.h"
#include "util.h"
#include <stdlib.h>

ColoredString* luax_checkcoloredstrings(lua_State* L, int index, uint32_t* count, ColoredString* stack) {
  if (lua_istable(L, index)) {
    lua_rawgeti(L, index, 2);
    bool nested = lua_type(L, -1) == LUA_TNIL || lua_type(L, -1) == LUA_TTABLE;
    lua_pop(L, 1);

    *count = luax_len(L, index) / (nested ? 1 : 2);
    ColoredString* strings = lovrMalloc(*count * sizeof(*strings));

    if (nested) {
      for (uint32_t i = 0; i < *count; i++) {
        lua_rawgeti(L, index, i + 1);
        luax_check(L, lua_istable(L, -1), "Expected table of tables for multicolor strings");
        lua_rawgeti(L, -1, 1);
        lua_rawgeti(L, -2, 2);
        luax_optcolor(L, -2, strings[i].color);
        luax_check(L, lua_isstring(L, -1), "Expected a string to print");
        strings[i].string = luaL_checklstring(L, -1, &strings[i].length);
        lua_pop(L, 3);
      }
    } else {
      for (uint32_t i = 0; i < *count; i++) {
        lua_rawgeti(L, index, i * 2 + 1);
        lua_rawgeti(L, index, i * 2 + 2);
        luax_optcolor(L, -2, strings[i].color);
        luax_check(L, lua_isstring(L, -1), "Expected a string to print");
        strings[i].string = luaL_checklstring(L, -1, &strings[i].length);
        lua_pop(L, 2);
      }
    }

    return strings;
  } else {
    stack->string = luaL_checklstring(L, index, &stack->length);
    stack->color[0] = stack->color[1] = stack->color[2] = stack->color[3] = 1.f;
    return *count = 1, stack;
  }
}

static int l_lovrFontGetRasterizer(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  const FontInfo* info = lovrFontGetInfo(font);
  luax_pushtype(L, Rasterizer, info->rasterizer);
  return 1;
}

static int l_lovrFontGetPixelDensity(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  float density = lovrFontGetPixelDensity(font);
  lua_pushnumber(L, density);
  return 1;
}

static int l_lovrFontSetPixelDensity(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  Rasterizer* rasterizer = lovrFontGetInfo(font)->rasterizer;
  float pixelDensity = luax_optfloat(L, 2, lovrRasterizerGetLeading(rasterizer));
  lovrFontSetPixelDensity(font, pixelDensity);
  return 0;
}

static int l_lovrFontGetLineSpacing(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  float spacing = lovrFontGetLineSpacing(font);
  lua_pushnumber(L, spacing);
  return 1;
}

static int l_lovrFontSetLineSpacing(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  float spacing = luax_optfloat(L, 2, 1.f);
  lovrFontSetLineSpacing(font, spacing);
  return 0;
}

static int l_lovrFontGetAscent(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  const FontInfo* info = lovrFontGetInfo(font);
  float density = lovrFontGetPixelDensity(font);
  float ascent = lovrRasterizerGetAscent(info->rasterizer);
  lua_pushnumber(L, ascent / density);
  return 1;
}

static int l_lovrFontGetDescent(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  const FontInfo* info = lovrFontGetInfo(font);
  float density = lovrFontGetPixelDensity(font);
  float descent = lovrRasterizerGetDescent(info->rasterizer);
  lua_pushnumber(L, descent / density);
  return 1;
}

static int l_lovrFontGetHeight(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  const FontInfo* info = lovrFontGetInfo(font);
  float density = lovrFontGetPixelDensity(font);
  float height = lovrRasterizerGetLeading(info->rasterizer);
  lua_pushnumber(L, height / density);
  return 1;
}

static int l_lovrFontGetKerning(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  Rasterizer* rasterizer = lovrFontGetInfo(font)->rasterizer;
  uint32_t first = luax_checkcodepoint(L, 2);
  uint32_t second = luax_checkcodepoint(L, 3);
  float kerning = lovrRasterizerGetKerning(rasterizer, first, second);
  float density = lovrFontGetPixelDensity(font);
  lua_pushnumber(L, kerning / density);
  return 1;
}

static int l_lovrFontGetWidth(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  uint32_t count;
  ColoredString stack;
  ColoredString* strings = luax_checkcoloredstrings(L, 2, &count, &stack);
  float width = lovrFontGetWidth(font, strings, count);
  lua_pushnumber(L, width);
  return 1;
}

static void online(void* context, const char* string, size_t length) {
  lua_State* L = context;
  int index = luax_len(L, -1) + 1;
  lua_pushlstring(L, string, length);
  lua_rawseti(L, -2, index);
}

static int l_lovrFontGetLines(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  uint32_t count;
  ColoredString stack;
  ColoredString* strings = luax_checkcoloredstrings(L, 2, &count, &stack);
  float wrap = luax_optfloat(L, 3, -1.f);
  lua_newtable(L);
  lovrFontGetLines(font, strings, count, wrap, online, L);
  if (strings != &stack) lovrFree(strings);
  return 1;
}

static int l_lovrFontGetVertices(lua_State* L) {
  Font* font = luax_checktype(L, 1, Font);
  uint32_t count;
  ColoredString stack;
  ColoredString* strings = luax_checkcoloredstrings(L, 2, &count, &stack);
  float wrap = luax_optfloat(L, 3, 0.f);
  HorizontalAlign halign = luax_checkenum(L, 4, HorizontalAlign, "center");
  VerticalAlign valign = luax_checkenum(L, 5, VerticalAlign, "middle");
  size_t totalLength = 0;
  for (uint32_t i = 0; i < count; i++) {
    totalLength += strings[i].length;
  }

  GlyphVertex* vertices = lovrMalloc(totalLength * 4 * sizeof(GlyphVertex));
  uint32_t glyphCount, lineCount;
  Material* material;

  bool success = lovrFontGetVertices(font, strings, count, wrap, halign, valign, vertices, &glyphCount, &lineCount, &material, false);
  if (strings != &stack) lovrFree(strings);

  if (!success) {
    lovrFree(vertices);
    luax_assert(L, false);
  }

  int vertexCount = glyphCount * 4;
  lua_createtable(L, vertexCount, 0);
  for (int i = 0; i < vertexCount; i++) {
    lua_createtable(L, 4, 0);
    lua_pushnumber(L, vertices[i].position.x);
    lua_rawseti(L, -2, 1);
    lua_pushnumber(L, vertices[i].position.y);
    lua_rawseti(L, -2, 2);
    lua_pushnumber(L, vertices[i].uv.u / 65535.f);
    lua_rawseti(L, -2, 3);
    lua_pushnumber(L, vertices[i].uv.v / 65535.f);
    lua_rawseti(L, -2, 4);
    lua_rawseti(L, -2, i + 1);
  }
  luax_pushtype(L, Material, material);
  lovrFree(vertices);
  return 2;
}

const luaL_Reg lovrFont[] = {
  { "getRasterizer", l_lovrFontGetRasterizer },
  { "getPixelDensity", l_lovrFontGetPixelDensity },
  { "setPixelDensity", l_lovrFontSetPixelDensity },
  { "getLineSpacing", l_lovrFontGetLineSpacing },
  { "setLineSpacing", l_lovrFontSetLineSpacing },
  { "getAscent", l_lovrFontGetAscent },
  { "getDescent", l_lovrFontGetDescent },
  { "getHeight", l_lovrFontGetHeight },
  { "getKerning", l_lovrFontGetKerning },
  { "getWidth", l_lovrFontGetWidth },
  { "getLines", l_lovrFontGetLines },
  { "getVertices", l_lovrFontGetVertices },
  { NULL, NULL }
};
