#include "font.h"

#include "ft2build.h"
#include "freetype/freetype.h"
#include "freetype/ftglyph.h"

#include "lephisto.h"
#include "log.h"
#include "pack.h"

#define FONT_DEF  "../dat/font.ttf"

// == Font render routines.================================
// Use a display list to store ASCII chars rendered with 
// freefont. There are several drawing methods depending
// on whether you want to print it all, to a max width,
// print centered or print a block of text.
//
// There are hardcoded size limits. 256 characters for all
// routines exept gl_printText which has a 1024 limit.
//
// TODO: Check if length is too long.
// ========================================================

// Default font.
glFont gl_defFont;
glFont gl_smallFont;

static void glFontMakeDList(FT_Face face, char ch,
      GLuint list_base, GLuint* tex_base, int* width_base);

static int pot(int n);

// Get the closest power of two.
static int pot(int n) {
  int i = 1;
  while(i < n)
    i<<=1;
  return i;
}

// Print text on screen! YES!!!! Just like printf! But different!
// Defaults ft_font to gl_defFont if NULL.
void gl_print(const glFont* ft_font, const double x, const double y,
      const glColour* c, const char* fmt, ...) {
  //float h = ft_font->h / .63; // Slightly increases font size.
  char txt[256];
  va_list ap;

  if(ft_font == NULL) ft_font = &gl_defFont;

  if(fmt == NULL) return;
  else {
    // convert the symbols to text.
    va_start(ap, fmt);
    vsprintf(txt, fmt, ap);
    va_end(ap);
  }

  glEnable(GL_TEXTURE_2D);

  glListBase(ft_font->list_base);

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix(); // Translation matrix.
  glTranslated(x - (double)gl_screen.w/2., y - (double)gl_screen.h/2., 0);

  if(c == NULL) glColor4d(1., 1., 1., 1.);
  else COLOUR(*c);
  glCallLists(strlen(txt), GL_UNSIGNED_BYTE, &txt);

  glPopMatrix(); // Translation matrix.
  glDisable(GL_TEXTURE_2D);
}

// Acts just like gl_print, but prints to a max length of max.
// Return the amount of characters we had to suppress.
int gl_printMax(const glFont* ft_font, const int max, const double x, const double y,
      const glColour* c, const char* fmt, ...) {
  //float h = ft_font->h / .63; // Slightly increases font size.
  char txt[256];
  va_list ap;
  int i, n, len, ret;

  if(ft_font == NULL) ft_font = &gl_defFont;

  if(fmt == NULL) return -1;
  else {
    // convert the symbols to text.
    va_start(ap, fmt);
    vsprintf(txt, fmt, ap);
    va_end(ap);
  }
  
  // Limit the size.
  len = (int)strlen(txt);
  for(n = 0, i = 0; i < len; i++) {
    n += ft_font->w[(int)txt[i]];
    if(n > max) {
      ret = len - i; // Difference.
      txt[i] = '\0';
      break;
    }
  }

  // Display the text.
  glEnable(GL_TEXTURE_2D);

  glListBase(ft_font->list_base);

  glMatrixMode(GL_MODELVIEW); // Projection gets full fast using modelview.
  glPushMatrix(); // Translation matrix.
  glTranslated(x - (double)gl_screen.w/2., y - (double)gl_screen.h/2., 0);

  if(c == NULL) glColor4d(1., 1., 1., 1.);
  else COLOUR(*c);
  glCallLists(i, GL_UNSIGNED_BYTE, &txt);

  glPopMatrix(); // Translation matrix.
  glDisable(GL_TEXTURE_2D);

  return ret;
}

// Acts just like gl_printMax, but centers the text in the width.
int gl_printMid(const glFont* ft_font, const int width, double x, const double y,
      const glColour* c, const char* fmt, ...) {
  //float h = ft_font->h / .63; // Slightly increases font size.
  char txt[256];
  va_list ap;
  int i, n, len, ret;
  
  ret = 0; // Default return value.

  if(ft_font == NULL) ft_font = &gl_defFont;

  if(fmt == NULL) return -1;
  else {
    // convert the symbols to text.
    va_start(ap, fmt);
    vsprintf(txt, fmt, ap);
    va_end(ap);
  }
  
  // Limit the size.
  len = (int)strlen(txt);
  for(n = 0, i = 0; i < len; i++) {
    n += ft_font->w[(int)txt[i]];
    if(n > width) {
      ret = len - i; // Difference.
      n -= ft_font->w[(int)txt[i]]; // Actual size.
      txt[i] = '\0';
      break;
    }
  }

  x += (double)(width-n)/2.;

  // Display the text.
  glEnable(GL_TEXTURE_2D);

  glListBase(ft_font->list_base);

  glMatrixMode(GL_MODELVIEW); // Projection gets full fast using modelview.
  glPushMatrix(); // Translation matrix.
  glTranslated(x - (double)gl_screen.w/2., y - (double)gl_screen.h/2., 0);

  if(c == NULL) glColor4d(1., 1., 1., 1.);
  else COLOUR(*c);
  glCallLists(i, GL_UNSIGNED_BYTE, &txt);

  glPopMatrix(); // Translation matrix.
  glDisable(GL_TEXTURE_2D);

  return ret;
}

// Print text with line breaks included to a max width and height precet.
int gl_printText(const glFont* ft_font, const int width, const int height,
      double bx, double by, glColour* c, const char* fmt, ...) {

  //float h = ft_font->h / .63; // Slightly increase font size.
  char txt[1024];
  char buf[128];
  va_list ap;
  int p, i, j, n, len, ret, lastspace;
  double x, y;
  
  ret = 0; // Default return value.

  if(ft_font == NULL) ft_font = &gl_defFont;

  if(fmt == NULL) return -1;
  else {
    // Convert the symbols to text.
    va_start(ap, fmt);
    vsprintf(txt, fmt, ap);
    va_end(ap);
  }
  bx -= (double)gl_screen.w/2.;
  by -= (double)gl_screen.h/2.;
  x = bx;
  y = by + height - (double)ft_font->h; // y is top left corner.

  // Prepare opengl.
  glEnable(GL_TEXTURE_2D);
  glListBase(ft_font->list_base);
  if(c == NULL) glColor4d(1., 1., 1., 1.);
  else COLOUR(*c);

  len = (int)strlen(txt);
  // Limit size per line.
  lastspace = 0; // Last ' ' or \n int text.
  n = 0; // Current width.
  i = 0; // Current position.
  p = -1; // Where we last drew up to.
  while(i < len+1) {
    if(by - y > (double)height) return len-lastspace; // Past height.

    n += ft_font->w[(int)txt[i]];

    if((txt[i] == ' ') || (txt[i] == '\n') || (txt[i] == '\0')) lastspace = i;

    if((n > width) || (txt[i] == '\n') || (txt[i] == '\0')) {
      // Time to draw the line.
      for(j = 0; j < (lastspace-p-1); j++)
        buf[j] = txt[p+j+1];
      // No need for null termination.

      glMatrixMode(GL_MODELVIEW); // using modelview, projection gets full fast.
      glPushMatrix(); // Translation matrix.
      glTranslated(x, y, 0);

      glCallLists(lastspace-p-1, GL_UNSIGNED_BYTE, &buf); // This is what we are displaying.

      glPopMatrix(); // Translation matrix.

      p = lastspace;
      n = 0;
      i = lastspace;
      y -= 1.5*(double)ft_font->h; // Move position down.
    }
    i++;
  }
  glDisable(GL_TEXTURE_2D);

  return ret;
}

// Get the width of the text about to be printed.
int gl_printWidth(const glFont* ft_font, const char* fmt, ...) {
  int i, n;
  char txt[256]; // Holds the string.
  va_list ap;

  if(ft_font == NULL) ft_font = &gl_defFont;

  if(fmt == NULL) return 0;
  else {
    // Convert the symbols to text.
    va_start(ap, fmt);
    vsprintf(txt, fmt, ap);
    va_end(ap);
  }

  for(n = 0, i = 0; i < (int)strlen(txt); i++)
    n += ft_font->w[(int)txt[i]];

  return n;
}

// ================
// FONT!
// ================
static void glFontMakeDList(FT_Face face, char ch, GLuint list_base, GLuint* tex_base, int* width_base) {
  FT_Glyph glyph;
  FT_Bitmap bitmap;
  GLubyte* expanded_data;
  int w, h;
  int i, j;

  if(FT_Load_Glyph(face, FT_Get_Char_Index(face, ch), FT_LOAD_FORCE_AUTOHINT))
    WARN("FT_Load_Glyph failed");

  if(FT_Get_Glyph(face->glyph, &glyph))
    WARN("FT_Ge_Glyph failed");

  // Convert your glyph to a bitmap.
  FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
  FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;

  bitmap = bitmap_glyph->bitmap; // To simplify.

  // Need the POT wrapping for GL.
  w = pot(bitmap.width);
  h = pot(bitmap.rows);

  // Memory for textured data.
  // Bitmap is useing two channels, one for luminosity and one for alpha.
  expanded_data = (GLubyte*)malloc(sizeof(GLubyte)*2*w*h);
  for(j = 0; j < h; j++) {
    for(i = 0; i < w; i++) {
      expanded_data[2*(i+j*w)] = expanded_data[2*(i+j*w)+1] =
          (i >= bitmap.width || j >= bitmap.rows) ? 0 : bitmap.buffer[i + bitmap.width*j];
    }
  }
  // Create the GL texture.
  glBindTexture(GL_TEXTURE_2D, tex_base[(int)ch]);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data);

  free(expanded_data); // No need for this now.

  // Create the display lists.
  glNewList(list_base+ch, GL_COMPILE);

  // Corrects a spacing flaw between letters and
  // downwards correction for letters like g or y.
  glPushMatrix();
  glTranslated(bitmap_glyph->left, bitmap_glyph->top-bitmap.rows, 0);

  // Take the opengl POT wrapping into account.
  double x = (double)bitmap.width/(double)w;
  double y = (double)bitmap.rows/(double)h;

  // Give the width a value.
  width_base[(int)ch] = bitmap.width;

  // Draw the texture mapped quad.
  glBindTexture(GL_TEXTURE_2D, tex_base[(int)ch]);
  glBegin(GL_QUADS);
    glTexCoord2d(0, 0);
      glVertex2d(0, bitmap.rows);
    glTexCoord2d(x, 0);
      glVertex2d(bitmap.width, bitmap.rows);
    glTexCoord2d(x, y);
      glVertex2d(bitmap.width, 0);
    glTexCoord2d(0, y);
      glVertex2d(0, 0);
  glEnd();

  glPopMatrix();
  glTranslated(face->glyph->advance.x >> 6, 0,0);

  // End of the display list.
  glEndList();

  FT_Done_Glyph(glyph);
}

void gl_fontInit(glFont* font, const char* fname, const unsigned int h) {
  if(font == NULL) font = &gl_defFont;
  
  uint32_t bufsize;
  FT_Byte* buf = pack_readfile(DATA, (fname) ? fname : FONT_DEF, &bufsize);

  // Allocatagery.
  font->textures = malloc(sizeof(GLuint)*128);
  font->w = malloc(sizeof(int)*128);
  font->h = (int)h;
  if(font->textures == NULL || font->w == NULL) {
    WARN("Out of memory!");
    return;
  }

  // Create a FreeType font library.
  FT_Library library;
  if(FT_Init_FreeType(&library)) {
    WARN("FT_Init_FreeType failed");
  }

  // Objects that freetype uses to store font info.
  FT_Face face;
  if(FT_New_Memory_Face(library, buf, bufsize, 0, &face))
    WARN("FT_New_Memory_Face failed loading library from %s", fname);

  // FreeType is pretty nice and measures using 1/64 of a pixel, therfore expand.
  FT_Set_Char_Size(face, h << 6, h << 6, 96, 96);

  // Have OpenGL allocate space for the textures / display lists.
  font->list_base = glGenLists(128);
  glGenTextures(128, font->textures);

  // Create each of the font display lists.
  unsigned char i;
  for(i = 0; i < 128; i++)
    glFontMakeDList(face, i, font->list_base, font->textures, font->w);

  // We can now free the face and library.
  FT_Done_Face(face);
  FT_Done_FreeType(library);
  free(buf);
}

void gl_freeFont(glFont* font) {
  if(font == NULL) font = &gl_defFont;
  glDeleteLists(font->list_base, 128);
  glDeleteTextures(128, font->textures);
  free(font->textures);
  free(font->w);
}