/**
 * @file font.c
 *
 * @brief OpenGL font rendering routines.
 *
 * Use a displaylist to store ASCII chars rendered with freefont.
 * There are several drawing methods depending on whether you want
 * to print it all, print to a max width, print centered or print a
 * block of text.
 *
 * There are hardcoded size limits. 256 characters for all routines
 * except gl_printText which has a 1024 limit.
 *
 * @todo Check if length is too long.
 */

#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" /**< Default font path. */

/* Default font. */
glFont gl_defFont;    /**< Default font. */
glFont gl_smallFont;  /**< Small font. */

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


/**
 * @fn void gl_print(const glFont* ft_font, const double x, const double y,
 *             const glColour* c, const char* fmt, ...)
 *
 * @brief Print text on screen like printf.
 *
 * Defaults ft_font to gl_defFont if NULL.
 *    @param ft_font Font to use (NULL means gl_defFont).
 *    @param x X position to put text at.
 *    @param y Y position to put text at.
 *    @param c Colour to use (uses white if NULL).
 *    @param fmt String formatted like printf to print. *
 */
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)SCREEN_W/2., y - (double)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);

  gl_checkErr();
}

/**
 * @fn int gl_printMax(const glFont* ft_font, const int max,
 *         const double x, const double y, const glColour* c, const char* fmt, ...)
 *
 * @brief Act like gl_print but stops displaying text after reaching a certain length.
 *    @param ft_font Font to use (Null means use gl_defFont).
 *    @param max Maximum length to reach.
 *    @param x X position to display text at.
 *    @param y Y position to display text at.
 *    @param c Colour to use (NULL defaults to white).
 *    @param fmt String to display formatted like printf.
 *    @return The number of characters it 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)SCREEN_W/2., y - (double)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);

  gl_checkErr();

  return ret;
}

/**
 * @fn int gl_printMid(const glFont* ft_font, const int width, double x, const double y,
 *               const glColour* c, const char* fmt, ...)
 *
 * @brief Display text centered in position and width.
 *
 * Will truncate if text is too long.
 *    @param ft_font Font to use (NULL defaults to gl_defFont).
 *    @param width Width of area to center in.
 *    @param x X Position to display text at.
 *    @param y Y Position to display text at.
 *    @param c Colour to use for text (NULL defaults to white).
 *    @param fmt Text to display formatted string like printf.
 *    @return The number of characters it had to truncate.
 */
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)SCREEN_W/2., y - (double)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);

  gl_checkErr();

  return ret;
}

/**
 * @fn int gl_printText(const glFont* ft_font, const int width, const int height,
 *                double bx, double by, glColour* c, const char* fmt, ...)
 *
 * @brief Print a block of text that fits in the dimensions given.
 *
 * Positions are based on origin being top-left.
 *    @param ft_font Font to use (NULL defaults to gl_defFont).
 *    @param width Maximum width to print to.
 *    @param height Maximum height to print to.
 *    @param bx X position to display text at.
 *    @param by Y position to display text at.
 *    @param c Colour to use (NULL defaults to white).
 *    @param fmt Text to display formatted like printf.
 *    @return 0 on success.
 */
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[4096];
  char buf[256];
  va_list ap;
  int p, i, j, n, m, 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)SCREEN_W/2.;
  by -= (double)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 = -1; /* 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) && ((p != lastspace)))
        || (txt[i] == '\n') || (txt[i] == '\0')) {

      /* Time to draw the line. */
      m = 0;
      if(lastspace == -1) lastspace = 0;
      for(j = 0; j < (lastspace-p-1); j++) {
        if(txt[p+j+1]=='\t') {
          p++;
          continue;
        }
        m += ft_font->w[(int)txt[p+j+1]];
        if(m > width) break;
        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);

      /* This is what we are displaying. */
      glCallLists(j, GL_UNSIGNED_BYTE, &buf);
      glPopMatrix(); /* Translation matrix. */

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

  gl_checkErr();

  return ret;
}

/**
 * @fn int gl_printWidth(const glFont* ft_font, const char* fmt, ...)
 *
 * @brief Get the width that it would take to print some text.
 *
 * Does not display text on screen.
 *    @param ft_font Font to use (NULL defaults to gl_defFont).
 *    @param fmt Text to calculate the length of.
 *    @return The length of the text in pixels.
 */
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;
}

/**
 * @fn int gl_printHeight(const glFont* ft_font, const int width,
 *                 const char* fmt, ...)
 * 
 * @brief Get the height of the text if it were printed.
 *
 * Does not display the text on screen.
 *    @param ft_font Font to use (NULL defaults to gl_defFont).
 *    @param width Width to jump to next line once reached.
 *    @param fmt Text to get the height of in printf format.
 *    @return The heiht of the text.
 */
int gl_printHeight(const glFont* ft_font, const int width,
                  const char* fmt, ...) {
  
  char txt[1024]; /* Holds the string. */
  va_list ap;
  int p, i, n, len, lastspace;
  double y;

  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);
  }
  y = 0.;

  len = (int) strlen(txt);
  /* Limit the size per line. */
  lastspace = -1; /* Last ' ' or '\n' in the text. */
  n = 0;    /* Current width. */
  i = 0;    /* Current position. */
  p = -1;   /* Where we last drew up to. */

  while(i < len+1) {
    n += ft_font->w[(int)txt[i]];

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

    if(((n > width) && (p != lastspace)) ||
        (txt[i] == '\n') || (txt[i] == '\0')) {

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

/* ================
 * FONT!
 * ================
 */

/**
 * @fn static void glFontMakeDList(FT_Face face, char ch, GLuint list_base,
 *                           GLuint* tex_base, int* width_base)
 * 
 * @brief Makes the font display list.
 */
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;
  double x, y;

  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 = gl_pot(bitmap.width);
  h = gl_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 + 1);
  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. */
  x = (double)bitmap.width/(double)w;
  y = (double)bitmap.rows/(double)h;

  /* 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);
  width_base[(int)ch] = (int)(face->glyph->advance.x >> 6);

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

  FT_Done_Glyph(glyph);

  gl_checkErr();
}

/**
 * @fn void gl_fontInit(glFont* font, const char* fname, const unsigned int h)
 *
 * @brief Initializes a font.
 *    @param font Font to load (NULL defaults to gl_defFont).
 *    @param fname Name of the font (from inside packfile, NULL defaults to default font).
 *    @param h Height of the font to generate.
 */
void gl_fontInit(glFont* font, const char* fname, const unsigned int h) {
  uint32_t bufsize;
  int i;

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

  FT_Byte* buf = pack_readfile(DATA, (fname!=NULL) ? 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. */
  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);
}

/**
 * @fn void gl_freeFont(glFont* font)
 *
 * @brief Free a loaded font.
 *    @param font Font to free.
 */
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);
}

