/* The font rasterizer takes a standard font and converts it into a bitmap
 * embeddable font. In other words, it takes a .ttf in and outputs a .c/.h pair.
 *
 * It can also print a PNG file showing how each glyph has been rendered.
*
 * Usage: rasterizer font_name glyph_width glyph_height
 * -> Generates a .png image with the font rasterized
 * -> Generates a .c file with the content of the font */

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdint.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#include "unicode_for_symbol.h"
#include "../../ion/src/external/lz4/lz4hc.h"


#define ENSURE(action, description...) { if (!(action)) { fprintf(stderr, "Error: "); fprintf(stderr, description); fprintf(stderr, "\n"); exit(-1);}}

// Pixel format is RGB888

typedef struct {
  unsigned char red;
  unsigned char green;
  unsigned char blue;
} pixel_t;

typedef struct {
  pixel_t * pixels;
  int width;
  int height;
} image_t;

#ifdef GENERATE_PNG
#include <png.h>
void writeImageToPNGFile(image_t * image, char * filename);
#endif

#define CHARACTER_RANGE_START 0x20
#define CHARACTER_RANGE_END 0x7E
#define CHARACTER_COUNT (CHARACTER_RANGE_END-CHARACTER_RANGE_START+1)
#define GLYPH_COUNT (CHARACTER_COUNT + NUMBER_OF_SYMBOLS)

#define GRID_WIDTH 19
#define GRID_HEIGHT 8

#if (GRID_WIDTH*GRID_HEIGHT < GLYPH_COUNT)
#error Grid too small. Consider increasing GRID_WIDTH or GRID_HEIGHT
#endif

void drawGlyphInImage(FT_Bitmap * glyphBitmap, image_t * image, int x, int y);
static void prettyPrintArray(FILE * stream, int maxWidth, int typeSize, void * array, int numberOfElements);

int main(int argc, char * argv[]) {
  FT_Library library;
  FT_Face face;
  image_t bitmap_image;

  int expectedNumberOfArguments = 6;
#ifdef GENERATE_PNG
  expectedNumberOfArguments = 7;
#endif
  if (argc != expectedNumberOfArguments) {
#ifdef GENERATE_PNG
    fprintf(stderr, "Usage: %s font_file glyph_width glyph_height font_name output_cpp output_png\n", argv[0]);
#else
    fprintf(stderr, "Usage: %s font_file glyph_width glyph_height font_name output_cpp\n", argv[0]);
#endif
    fprintf(stderr, "  font_file: Path of the font file to load\n");
    fprintf(stderr, "  glyph_width: Width of bitmap glyphs, in pixels\n");
    fprintf(stderr, "  glyph_height: Height of bitmap glyphs, in pixels\n");
    fprintf(stderr, "  font_name: name of the loaded font\n");
    fprintf(stderr, "  output_cpp: Name of the generated C source file\n");
#ifdef GENERATE_PNG
    fprintf(stderr, "  output_png: Name of the generated PNG file\n");
#endif
    exit(1);
  }

  char * font_file = argv[1];
  int requested_glyph_width = atoi(argv[2]);
  int requested_glyph_height = atoi(argv[3]);
  char * font_name = argv[4];
  char * output_cpp = argv[5];
#ifdef GENERATE_PNG
  char * output_png = argv[6];
#endif

  ENSURE(!FT_Init_FreeType(&library), "Initializing library");

  // 0 means we're picking the first face in the provided file
  ENSURE(!FT_New_Face(library, font_file, 0, &face), "Loading font file %s", font_file);

  ENSURE(!FT_Set_Pixel_Sizes(face, requested_glyph_width, requested_glyph_height), "Setting face pixel size to %dx%d", requested_glyph_width, requested_glyph_height);

  /* Glyph metrics are complicated. Here are some useful links:
   * https://www.freetype.org/freetype2/docs/glyphs/metrics.png
   * https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html
   * https://www.freetype.org/freetype2/docs/reference/ft2-basic_types.html#FT_Bitmap
   */
  int maxWidth = 0;
  int maxAboveBaseline = 0;
  int maxBelowBaseline = 0;
  for (unsigned char character = CHARACTER_RANGE_START; character <= CHARACTER_RANGE_END; character++) {
    ENSURE(!FT_Load_Char(face, character, FT_LOAD_RENDER), "Loading character 0x%02x", character);
    int aboveBaseline = face->glyph->bitmap_top;
    int belowBaseline = face->glyph->bitmap.rows - face->glyph->bitmap_top;
    int width = face->glyph->bitmap_left + face->glyph->bitmap.width;
    if (width > maxWidth) {
      maxWidth = width;
    }
    if (aboveBaseline > maxAboveBaseline) {
      maxAboveBaseline = aboveBaseline;
    }
    if (belowBaseline > maxBelowBaseline) {
      maxBelowBaseline = belowBaseline;
    }
  }
  for (int charIndex = 0; charIndex < NUMBER_OF_SYMBOLS; charIndex++) {
    wchar_t wideChar = codePointForSymbol[charIndex];
    ENSURE(!FT_Load_Char(face, wideChar, FT_LOAD_RENDER), "Loading character 0x%02x", wideChar);
    int aboveBaseline = face->glyph->bitmap_top;
    int belowBaseline = face->glyph->bitmap.rows - face->glyph->bitmap_top;
    int width = face->glyph->bitmap_left + face->glyph->bitmap.width;
    if (width > maxWidth) {
      maxWidth = width;
    }
    if (aboveBaseline > maxAboveBaseline) {
      maxAboveBaseline = aboveBaseline;
    }
    if (belowBaseline > maxBelowBaseline) {
      maxBelowBaseline = belowBaseline;
    }
  }

  int glyph_width = maxWidth-1;
  int glyph_height = maxAboveBaseline+maxBelowBaseline;
  //printf("Actual glyph size = %dx%d\n", glyph_width, glyph_height);

  int grid_size = 1;

  bitmap_image.width = GRID_WIDTH*glyph_width+(GRID_WIDTH-1)*grid_size;
  bitmap_image.height = GRID_HEIGHT*glyph_height+(GRID_HEIGHT-1)*grid_size;
  bitmap_image.pixels = malloc(sizeof(pixel_t)*bitmap_image.width*bitmap_image.height);
  ENSURE(bitmap_image.pixels != NULL, "Allocating bitmap image of size %dx%d at %ld bytes per pixel", bitmap_image.width, bitmap_image.height, sizeof(pixel_t));

  // Draw the grid and the background
  for (int i = 0; i<bitmap_image.width;i++) {
    for (int j = 0; j<bitmap_image.height;j++) {
      pixel_t pixel = {.red = 0xFF, .green = 0xFF, .blue = 0xFF};
      if (i%(glyph_width+grid_size) >= glyph_width || j%(glyph_height+grid_size) >= glyph_height) {
        pixel = (pixel_t){.red = 0xFF, .green = 0, .blue = 0};
      }
      *(bitmap_image.pixels + j*bitmap_image.width + i) = pixel;
    }
  }

  // We're doing the ASCII table, so characters from 0 to 255 inclusive
  for (unsigned char character = CHARACTER_RANGE_START; character <= CHARACTER_RANGE_END; character++) {
    int x = (character-CHARACTER_RANGE_START)%(GRID_WIDTH);
    int y = (character-CHARACTER_RANGE_START)/(GRID_WIDTH);
    // FT_LOAD_RENDER: Render the glyph upon load
    ENSURE(!FT_Load_Char(face, character, FT_LOAD_RENDER), "Loading character 0x%02x", character);
    //printf("Advances = %dx%d\n", face->glyph->bitmap_left, face->glyph->bitmap_top);
    drawGlyphInImage(&face->glyph->bitmap,
        &bitmap_image,
        x*(glyph_width+grid_size) + face->glyph->bitmap_left,
        y*(glyph_height+grid_size) + maxAboveBaseline - face->glyph->bitmap_top
    );
  }
  // We are now using unicode to access non-ASCII characters
  for (int charIndex = 0; charIndex < NUMBER_OF_SYMBOLS; charIndex++) {
    wchar_t wideChar = codePointForSymbol[charIndex];
    int x = (charIndex+1+CHARACTER_RANGE_END-CHARACTER_RANGE_START)%(GRID_WIDTH);
    int y = (charIndex+1+CHARACTER_RANGE_END-CHARACTER_RANGE_START)/(GRID_WIDTH);
    // FT_LOAD_RENDER: Render the glyph upon load
    ENSURE(!FT_Load_Char(face, wideChar, FT_LOAD_RENDER), "Loading character 0x%02x", wideChar);
    //printf("Advances = %dx%d\n", face->glyph->bitmap_left, face->glyph->bitmap_top);
    drawGlyphInImage(&face->glyph->bitmap,
        &bitmap_image,
        x*(glyph_width+grid_size) + face->glyph->bitmap_left,
        y*(glyph_height+grid_size) + maxAboveBaseline - face->glyph->bitmap_top
    );
  }

#if GENERATE_PNG
  writeImageToPNGFile(&bitmap_image, output_png);
#endif

  FILE * sourceFile = fopen(output_cpp, "w");

  fprintf(sourceFile, "/* This file is auto-generated by the rasterizer */\n\n");
  fprintf(sourceFile, "#include <kandinsky/font.h>\n\n");
  fprintf(sourceFile, "static constexpr KDCoordinate glyphWidth = %d;\n\n", glyph_width);
  fprintf(sourceFile, "static constexpr KDCoordinate glyphHeight = %d;\n\n", glyph_height);

  int greyscaleBitsPerPixel = 4;

  int sizeOfUncompressedGlyphBuffer = glyph_width * glyph_height * greyscaleBitsPerPixel/8;
  uint8_t * uncompressedGlyphBuffer = (uint8_t *)malloc(sizeOfUncompressedGlyphBuffer);

  uint16_t glyphDataOffset[GLYPH_COUNT+1];
  int maxGlyphDataSize = GLYPH_COUNT* sizeOfUncompressedGlyphBuffer;
  uint8_t * glyphData = (uint8_t *)malloc(maxGlyphDataSize);
  uint16_t lastOffset = 0;

  for (int character = 0; character < GLYPH_COUNT; character++) {
    int characterX = (character%GRID_WIDTH * (glyph_width+grid_size));
    int characterY = (character/GRID_WIDTH * (glyph_height+grid_size));
    uint8_t accumulator = 0;
    int uncompressedGlyphBufferIndex = 0;
    int numberOfValuesAccumulated = 0;
    for (int y = 0; y < glyph_height; y++) {
      for (int x = 0; x < glyph_width; x++) {
        pixel_t * pixel = (bitmap_image.pixels + (y+characterY)*bitmap_image.width + (x+characterX));

        uint8_t greyscaleValue = (0xFF - pixel->green) >> (8 - greyscaleBitsPerPixel);
        accumulator = (accumulator << greyscaleBitsPerPixel) | greyscaleValue;
        if (numberOfValuesAccumulated++ == (8/greyscaleBitsPerPixel)-1) {
          uncompressedGlyphBuffer[uncompressedGlyphBufferIndex++] = accumulator;
          accumulator = 0;
          numberOfValuesAccumulated = 0;
        }
      }
    }
    ENSURE(accumulator == 0, "Discarded accumulator data");
    ENSURE(numberOfValuesAccumulated == 0, "Discarded accumulator data");
    ENSURE(uncompressedGlyphBufferIndex == sizeOfUncompressedGlyphBuffer, "Error filling uncompressed buffer, only %d out of %d", uncompressedGlyphBufferIndex, sizeOfUncompressedGlyphBuffer);

    int sizeOfCompressedGlyphBuffer = LZ4_compress_HC(
      (const char *)uncompressedGlyphBuffer,
      (char *)(glyphData + lastOffset),
      sizeOfUncompressedGlyphBuffer,
      maxGlyphDataSize - lastOffset,
      LZ4HC_CLEVEL_MAX
    );

    ENSURE(sizeOfCompressedGlyphBuffer > 0, "Could not compress glyph %d", character);

    glyphDataOffset[character] = lastOffset;
    lastOffset += sizeOfCompressedGlyphBuffer;
  }
  glyphDataOffset[GLYPH_COUNT] = lastOffset;

  fprintf(sourceFile, "static constexpr uint16_t glyphDataOffset[%d] = {", GLYPH_COUNT+1);
  prettyPrintArray(sourceFile, 80, 2, glyphDataOffset, GLYPH_COUNT+1);
  fprintf(sourceFile, "};\n\n");

  size_t finalDataSize = lastOffset;
  size_t initialDataSize = GLYPH_COUNT * glyph_width * glyph_height;

  fprintf(sourceFile, "/* Rasterized  = %5zu bytes (%d glyphs x %d pixels)\n", initialDataSize, GLYPH_COUNT, glyph_width*glyph_height);
  fprintf(sourceFile, " * Downsampled = %5lu bytes (1/%d of rasterized)\n", initialDataSize*greyscaleBitsPerPixel/8, 8/greyscaleBitsPerPixel);
  fprintf(sourceFile, " * Compressed  = %5zu bytes (%.2f%% of rasterized) */\n", finalDataSize, 100.0*finalDataSize/initialDataSize);

  fprintf(sourceFile, "static constexpr uint8_t glyphData[%d] = {", lastOffset);
  prettyPrintArray(sourceFile, 80, 1, glyphData, lastOffset);
  fprintf(sourceFile, "};\n\n");

  free(glyphData);
  free(uncompressedGlyphBuffer);

  fprintf(sourceFile, "const KDFont KDFont::private%s(glyphWidth, glyphHeight, glyphDataOffset, glyphData);\n", font_name);

  fclose(sourceFile);

  free(bitmap_image.pixels);

  return 0;
}

static void prettyPrintArray(FILE * stream, int maxWidth, int typeSize, void * array, int numberOfElements) {
  int lineWidth = 999;
  for (int i=0; i<numberOfElements; i++) {
    int itemWidth = 1 + 2*typeSize + 2 + 1; // space + 0x + data + comma
    if (lineWidth + itemWidth > maxWidth) {
      fprintf(stream, "\n ");
      lineWidth = 1;
    }
    if (typeSize == 1) {
      fprintf(stream, " 0x%02x", ((uint8_t *)array)[i]);
    } else if (typeSize == 2) {
      fprintf(stream, " 0x%04x", ((uint16_t *)array)[i]);
    }
    if (i == numberOfElements-1) {
      fprintf(stream, "\n");
    } else {
      fprintf(stream, ",");
    }
    lineWidth += itemWidth;
  }
}

void drawGlyphInImage(FT_Bitmap * glyphBitmap, image_t * image, int x, int y) {
  // printf("Drawing glyph. Size is %dx%d, pitch is %d\n", glyphBitmap->width, glyphBitmap->rows, glyphBitmap->pitch);
  ENSURE(glyphBitmap->pixel_mode == FT_PIXEL_MODE_GRAY, "Checking glyph is in FT_PIXEL_MODE_GRAY");
  for (int j=0;j<glyphBitmap->rows;j++) {
    for (int i=0;i<glyphBitmap->width;i++) {
      uint8_t glyphPixel = *(glyphBitmap->buffer + j*glyphBitmap->pitch + i);
      pixel_t * currentPixelPointer = (image->pixels + (y+j)*image->width + (x+i));
      *currentPixelPointer = (pixel_t){.red = (0xFF-glyphPixel), .green = (0xFF-glyphPixel), .blue = (0xFF-glyphPixel)}; // Alpha blending
    }
  }
}

#if GENERATE_PNG

void writeImageToPNGFile(image_t * image, char * filename) {
  FILE * file = fopen(filename, "wb"); // Write in binary mode
  ENSURE(file != NULL, "Opening file %s", filename);

  png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  ENSURE(png != NULL, "Allocating PNG write structure");

  png_infop info = png_create_info_struct(png);
  ENSURE(info != NULL, "Allocating info structure");

  png_init_io(png, file);

  png_set_IHDR(png, info,
      image->width, image->height,
      8, // Number of bits per channel
      PNG_COLOR_TYPE_RGB,
      PNG_INTERLACE_NONE,
      PNG_COMPRESSION_TYPE_DEFAULT,
      PNG_FILTER_TYPE_DEFAULT);

  png_write_info(png, info);

  for (int j=0;j<image->height;j++) {
    png_write_row(png, (png_bytep)(image->pixels+j*image->width));
  }

  png_write_end(png, NULL);

  png_free_data(png, info, PNG_FREE_ALL, -1); // -1 = all items
  png_destroy_write_struct(&png, (png_infopp)NULL);
  fclose(file);
}
#endif
