/* XRACER (C) 1999-2000 Richard W.M. Jones <rich@annexia.org> and other AUTHORS
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: text.c,v 1.6 2000/01/18 23:24:21 rich Exp $
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "xracer.h"
#include "xracer-jpeg.h"
#include "xracer-log.h"
#include "xracer-text.h"

/* The text code has now been completely rewritten. The new
 * code is faster (it uses blended textures), and supports
 * antialiasing.
 *
 * Font files (JPEGs created in GIMP) contain the letters
 * in ASCII order from ASCII code 33 (exclamation mark) to
 * code 126 (tilde). The font file has a white background
 * and black lettering.
 *
 * Here we parse the font file and create a texture or textures
 * from it. We do this by examining the font file from left
 * to right, looking for the first character (an exclamation
 * mark). Once we have found this character, we scan the character
 * up and down to find out its total height. From this we have
 * determined the average base line and top line of the font.
 * We now move rightwards finding all the rest of the letters
 * in the same manner. For each letter we find, we add it
 * to a texture map, until the texture map grows larger than
 * 128x128, at which point we create another texture map and
 * continue until we have found all letters in the font file.
 */

static void add_font (const char *name, int size);

/* Program-level text initialization. */
void
xrTextInit ()
{
  add_font ("crillee", 14);
  add_font ("crillee", 24);
  add_font ("crillee", 36);
  add_font ("crillee", 48);
}

static void begin_font (const char *name, int size, int baseline, int normal_char_height, int lowest_extent, int total_height);

static void add_letter (const GLubyte *image, int width, int height, int components, int x, int y, int char_width, int char_height, int code);

static void end_font (void);

static int find_next_char (const GLubyte *image, int width, int height, int components, int start_x, int *xr, int *yr, int *char_widthr, int *char_heightr);

static void find_extents (const GLubyte *image, int width, int height, int components, int *lowest_extent, int *total_height);

#if 0
/* Debugging function. */
static void
dump_texture (const GLubyte *image, int width, int height, int n)
{
  int i;
  char name[1024];
  FILE *fp;

  snprintf (name, sizeof name, "/tmp/texture-%d.ppm", n);
  fp = fopen (name, "w");
  if (fp == 0) { xrLogPerror (name); exit (1); }

  fprintf (fp, "P3 %d %d 255\n", width, height);
  for (i = 0; i < width*height; ++i)
    {
      int r = *image++;
      int g = *image++;
      int b = *image++;
      int a = *image++;
      fprintf (fp, "%d %d %d\n", 0, a, 0);
    }
  fclose (fp);
}
#endif

static void
add_font (const char *name, int size)
{
  GLubyte *image;
  int width, height, components, x, y, char_height, char_width, i;
  int total_height, lowest_extent;
  char filename[1024];

  /* Load the JPEG file. */
  snprintf (filename, sizeof filename, "fonts/%s%d.jpg", name, size);
  image = xrJPEGReadFile (filename, &width, &height, &components);

  if (image == 0)
    xrLogFatal ("can't load font file %s", filename);

  /* Find bottom and top of characters. */
  find_extents (image, width, height, components,
		&lowest_extent, &total_height);

  /* Find the first character. */
  if (find_next_char (image, width, height, components,
		      0, &x, &y, &char_width, &char_height)
      < 0)
    xrLogFatal ("can't locate first character in file %s", filename);

  /* Create the font and first texture (exclamation mark, ASCII 33). */
  begin_font (name, size, y, char_height, lowest_extent, total_height);

  add_letter (image, width, height, components,
	      x, y, char_width, char_height, 33);

  /* Quote character (ASCII 34) is not joined ... */
  {
    int x0 = x;

    if (find_next_char (image, width, height, components,
			x + char_width, &x, &y, &char_width, &char_height)
	< 0)
      xrLogFatal ("can't locate quote character in file %s", filename);

    if (find_next_char (image, width, height, components,
			x + char_width, &x, &y, &char_width, &char_height)
	< 0)
      xrLogFatal ("can't locate quote character in file %s", filename);

    add_letter (image, width, height, components,
		x0, y, char_width + (x - x0), char_height, 34);
  }

  /* Look for ASCII characters 35 - 126. */
  for (i = 35; i <= 126; ++i)
    {
      if (find_next_char (image, width, height, components,
			  x + char_width, &x, &y, &char_width, &char_height)
	  < 0)
	xrLogFatal ("can't locate character code %d in file %s", i, filename);

      add_letter (image, width, height, components,
		  x, y, char_width, char_height, i);
    }

  if (find_next_char (image, width, height, components,
		      x + char_width, &x, &y, &char_width, &char_height) == 0)
    xrLog (LOG_DEBUG, "ignored extra junk in font file %s", filename);

  free (image);

  end_font ();
}

#define IMAGE(x,y) (image[((x) + width*(y))*components])

/* JPEG encoding introduces scattered light-coloured pixels. The
 * real solution here is to use another lossless format, eg. PPM.
 * However, this will do for now.
 */
#define EPSILON 128

static int
is_white_column (const GLubyte *image, int width, int height, int components,
		 int x)
{
  int y;

  for (y = 0; y < height; ++y)
    if (IMAGE (x,y) <= EPSILON)
      return 0;

  return 1;
}

static int
is_white_row (const GLubyte *image, int width, int height, int components,
	      int x0, int x1, int y)
{
  int x;

  for (x = x0; x < x1; ++x)
    if (IMAGE (x, y) <= EPSILON)
      return 0;

  return 1;
}

static void
find_extents (const GLubyte *image, int width, int height, int components,
	      int *lowest_extentr, int *total_heightr)
{
  int y = 0;

  while (is_white_row (image, width, height, components, 0, width, y))
    y++;

  *lowest_extentr = y;

  y = height - 1;
  while (is_white_row (image, width, height, components, 0, width, y))
    y--;
  y++;

  *total_heightr = y - *lowest_extentr;
}

static int
find_next_char (const GLubyte *image, int width, int height, int components,
		int start_x,
		int *xr, int *yr, int *char_widthr, int *char_heightr)
{
  int x = start_x, x1, y, y1;

  while (x < width)
    {
      /* Search for the character. */
      if (!is_white_column (image, width, height, components, x))
	{
	  /* Found the next character. */
	  *xr = x;

	  /* Find the width of the character. */
	  x1 = x + 1;
	  while (!is_white_column (image, width, height, components, x1))
	    x1++;

	  *char_widthr = x1 - x;

	  /* Find the base and height of the character. */
	  y = 0;
	  while (is_white_row (image, width, height, components, x, x1, y))
	    y++;
	  *yr = y;

	  /* Find the top of the character. */
	  y1 = height - 1;
	  while (is_white_row (image, width, height, components, x, x1, y1))
	    y1--;
	  y1++;

	  *char_heightr = y1 - y;

	  return 0;
	}

      x++;
    }

  return -1;			/* Not found. */
}

static int
next_power_of_2 (int a)
{
  int b;

  for (b = 1; ; b <<= 1)
    if (a <= b) return b;
}

struct font_char
{
  GLuint tex_num;
  GLfloat tex_coord_x_low;
  GLfloat tex_coord_x_high;
  GLfloat tex_coord_y_low;
  GLfloat tex_coord_y_high;
  int char_width;
};

struct font
{
  struct font *next;		/* Stored on a linked list. */
  const char *name;		/* Font name. */
  int size;			/* Point size. */

  int quad_y_coords[2];

  int text_height;

  /* This maps character codes to actual textures. */
  struct font_char c[128];
};

static struct font *fonts = 0;

static int saved_lowest_extent;
static int saved_total_height;

/* Textures are limited to this width - must be a power of 2. */
#define MAX_TEXTURE_WIDTH 128

/* Text colour - current implementation only allows text to be one colour. */
static GLubyte text_colour[3] = { 0, 255, 0 };

/* The texture that we are currently building. */
static GLuint tex_num;
static GLubyte *tex_image;
static int tex_width, tex_height;
static int tex_x_posn;
struct font *font;

static void
begin_font (const char *name, int size, int baseline, int normal_char_height,
	    int lowest_extent, int total_height)
{
  saved_lowest_extent = lowest_extent;
  saved_total_height = total_height;

  font = xmalloc (sizeof (struct font));
  memset (font, 0, sizeof font);
  font->next = fonts;
  fonts = font;

  font->name = name;
  font->size = size;

  font->quad_y_coords[0] = lowest_extent - baseline;
  font->quad_y_coords[1] = font->quad_y_coords[0] + total_height;

  font->text_height = total_height;

  /* Allocate a texture. */
  glGenTextures (1, &tex_num);
  glBindTexture (GL_TEXTURE_2D, tex_num);

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

  glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

  tex_width = MAX_TEXTURE_WIDTH;
  tex_height = next_power_of_2 (total_height);
  tex_x_posn = 0;
  tex_image = xmalloc (4 * tex_width * tex_height);
  memset (tex_image, 0, 4 * tex_width * tex_height);
}

static void
add_letter (const GLubyte *image, int width, int height, int components,
	    int x, int y, int char_width, int char_height, int code)
{
  int ix, iy, tx, ty;

  /* Can it fit into this texture? If not, allocate a new one. */
  if (tex_x_posn + char_width > tex_width)
    {
      glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0,
		    GL_RGBA, GL_UNSIGNED_BYTE, tex_image);
      free (tex_image);

      glGenTextures (1, &tex_num);
      glBindTexture (GL_TEXTURE_2D, tex_num);

      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

      glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

      tex_width = MAX_TEXTURE_WIDTH;
      tex_height = next_power_of_2 (saved_total_height);
      tex_x_posn = 0;
      tex_image = xmalloc (4 * tex_width * tex_height);
      memset (tex_image, 0, 4 * tex_width * tex_height);
    }

  /* Cut out the character and copy it into the texture image. */
  tx = tex_x_posn;
  ty = y - saved_lowest_extent;

  for (iy = 0; iy < char_height; ++iy)
    for (ix = 0; ix < char_width; ++ix)
      {
	int alpha = 255 - IMAGE (x + ix, y + iy);

	tex_image [(tx + ix + tex_width * (ty + iy)) * 4]
	  = text_colour[0];
	tex_image [(tx + ix + tex_width * (ty + iy)) * 4 + 1]
	  = text_colour[1];
	tex_image [(tx + ix + tex_width * (ty + iy)) * 4 + 2]
	  = text_colour[2];
	tex_image [(tx + ix + tex_width * (ty + iy)) * 4 + 3]
	  = alpha;
      }

  /* Create the character. */
  font->c[code].tex_num = tex_num;
  font->c[code].tex_coord_x_low = (GLfloat) tx / tex_width;
  font->c[code].tex_coord_x_high = (GLfloat) (tx + char_width) / tex_width;
  font->c[code].tex_coord_y_low = 0;
  font->c[code].tex_coord_y_high = (GLfloat) saved_total_height / tex_height;
  font->c[code].char_width = char_width;

  tex_x_posn += char_width;
}

static void
end_font ()
{
  /* Finish last texture. */
  glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0,
		GL_RGBA, GL_UNSIGNED_BYTE, tex_image);
  free (tex_image);
}

void *
xrTextFindFont (const char *name, int point_size)
{
  struct font *font = fonts;

  while (font)
    {
      if (strcmp (font->name, name) == 0 && font->size == point_size)
	return (void *) font;
      font = font->next;
    }

  return 0;
}

void
xrTextPuts (void *fv, const char *text, int x, int y)
{
  struct font *font = (struct font *) fv;

  glColor3f(1.0,1.0,1.0);

  glEnable (GL_TEXTURE_2D);

  while (*text)
    {
      if (*text >= 33 && *text <= 126)
	{
	  struct font_char *fc = &font->c[(int) (*text)];

	  glBindTexture (GL_TEXTURE_2D, fc->tex_num);

	  glBegin (GL_QUADS);
          glTexCoord2f (fc->tex_coord_x_low, fc->tex_coord_y_high);
          glVertex2i (x, xrHeight - (y + font->quad_y_coords[0]));
          glTexCoord2f (fc->tex_coord_x_low, fc->tex_coord_y_low);
          glVertex2i (x, xrHeight - (y + font->quad_y_coords[1]));
          glTexCoord2f (fc->tex_coord_x_high, fc->tex_coord_y_low);
          glVertex2i (x + fc->char_width, xrHeight - (y + font->quad_y_coords[1]));
          glTexCoord2f (fc->tex_coord_x_high, fc->tex_coord_y_high);
          glVertex2i (x + fc->char_width, xrHeight - (y + font->quad_y_coords[0]));
	  glEnd ();

	  x += fc->char_width;
	}
      else
	{
	  x += font->c[33].char_width;
	}
      text++;
    }

  glDisable (GL_TEXTURE_2D);
}

void
xrTextPrintf (void *fv, int x, int y, const char *fs, ...)
{
  va_list args;
  char message[1024];

  va_start (args, fs);
  vsnprintf (message, sizeof message, fs, args);
  va_end (args);

  xrTextPuts (fv, message, x, y);
}

int
xrTextGetWidth (void *fv, const char *text)
{
  struct font *font = (struct font *) fv;
  int w = 0;

  while (*text)
    {
      if (*text >= 33 && *text <= 126)
	w += font->c[(int) (*text)].char_width;
      else
	w += font->c[33].char_width;
      text++;
    }

  return w;
}

int
xrTextGetHeight (void *fv)
{
  struct font *font = (struct font *) fv;

  return font->text_height;
}
