/*******************************************************************************

  Copyright(c) 2003-2004 Aurelien Reynaud.

  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.

  The full GNU General Public License is included in this distribution in the
  file called COPYING.

*******************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "ggiterm.h"
#include "debug.h"

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H

#include <ggi/ggi.h>

/* Global variables */
FT_Library library;
FT_Face face;
FT_Int32 ft_flags;
ggi_color *colortab = NULL;
int cursor_x0, cursor_y0;
int underline_y, underline_h;

/* External variables */
extern ggi_visual_t vis;
extern ggi_pixel color[];
extern ggi_color palette[];
extern int cell_w, cell_h;
extern ggi_visual_t backend_vis;

int unicode_is_alnum(wchar_t);

int min(int a, int b)
{
	return (a>b?b:a);
}
int max(int a, int b)
{
	return (a>b?a:b);
}

int freetype_cache_load(void *boxptr, wchar_t code, int fgcol, int bgcol, int ul)
{
	int i, j, colorlen, err;
	unsigned char *tab;
	unsigned char val;
	ggi_color bg, fg;
	int bmp_xmin, bmp_xmax, bmp_ymin, bmp_ymax;

	debug(DEBUG_FUNCTION,
	      "called with args (boxptr=[void*], code=0x%X04, fgcol=%d, bgcol=%d, ul=%d",
	      (wint_t)code, fgcol, bgcol, ul);

	debug(DEBUG_FT, "Loading character 0x%04X", (wint_t)code);
	err = FT_Load_Char(face, code, ft_flags|FT_LOAD_RENDER);
	if (err) {
		error("Error loading/rendering code 0x%04X", (unsigned int)code);
		error("Using [SPACE] instead");
		FT_Load_Char(face, 0x20, ft_flags|FT_LOAD_RENDER);
	}
	debug(DEBUG_FT, "Glyph rendered to bitmap");

	debug(DEBUG_FT, "Bitmap fields:");
	debug(DEBUG_FT, "%d rows %d pixels wide",
	       face->glyph->bitmap.rows, face->glyph->bitmap.width);
	debug(DEBUG_FT, "top = %d, left = %d",
	       face->glyph->bitmap_top, face->glyph->bitmap_left);
	debug(DEBUG_FT, "pitch = %d",
	       face->glyph->bitmap.pitch);
	debug(DEBUG_FT, "Number of grays: %d",
	       face->glyph->bitmap.num_grays);


	/*debug(DEBUG_FT, "Bitmap values:");*/
	tab = face->glyph->bitmap.buffer;
	colorlen = 0;
	bg = palette[bgcol];
	fg = palette[fgcol];
	/* We don't use the computed bitmap positions, since hinting can cause
	   the bitmap to be larger than the cell.
	   If the bitmap starts before the cell's left edge
	   (face->glyph->bitmap_left < 0), we reduce the width and shift the
	   bitmap to the right by the same value. */
	bmp_xmin = max(-face->glyph->bitmap_left - cursor_x0, 0);
	bmp_xmax = min(cell_w - cursor_x0 - face->glyph->bitmap_left, face->glyph->bitmap.width);
	bmp_ymin = max(face->glyph->bitmap_top - cursor_y0, 0);
	bmp_ymax = min(cell_h - cursor_y0 + face->glyph->bitmap_top, face->glyph->bitmap.rows);
	debug(DEBUG_FT, "bmp_xmin=%d, bmp_xmax=%d", bmp_xmin, bmp_xmax);
	debug(DEBUG_FT, "bmp_ymin=%d, bmp_ymax=%d", bmp_ymin, bmp_ymax);
	for (j = bmp_ymin; j < bmp_ymax; j++) {
		/*fprintf(stderr,"Row %3d:", j);*/
		for (i = bmp_xmin; i < bmp_xmax; i++) {
			if (ft_flags & FT_LOAD_MONOCHROME)
				/* Maybe replace 128>> with 1<< on bigendian */
				val = (tab[i/8] & (128>>(i%8)) ? 255 : 0);
			else val = tab[i];
			/*fprintf(stderr," %3d", val);*/
			colortab[colorlen].r = bg.r + ((fg.r-bg.r) * val) / 255;
			colortab[colorlen].g = bg.g + ((fg.g-bg.g) * val) / 255;
			colortab[colorlen].b = bg.b + ((fg.b-bg.b) * val) / 255;
			colorlen++;
		}
		/*fprintf(stderr,"\n");*/
		tab += face->glyph->bitmap.pitch;
	}
	debug(DEBUG_FT, "colorlen=%d", colorlen);
		
	/* Erase background */
	ggiSetGCForeground(backend_vis, color[bgcol]);
	ggiDrawBox(backend_vis, 0, 0, cell_w, cell_h);

	err = ggiPackColors(backend_vis, boxptr, colortab, colorlen);
	if (err) {
		error("PackColors failed");
		debug(DEBUG_FUNCTION, "Leaving");
		return 1;
	}
	debug(DEBUG_FT, "PackColors succeeded");

	err = ggiPutBox(backend_vis,
		        cursor_x0 + face->glyph->bitmap_left + bmp_xmin,
			cursor_y0 - face->glyph->bitmap_top + bmp_ymin,
			bmp_xmax - bmp_xmin,
			bmp_ymax - bmp_ymin,
			boxptr);
	if (err) {
		error("PutBox failed");
		debug(DEBUG_FUNCTION, "Leaving");
		return 1;
	}
	debug(DEBUG_FT, "PutBox succeeded");

	if (ul && underline_h != 0) {
		ggiSetGCForeground(backend_vis, color[fgcol]);
		ggiDrawBox(backend_vis,
		           0, cursor_y0 - underline_y,
		           cell_w, underline_h);
	}
	ggiGetBox(backend_vis, 0, 0, cell_w, cell_h, boxptr);

	debug(DEBUG_FUNCTION, "Leaving");
	return 0;
}

wchar_t freetype_get_charmap()
{
	static FT_UInt gindex;
	static wchar_t last = 0;
	
	debug(DEBUG_FUNCTION, "called with no args");
	if(last == 0) {
		last = FT_Get_First_Char(face, &gindex);
	} else {
		last = gindex ? FT_Get_Next_Char(face, last, &gindex) : 0;
	}
	debug(DEBUG_FUNCTION, "Leaving");
	return last;
}

int freetype_has_glyph(wchar_t glyph)
{
	return FT_Get_Char_Index(face, glyph);
}

int freetype_validate_font()
{
	debug(DEBUG_FUNCTION, "called with no args");
	
	debug(DEBUG_INIT, "Font family: %s", face->family_name);
	if (FT_IS_SCALABLE(face)) {
		debug(DEBUG_INIT, "font is scalable");
	} else {
		debug(DEBUG_INIT, "font is not scalable");
		debug(DEBUG_FUNCTION, "Leaving");
		return 1;
	}
	if (FT_IS_FIXED_WIDTH(face)) {
		debug(DEBUG_INIT, "font is fixed width");
	} else {
		debug(DEBUG_INIT, "font is not fixed width");
#ifdef FORCE_MONOSPACE
		debug(DEBUG_FUNCTION, "Leaving");
		return 1;
#endif
	}
	if (face->charmap != NULL) {
		debug(DEBUG_INIT, "font has Unicode/Latin1/ASCII charmap");
	} else {
		debug(DEBUG_INIT, "font doesn't have Unicode/Latin1/ASCII charmap");
		debug(DEBUG_FUNCTION, "Leaving");
		return 1;
	}
	debug(DEBUG_INIT, "%ld glyphs in font", face->num_glyphs);
	debug(DEBUG_INIT, "global metrics in font units:");
	debug(DEBUG_INIT, "BBox width  = %ld to %ld (%ld)",
	      face->bbox.xMin, face->bbox.xMax,
	      face->bbox.xMax - face->bbox.xMin);
	debug(DEBUG_INIT, "BBox height = %ld to %ld (%ld)",
	      face->bbox.yMin, face->bbox.yMax,
	      face->bbox.yMax - face->bbox.yMin);
	debug(DEBUG_INIT, "text height = %d", face->height);
	debug(DEBUG_INIT, "maximum advance width = %d",
	      face->max_advance_width);
	debug(DEBUG_INIT, "units per EM = %d", face->units_per_EM);
	debug(DEBUG_INIT, "ascender = %d", face->ascender);
	debug(DEBUG_INIT, "descender = %d", face->descender);
	debug(DEBUG_INIT, "underline position = %d", face->underline_position);
	debug(DEBUG_INIT, "underline thickness = %d",
	      face->underline_thickness);
	if (!face->underline_thickness) {
		error("WARNING! font does not allow underlining. "
		      "Going on anyway...");
	}
	debug(DEBUG_FUNCTION, "Leaving");
	return 0;
}

void freetype_find_cell_size(int fontsize, int *w, int *h)
{
	FT_UInt gindex;
	wchar_t code;
	int i=0;
	int bearingX, bearingY, height, width, advance;
	int xmin=65535, xmax=-65536, ymin=65535, ymax=-65536;
	int advance_max=0, height_max=0, width_max=0;
	int top, bottom;

	debug(DEBUG_FUNCTION,
	      "called with args (fontsize=%d, width=[*int], height=[*int]",
	      fontsize);
	
	debug(DEBUG_INIT, "Parsing font...");
	code = FT_Get_First_Char(face, &gindex);
	while (gindex) {
		if (!FT_Load_Char(face, code, ft_flags|FT_LOAD_NO_SCALE) &&
		    /*unicode_is_alnum(code)*/code < 256) {
			bearingY = face->glyph->metrics.horiBearingY;
			bearingX = face->glyph->metrics.horiBearingX;
			advance  = face->glyph->metrics.horiAdvance;
			height   = face->glyph->metrics.height;
			width    = face->glyph->metrics.width;

			advance_max = max(advance_max, advance);
			xmin        = min(xmin, bearingX);
			xmax        = max(xmax, bearingX + width - 1);
			ymax        = max(ymax, bearingY);
			ymin        = min(ymin, bearingY - height + 1);
			height_max  = max(height_max, height);
			width_max   = max(width_max, width);

			i++;
		}
		code = FT_Get_Next_Char(face, code, &gindex);
	}
	debug(DEBUG_INIT, "%d glyphs scanned", i);

	/* Failsafe: if we were too restrictive in the range of scanned codes
	   and none was suitable, fallback to the global value */
	if (i == 0) {
		advance_max = face->max_advance_width;
	}
	debug(DEBUG_INIT, "ymin:        %d", ymin);
	debug(DEBUG_INIT, "ymax:        %d", ymax);
	debug(DEBUG_INIT, "ymax-ymin:   %d", ymax-ymin);
	debug(DEBUG_INIT, "height_max:  %d", height_max);
	debug(DEBUG_INIT, "xmin:        %d", xmin);
	debug(DEBUG_INIT, "xmax:        %d", xmax);
	debug(DEBUG_INIT, "xmax-xmin:   %d", xmax-xmin);
	debug(DEBUG_INIT, "width_max:   %d", width_max);
	debug(DEBUG_INIT, "advance_max: %d", advance_max);
	debug(DEBUG_INIT, "global height:  %d", face->height);
	
	top = ymax;
	bottom = min(ymin, face->underline_position);
	if (top - bottom + 1 > face->height) {
		error("Font problem: Underline is outside the cell!");
		bottom = ymin;
	}

	cursor_x0 = - (xmin * advance_max) / (xmax - xmin + 1);
	cursor_y0 = (ymax * face->height) / (top - bottom + 1);

	cursor_x0 = (fontsize * cursor_x0) / face->units_per_EM;
	cursor_y0 = (fontsize * cursor_y0) / face->units_per_EM;
	*w        = (fontsize * advance_max) / face->units_per_EM;
	*h        = (fontsize * face->height) / face->units_per_EM;
	debug(DEBUG_INIT, "cells will be: %dx%d", *w, *h);
	debug(DEBUG_INIT, "cursor_x0: %d", cursor_x0);
	debug(DEBUG_INIT, "cursor_y0: %d", cursor_y0);

	debug(DEBUG_FUNCTION, "Leaving");
}

int freetype_init(char *file, int size, int *width, int *height)
{
	int err;
	long memory_size;
	ggi_mode current_mode;

	debug(DEBUG_FUNCTION,
	      "called with args (file=%s, size=%d, *width, *height",
	      file, size);
	
	err = FT_Init_FreeType(&library);
	if (err) {
		error("Error during library initialization");
		goto exit_with_error;
	}
	debug(DEBUG_INIT, "Library initialized");

	err = FT_New_Face(library, file, 0, &face);
	if (err == FT_Err_Unknown_File_Format) {
		error("Font format not supported");
		goto free_and_exit_with_error;
	} else if (err) {
		error("Cannot open %s", file);
		goto free_and_exit_with_error;
	}
	err = freetype_validate_font();
	if (err) {
		error("Font not suitable!");
		goto free_and_exit_with_error;
	}
	err = FT_Set_Pixel_Sizes(face, size, 0);
	if (err) {
		error("Couldn't set pixel size");
		goto free_and_exit_with_error;
	}

	ft_flags = FT_LOAD_DEFAULT; /* scale and fit to grid */
	/* Ignore embedded bitmaps, as they do not necessarily fit in our
	   carefully computed borders */
	ft_flags |= FT_LOAD_NO_BITMAP;
	/* Do we do aliasing? */
	ggiGetMode(vis, &current_mode);
	if (GT_DEPTH(current_mode.graphtype) <= 8 || FORCE_MONOCHROME) {
		debug(DEBUG_INIT, "Color depth is %d: Won't do aliasing",
		      GT_DEPTH(current_mode.graphtype));
		/* use hinting algorithm for 1 bit values */
		ft_flags |= FT_LOAD_TARGET_MONO;
		/* render 1 bit values */
		ft_flags |= FT_LOAD_MONOCHROME;
	} else {
		debug(DEBUG_INIT, "Color depth is %d: Will do aliasing",
		      GT_DEPTH(current_mode.graphtype));
		/* use hinting algorithm for 8 bits values */
		ft_flags |= FT_LOAD_TARGET_NORMAL;
	}

	freetype_find_cell_size(size, width, height);
	debug(DEBUG_INIT, "Cell size set to %dx%d", *width, *height);

	/* freetype-specific memory allocation */
	memory_size = (*width) * (*height) * sizeof(ggi_color);
	colortab = malloc(memory_size);
	if (colortab == NULL) {
		error("Couldn't allocate memory");
		goto free_and_exit_with_error;
	}
	debug(DEBUG_INIT,
	      "%ld bytes allocated for freetype rendering engine", memory_size);

	/*underline_y = size *
	              (face->underline_position - (face->underline_thickness / 2)) /
		      face->units_per_EM;*/
	underline_y = (size *
	              face->underline_position) /
		      face->units_per_EM;
	underline_h = (size * face->underline_thickness) % face->units_per_EM ?
	              (size * face->underline_thickness) / face->units_per_EM + 1 :
		      (size * face->underline_thickness) / face->units_per_EM;
	debug(DEBUG_INIT,
	      "Underline starts at %d pixels from baseline", underline_y);
	debug(DEBUG_INIT,
	      "Underline is %d pixels thick", underline_h);

	debug(DEBUG_FUNCTION, "Leaving");
	return 0;

free_and_exit_with_error:
	FT_Done_FreeType(library);
exit_with_error:
	debug(DEBUG_FUNCTION, "Leaving");
	return 1;
}

void freetype_exit()
{
	debug(DEBUG_FUNCTION, "called with no args");
	free(colortab);
	FT_Done_FreeType(library);
	debug(DEBUG_FUNCTION, "Leaving");
}
