#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_TRUETYPE_DRIVER_H
#include FT_MODULE_H
#include <getopt.h>

#define OPCODE_REPEAT 253
#define OPCODE_EOL  254
#define OPCODE_EOC  255

#define MAX_RLE   80  /* Maximum number of repeats */


/****************************************************************************
* BYTEPOINTER FUNCTIONS
****************************************************************************/
struct histogram {
  int position;
  int count;
};
struct bytepointer {
  unsigned char *p;
  unsigned char *start;
  unsigned char *end;
  struct histogram histogram[256];
};

struct bitoutput {
  FILE *fp;
  int col;
  int byte_count;
  uint8_t byte;
  uint8_t bit;
};

void init_bytepointer (struct bytepointer *bp, unsigned char *buffer, int n) {
  int i;
  bp->p = buffer;
  bp->start = buffer;
  bp->end = buffer + n;
  for (i=0; i<256; i++) {
    bp->histogram[i].position = i;
    bp->histogram[i].count = 0;
  }
}

void save_byte (struct bytepointer *bp, unsigned char byte) {
  if (bp->p == bp->end) {
    printf ("OUT OF BITPOINTER MEMORY.\n");
    exit (2);
  }

  // If byte is more than MAX_RLE, break into MAX_RLE, 0, remainder
  while (byte < OPCODE_REPEAT && byte > MAX_RLE) {
    * (bp->p++) = MAX_RLE;
    * (bp->p++) = 0;
    byte -= MAX_RLE;
  }
  * (bp->p++) = byte;
  bp->histogram[byte].count++;
}

int histcmp (const void *p, const void *q) {
  struct histogram *p0 = (struct histogram *) p;
  struct histogram *q0 = (struct histogram *) q;
  return q0->count - p0->count;
}

void save_bits (struct bitoutput *bits, int bit) {
  if (bit) bits->byte |= bits->bit;
  bits->bit >>= 1;
  if (!bits->bit) {
    if (bits->col == 12) { fprintf (bits->fp, ",\n\t"); bits->col = 0; }
    else if (bits->col != 0) fprintf (bits->fp, ", ");
    fprintf (bits->fp, "0x%02X", bits->byte);
    bits->col++;
    bits->byte_count++;
    bits->bit = 0x80;
    bits->byte = 0;
  }
}

void output_bitstream (struct bytepointer *bp, int max_width, int scaled) {
  unsigned char *p;
  struct bitoutput bits;
  int col, i, v, lookup[256], offset, offsets[96];

  qsort (bp->histogram, 256, sizeof (struct histogram), histcmp);
  for (i=0; i<256; i++) {
    lookup[bp->histogram[i].position] = i;
  }

  bits.fp = fopen ("font.h", "w");
  bits.byte = 0;
  bits.bit = 0x80;
  bits.col = 0;
  bits.byte_count = 0;

  offset = 0;
  offsets[0] = 0;

  fprintf (bits.fp,
           "/********************************************************************\n"
           "* Font File format:\n"
           "*   2 bits: =0, opcode lut index 0\n"
           "*           =1, opcode lut index is in next 3 bits + 1\n"
           "*           =2, opcode lut index is in next 4 bits + 9\n"
           "*           =3, opcode lut index is in next 6 bits + 25\n"
           "********************************************************************/\n");
  fprintf (bits.fp, "unsigned char mfont[] = {\n\t");
  for (p = bp->start; p <= bp->p; p++) {
    v = lookup[*p];
    if (v == 0) {
      save_bits (&bits, 0);
      save_bits (&bits, 0);
    } else if (v < 9) {
      v = v-1;
      save_bits (&bits, 0);
      save_bits (&bits, 1);
      save_bits (&bits, v & 4);
      save_bits (&bits, v & 2);
      save_bits (&bits, v & 1);
    } else if (v < 25) {
      v = v-9;
      save_bits (&bits, 1);
      save_bits (&bits, 0);
      save_bits (&bits, v & 8);
      save_bits (&bits, v & 4);
      save_bits (&bits, v & 2);
      save_bits (&bits, v & 1);
    } else {
      v = v-25;
      save_bits (&bits, 1);
      save_bits (&bits, 1);
      save_bits (&bits, v & 32);
      save_bits (&bits, v & 16);
      save_bits (&bits, v & 8);
      save_bits (&bits, v & 4);
      save_bits (&bits, v & 2);
      save_bits (&bits, v & 1);
    }
    if (*p == OPCODE_EOC) {
      while (bits.bit != 0x80) save_bits (&bits, 0);
      offsets[++offset] = bits.byte_count;
    }

  }
  fprintf (bits.fp, "};\n");

  fprintf (bits.fp, "unsigned char flut[] = {\n\t");
  for (i=0, col=0; bp->histogram[i].count; i++) {
    if (col == 14) { fprintf (bits.fp, ",\n\t"); col = 0; }
    else if (col != 0) fprintf (bits.fp, ", ");
    fprintf (bits.fp, "%3d", bp->histogram[i].position);
    col++;
    bits.byte_count++;
  }
  fprintf (bits.fp, "};\n");

  fprintf (bits.fp, "unsigned int foffs[] = {\n\t");
  for (i=0, col=0; i<offset; i++) {
    if (col == 10) { fprintf (bits.fp, ",\n\t"); col = 0; }
    else if (col != 0) fprintf (bits.fp, ", ");
    fprintf (bits.fp, "%5d", offsets[i]);
    bits.byte_count += 2;
    col++;
  }
  fprintf (bits.fp, "};\n\n");


  if (scaled) max_width += max_width;
  fprintf (bits.fp, "#define FONT_MAXWIDTH %d\n", max_width);
  fprintf (bits.fp, "#define FONT_DOUBLED  %d\n\n", scaled);
  fprintf (bits.fp, "#define OPCODE_REPEAT	%d\n", OPCODE_REPEAT);
  fprintf (bits.fp, "#define OPCODE_EOL	%d\n", OPCODE_EOL);
  fprintf (bits.fp, "#define OPCODE_EOC	%d\n", OPCODE_EOC);

  printf ("%d bytes.\n", bits.byte_count);
}



int main (int argc, char *argv[]) {
  int error;
  FT_Library library;
  FT_Face face;
  FT_GlyphSlot slot;
  FT_Glyph glyph;
  FT_Bitmap *bitmap;
  int advance, byte, c, x, y, y0, v, offset;
  uint8_t bit;
  int max_decender = 0;
  int max_width = 0;

  struct bytepointer bp;
  unsigned char buffer[48*1024];
  int color, scaled, verbose;
  char *font = NULL;
  int ch, width, height;

  struct option longopts[] = {
    { "font", required_argument,  NULL, 'f' },
    { "width",  required_argument,  NULL,   'w' },
    { "double", no_argument,    &scaled, 1 },
    { "verbose",  no_argument,    &verbose, 1 },
    { NULL,   0,      NULL, 0}
  };

  width = 80;
  while ( (ch = getopt_long (argc, argv, "f:h:", longopts, NULL)) != -1) {
    switch (ch) {
    case 'f':
      font = optarg;
      break;
    case 'w':
      width = atoi (optarg);
      break;
    case 0:
      break;
    default:
      fprintf (stderr, "mkfont: --font FONTNAME [--width HEIGHT]\n");
      exit (2);
    }
  }

  if (!font) {
    fprintf (stderr, "No font specified. Use --font option\n");
    exit (2);
  }

  /* Adjust width to account for 0 base */
  if (scaled) width /= 2;
  width--;
  height = width * 6 / 10;

  init_bytepointer (&bp, buffer, sizeof (buffer));

  if ( (error = FT_Init_FreeType (&library))) {
    fprintf (stderr, "Error initializing FreeType");
    exit (2);
  }

  FT_UInt version = TT_INTERPRETER_VERSION_35;
  FT_Property_Set (library, "truetype", "interpreter-version",
                   &version);

  if ( (error = FT_New_Face (library, font, 0, &face))) {
    fprintf (stderr, "Error loading Face");
    exit (2);
  }

  // Height, width reversed below because characters are rotated
  if ( (error = FT_Set_Pixel_Sizes (face, height, width))) {
    fprintf (stderr, "Error setting pixel size");
    exit (2);
  }

  /* Find maximum decender and width */
  for (c=32; c < 127; c++) {
    if ( (error = FT_Load_Char (face, c, FT_LOAD_TARGET_MONO))) {
      fprintf (stderr, "Error loading char");
      exit (2);
    }
    slot = face->glyph;
    if ( (error = FT_Render_Glyph (slot, FT_RENDER_MODE_MONO))) {
      fprintf (stderr, "Error loading char");
      exit (2);
    }
    if ( (error = FT_Get_Glyph (slot, &glyph))) {
      fprintf (stderr, "Error loading char");
      exit (2);
    }
    bitmap = &slot->bitmap;
    if ( (int) (slot->bitmap_top - bitmap->rows) < max_decender) {
      max_decender = slot->bitmap_top - bitmap->rows;
    }
    if (bitmap->rows > max_width)
      max_width = bitmap->rows;
  }
  max_width++;

  /* Generate glyphs and encode */
  for (c=32; c < 127; c++) {
    if ( (error = FT_Load_Char (face, c, FT_LOAD_TARGET_MONO))) {
      fprintf (stderr, "Error loading char");
      exit (2);
    }
    slot = face->glyph;
    if ( (error = FT_Render_Glyph (slot, FT_RENDER_MODE_MONO))) {
      fprintf (stderr, "Error loading char");
      exit (2);
    }
    if ( (error = FT_Get_Glyph (slot, &glyph))) {
      fprintf (stderr, "Error loading char");
      exit (2);
    }

    bitmap = &slot->bitmap;
    advance = slot->bitmap_left;
    if (advance < 0) advance = 0;

    color = 0;
    for (; advance > 0; advance--) {
      save_byte (&bp, OPCODE_EOL);
      if (verbose) printf ("\n");
    }
    for (x=0; x < bitmap->width; x++) {
      int repeat = 0;

      // Check for repeating line
      if (x > 0) {
        int v0;
        for (y = bitmap->rows-1; y >= 0; y--) {
          byte = (x-1) /8;
          bit = 0x80 >> ( (x-1) &7);
          v0 = (bitmap->buffer[y*bitmap->pitch + byte]&bit) ? 1: 0;
          byte = x/8;
          bit = 0x80 >> (x&7);
          v = (bitmap->buffer[y*bitmap->pitch + byte]&bit) ? 1: 0;
          if (v0 != v)
            break;
        }
        if (y < 0) repeat = 1;
      }
      // Find end of spaces
      for (y0=0; y0<bitmap->rows; y0++) {
        byte = x/8;
        bit = 0x80 >> (x&7);
        v = (bitmap->buffer[y0*bitmap->pitch + byte]&bit) ? 1: 0;
        if (v) break;
      }
      offset = -max_decender+ (slot->bitmap_top - bitmap->rows);
      if (y0 == bitmap->rows) offset = 0;
      if (verbose) {
        for (y=0; y<offset; y++) {
          printf (" ");
        }
      }
      for (y=bitmap->rows-1; y>=y0; y--) {
        byte = x/8;
        bit = 0x80 >> (x&7);
        v = (bitmap->buffer[y*bitmap->pitch + byte]&bit) ? 1: 0;
        if (verbose) {
          if (v)
            printf (repeat ? "*" : "#");
          else
            printf (" ");
        }

        if (v==color) offset++;
        else {
          if (!repeat) save_byte (&bp, offset);
          color ^= 1;
          offset = 1;
        }
      }
      if (repeat) {
        save_byte (&bp, OPCODE_REPEAT);
      } else {
        save_byte (&bp, offset);
        save_byte (&bp, OPCODE_EOL);
      }
      color = 0;
      if (verbose) printf ("\n");
    }
    advance = (slot->advance.x >> 6) - bitmap->width -
              ( (slot->bitmap_left > 0) ? slot->bitmap_left : 0);
    if (advance < 0) advance = 0;
    for (; advance > 0; advance--) {
      save_byte (&bp, OPCODE_EOL);
      if (verbose) printf ("\n");
    }
    save_byte (&bp, OPCODE_EOC);
    FT_Done_Glyph (glyph);
  }
  output_bitstream (&bp, max_width, scaled);
}