#include <font.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static const FT_Int32 LoadFlags = FT_LOAD_DEFAULT;

static void RenderGlyph(
    unsigned char c, const unsigned char* pixels, int width, int height) {
  assert(pixels);

  printf("Glyph (%c), %dx%d\n", c, width, height);

  const unsigned char* pixel = pixels;
  for (int y = 0; y < height; ++y) {
    printf("%02d  ", y);
    for (int x = 0; x < width; ++x, ++pixel) {
      unsigned char p = ' ';
      if (*pixel >= 128) {
        p = '#';
      } else if (*pixel >= 32) {
        p = '*';
      } else if (*pixel > 0) {
        p = '.';
      }
      printf("%c", p);
    }
    printf("\n");
  }
  printf("\n");
}

static void RenderText(const FontAtlas* atlas, const char* text) {
  assert(text);

  const int glyph_width  = atlas->header.glyph_width;
  const int glyph_height = atlas->header.glyph_height;
  const int glyph_size   = glyph_width * glyph_height;

  for (int y = 0; y < glyph_height; ++y) {
    printf("%02d  ", y);

    for (size_t i = 0; i < strlen(text); ++i) {
      const char c            = text[i];
      const int  glyph_offset = (c - FontGlyphStart) * glyph_size;
      const int  row_offset   = (y * glyph_width);

      const unsigned char* pixel = atlas->pixels + glyph_offset + row_offset;

      for (int x = 0; x < glyph_width; ++x, ++pixel) {
        unsigned char p = ' ';
        if (*pixel >= 128) {
          p = '#';
        } else if (*pixel >= 32) {
          p = '*';
        } else if (*pixel > 0) {
          p = '.';
        }
        printf("%c", p);
      }
    }
    printf("\n");
  }
}

static bool WriteAtlas(const FontAtlas* atlas, const char* output_path) {
  assert(atlas);
  assert(output_path);

  bool success = true;

  FILE* out = NULL;

  const int num_pixels = atlas->header.num_glyphs * atlas->header.glyph_width *
                         atlas->header.glyph_height;

  out = fopen(output_path, "wb");
  if (out == NULL) {
    fprintf(stderr, "Failed opening output file: %s\n", output_path);
    success = false;
    goto cleanup;
  }
  if (fwrite(&atlas->header, sizeof(atlas->header), 1, out) != 1) {
    fprintf(stderr, "Failed writing atlas header\n");
    success = false;
    goto cleanup;
  }
  if (fwrite(atlas->pixels, num_pixels, 1, out) != 1) {
    fprintf(stderr, "Failed writing atlas\n");
    success = false;
    goto cleanup;
  }

cleanup:
  if (out != NULL) {
    fclose(out);
  }
  return success;
}

static int GetYOffset(FT_Face face, int font_size) {
  const int y_offset = font_size - face->glyph->bitmap_top;
  assert(y_offset >= 0);
  return y_offset;
}

static bool GetMaxGlyphDimensions(
    FT_Library ft, FT_Face face, int font_size, int* width, int* height) {
  assert(ft);
  assert(face);
  assert(width);
  assert(height);

  FT_Error error = 0;

  int max_width  = 0;
  int max_height = 0;

  for (unsigned char c = FontGlyphStart; c < FontGlyphEnd; ++c) {
    const FT_UInt glyph_index = FT_Get_Char_Index(face, c);
    assert(glyph_index > 0);

    error = FT_Load_Glyph(face, glyph_index, LoadFlags);
    assert(error == 0);

    error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
    assert(error == 0);

    const int y_offset = GetYOffset(face, font_size);

    const int xmax = (int)face->glyph->bitmap.width;
    const int ymax = (int)face->glyph->bitmap.rows + y_offset;

    if (xmax > max_width) {
      max_width = xmax;
    }

    if (ymax > max_height) {
      max_height = ymax;
    }
  }

  *width  = max_width;
  *height = max_height;

  return true;
}

static void GammaCorrect(unsigned char* pixels, int num_pixels) {
  assert(pixels);

  for (int i = 0; i < num_pixels; ++i) {
    pixels[i] =
        (unsigned char)(pow((double)pixels[i] / 255.0, 1.0 / 2.2) * 255.0);
  }
}

int main(int argc, const char** argv) {
  const int font_size = 26;

  bool success = true;

  if (argc < 3) {
    fprintf(stderr, "Usage: %s <font file> <output file>\n", argv[0]);
    return 0;
  }

  const char* font_path   = argv[1];
  const char* output_path = argv[2];

  FT_Library ft    = {0};
  FT_Face    face  = {0};
  FT_Error   error = 0;
  FontAtlas* atlas = 0;

  if (FT_Init_FreeType(&ft) != 0) {
    fprintf(stderr, "FT_Init_FreeType() failed\n");
    success = false;
    goto cleanup;
  }

  if (FT_New_Face(ft, font_path, 0, &face) != 0) {
    fprintf(stderr, "Failed loading font file: %s\n", font_path);
    success = false;
    goto cleanup;
  }

  if (FT_Set_Pixel_Sizes(face, 0, font_size) != 0) {
    fprintf(stderr, "Failed to set character size\n");
    success = false;
    goto cleanup;
  }

  // Find the largest glyph dimensions.
  int glyph_width, glyph_height;
  GetMaxGlyphDimensions(ft, face, font_size, &glyph_width, &glyph_height);

  const int glyph_size = glyph_width * glyph_height;
  const int atlas_size = glyph_size * FontAtlasNumGlyphs;

  printf("Glyph: %dx%d (%d bytes)\n", glyph_width, glyph_height, glyph_size);
  printf(
      "Atlas: %dx%d (%d bytes)\n", FontAtlasNumGlyphs * glyph_width,
      glyph_height, atlas_size);

  // -1 because Atlas already includes 1 pixel.
  atlas = calloc(1, sizeof(FontAtlas) + atlas_size - 1);
  if (!atlas) {
    success = false;
    goto cleanup;
  }
  atlas->header = (FontHeader){
      .glyph_width  = glyph_width,
      .glyph_height = glyph_height,
      .num_glyphs   = FontAtlasNumGlyphs,
  };

  // Render into atlas in glyph-major order so that glyphs can later be rendered
  // by scanning memory linearly.
  for (unsigned char c = FontGlyphStart; c < FontGlyphEnd; ++c) {
    const FT_UInt glyph_index = FT_Get_Char_Index(face, c);
    assert(glyph_index > 0);

    error = FT_Load_Glyph(face, glyph_index, LoadFlags);
    assert(error == 0);

    error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
    assert(error == 0);

    const int this_width  = (int)face->glyph->bitmap.width;
    const int this_height = (int)face->glyph->bitmap.rows;

    // For debugging.
    if ((c == 'A') || (c == 'B') || (c == 'C') || (c == 'e') || (c == '!')) {
      RenderGlyph(c, face->glyph->bitmap.buffer, this_width, this_height);
    }

    // Space gets no bitmap allocated, skip rendering.
    if (c == ' ') {
      continue;
    }

    // Render into atlas.
    const int glyph_offset = (c - FontGlyphStart) * glyph_size;
    assert(glyph_offset + glyph_size <= atlas_size);

    assert(this_width <= glyph_width);
    assert(this_height <= glyph_height);

    const int x_offset = (glyph_width - this_width) / 2;
    const int y_offset = GetYOffset(face, font_size);

    assert(y_offset >= 0);
    assert(x_offset >= 0);
    assert(y_offset + this_height <= glyph_height);
    assert(x_offset + this_width <= glyph_width);

    unsigned char* pAtlas =
        atlas->pixels + glyph_offset + (y_offset * glyph_width) + x_offset;
    const unsigned char* pixel = face->glyph->bitmap.buffer;

    for (int y = 0; y < this_height; ++y) {
      for (int x = 0; x < this_width; ++x, ++pixel, ++pAtlas) {
        *pAtlas = *pixel;
      }
      // Glyph may be narrower than the maximum width.
      if (glyph_width > this_width) {
        pAtlas += (glyph_width - this_width);
      }
    }
  }

  // FreeType renders glyphs in linear space. Gamma-correct the atlas so that
  // applications can directly render to screen.
  GammaCorrect(atlas->pixels, atlas_size);

  // Quick test.
  const char* test = "Good Stuff! \"g_g\", Baking Complete.";
  RenderText(atlas, test);

  if (!WriteAtlas(atlas, output_path)) {
    goto cleanup;
  }

cleanup:
  if (atlas) {
    free(atlas);
  }
  if (face) {
    FT_Done_Face(face);
  }
  if (ft) {
    FT_Done_FreeType(ft);
  }
  return success ? 0 : 1;
}