/*
 * Copyright (C) 1997 and 1998 WIDE Project.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/*
 * $Id: tfont.c,v 1.1.1.1 2001-03-14 11:57:54 thep Exp $
 */

#include "mgp.h"

#ifdef FREETYPE

int tfcachesize = 3000; /*XXX*/
#define	TFFONT_NUM	128
#define	TFHASH_SIZE	255
#define TFCACHE_HASH(c, siz)   ((c ^ siz) % TFHASH_SIZE)

static TT_Raster_Map		bitmap;
static TT_Face_Properties	properties[TFFONT_NUM];
static TT_Face			face[TFFONT_NUM];
static TT_Glyph			glyph[TFFONT_NUM];
static TT_Instance		instance[TFFONT_NUM];
static TT_Glyph_Metrics		metrics;
static TT_Error			error;
static TT_Engine		engine;
static XImage *xim;

static char *tf_curname = NULL;
static char *tf_mcurname = NULL;
static int tfcinitdone;
static int tfcachecnt;
static struct tfont tfclru;
static struct tfont tfcache[TFHASH_SIZE];
static int tffontcnt = 0;
static int tfcachehit = 0;
static int tfcachemiss = 0;
static int tfcuridx = -1;
static char tfloadedfont[1024][TFFONT_NUM];

#define TFC_INSHASH(tfc) { \
	struct tfont *h, *n;						\
	h = &tfcache[TFCACHE_HASH(tfc->code, tfc->size)]; \
	n = h->next; tfc->next = n; n->prev = tfc;			\
	h->next = tfc; tfc->prev = h;					\
}
#define TFC_DELHASH(tfc) { \
	struct tfont *n, *p;						\
	n = tfc->next; p = tfc->prev;					\
	n->prev = p; p->next = n;					\
}
#define TFC_INSLRU(tfc) { \
	struct tfont *p;						\
	p = tfclru.lruprev; tfc->lruprev = p; p->lrunext = tfc;		\
	tfclru.lruprev = tfc; tfc->lrunext = &tfclru;			\
}
#define TFC_DELLRU(tfc) { \
	struct tfont *n, *p;						\
	n = tfc->lrunext; p = tfc->lruprev;				\
	n->lruprev = p; p->lrunext = n;					\
}

static void tfc_init __P((void));
static void tfc_free __P((struct tfont *));
static struct tfont *tfc_lookup __P((u_int, u_int, char*));
static struct tfont *tfc_alloc __P((u_int, u_int, char *, char *));
static short CharToUnicode __P((u_int, char *));
static short jistosjis __P((u_int));
static TT_Error LoadTrueTypeChar __P((int, int));

#define TTFLOOR(x)	((x) & -64)
#define TTCEIL(x)	(((x) + 63) & -64)

static void
tfc_init()
{
	u_int	i;
	struct tfont *tfc;

	/* Initialize engine */
	if ((error = TT_Init_FreeType(&engine))) {
		fprintf(stderr, "Error while initializing engine, code = %d.\n",
			(int)error);
		cleanup(-1);
	}

	tfc = tfcache;
	for (tfc = tfcache, i = 0; i < TFHASH_SIZE; tfc++, i++)
		tfc->next = tfc->prev = tfc;
	tfclru.lrunext = tfclru.lruprev = &tfclru;
	tfcinitdone ++;

#ifdef FREETYPE_CHARSET16
	unicode_map_init();
#endif
}

static void
tfc_free(tfc)
	struct tfont *tfc;
{
	TFC_DELHASH(tfc);
	TFC_DELLRU(tfc);
	free(tfc->fontname);
	free(tfc->dbitmap);
	free(tfc);
	tfcachecnt--;
}

int
tfc_setsize(char_size)
	u_int char_size;
{
	if (!tfcinitdone)
		return -1;

	if (tfcuridx < 0 || tffontcnt <= tfcuridx) {
		if (verbose)
			fprintf(stderr, "tfc_setsize: font index out of bound\n");
		return -1;
	}

	error = TT_Set_Instance_CharSize(instance[tfcuridx],
			(char_size * 0.75) * 64);
	if (error) {
		fprintf(stderr,
			"Could not set device resolutions for \"%s\".\n",
			tfloadedfont[tfcuridx]);
		return -1;
	}
	return 0;
}

struct tfont *
tfc_get(code, size, force, registry, charset16)
	u_int code, size;
	int force;
	char *registry;
	int charset16;
{
	struct tfont *tfc, *ntfc;

	if (!tfcinitdone)
		tfc_init();

	if (charset16)
		tfc = tfc_lookup(code, size, tf_mcurname);
	else
		tfc = tfc_lookup(code, size, tf_curname);

	if (tfc == NULL) {
		/* XXX */
		if (tfcachecnt >= tfcachesize) {
			if (!force)
				return NULL;
			tfc = tfclru.lrunext;
			while (tfcachecnt >= tfcachesize) {
				if (tfc == &tfclru)
					break;
				ntfc = tfc->lrunext;
				if (tfc->ref == 0)
					tfc_free(tfc);
				tfc = ntfc;
			}
		}

		if (charset16)
			tfc = tfc_alloc(code, size, tf_mcurname, registry);
		else
			tfc = tfc_alloc(code, size, tf_curname, registry);
	}
	return tfc;
}

void
tfc_setfont(name, charset16)
	char *name;
	int charset16;  /*2-octet charset?*/
{
	char *fontname = NULL;
	u_int i;
	int res = 96; /* XXX */
	char pathname[MAXPATHLEN];
	int trial;
	TT_Face	tface;

	if (!tfcinitdone)
		tfc_init();

	if (TFFONT_NUM <= tffontcnt) {
		fprintf(stderr, "internal error: "
			"too many fonts opened (increase TFFONT_NUM)\n");
		cleanup(-1);
	}

	if (!name || !name[0]) {
		if (freetypefont0 && freetypefont0[0]) {
			if (verbose) {
				fprintf(stderr, "tfc_setfont: "
					"font name not given, "
					"using last resort (%s)\n",
					freetypefont0);
			}
			name = freetypefont0;
		} else {
			if (verbose) {
				fprintf(stderr, "tfc_setfont: "
					"font name not given, fail\n");
			}
			goto fail;
		}
	}

	/* check font cache first */
	for (trial = 0; trial < 2; trial++) {
		switch (trial) {
		case 0:
			fontname = name;
			break;
		case 1:
			snprintf(pathname, sizeof(pathname),
				"%s/%s", freetypefontdir, name);
			fontname = pathname;
			break;
		}

		for (i = 0; i < tffontcnt; i ++) {
			if (!strcmp(fontname, tfloadedfont[i])) {
				/* already loaded */
#if 0
				if (verbose) {
					fprintf(stderr,
						"font cache hit for \"%s\"\n",
						fontname);
				}
#endif
				tfcuridx = i;
				if (charset16)
					tf_mcurname = tfloadedfont[i];
				else
					tf_curname = tfloadedfont[i];
#if 0
				tfc_setsize(char_size);
#endif
				return;
			}
		}
	}

	/* try to load font */
	for (trial = 0; trial < 3; trial++) {
		switch (trial) {
		case 0:
			fontname = name;
			break;
		case 1:
			snprintf(pathname, sizeof(pathname),
				"%s/%s", freetypefontdir, name);
			fontname = pathname;
			break;
		case 2:
			if (!freetypefont0 || !freetypefont0[0])
				continue;
			fontname = freetypefont0;
			/* already loaded? */
			for (i = 0; i < tffontcnt; i ++) {
				if (!strcmp(fontname, tfloadedfont[i])){
					tfcuridx = i;
					if (verbose) 
						fprintf(stderr, "font \"%s\" already loaded: %d\n", 
							fontname, tffontcnt);
					goto cached;
				}
			}
			break;
		}

		/* Load face */
		if (verbose) fprintf(stderr, "trying to open font \"%s\"\n", fontname);
		error = TT_Open_Face(engine, fontname, &tface);
		if (!error)
			break;
	}
	if (trial == 2) {
		if (verbose) {
			fprintf(stderr, "font \"%s\" not found, "
				"last resort font \"%s\" was used\n",
				name, freetypefont0);
		}
	} else if (trial == 3) {
		if (verbose) {
			fprintf(stderr, "could not load font \"%s\", "
				"using other rendering engine\n", name);
		}
		goto fail;
	}
	if (verbose) {
		fprintf(stderr, "font \"%s\" opened successfully\n",
			fontname);
	}

	strcpy(tfloadedfont[tffontcnt], fontname);
	tfcuridx = tffontcnt;
	face[tfcuridx] = tface;
	tffontcnt++;

	/* get face properties and allocate preload arrays */
	error = TT_Get_Face_Properties(face[tfcuridx], &properties[tfcuridx]);
	if (error) {
		fprintf(stderr, "Could not create face property for "
			"\"%s\" (err 0x%04x).\n", fontname, (int)error);
		goto fail2;
	}

	/* create glyph */
	error = TT_New_Glyph(face[tfcuridx], &glyph[tfcuridx]);
	if (error) {
		fprintf(stderr, "Could not create glyph container for "
			"\"%s\" (err 0x%04x).\n", fontname, (int)error);
		goto fail2;
	}

	/* create instance */
	error = TT_New_Instance(face[tfcuridx], &instance[tfcuridx]);
	if (error) {
		fprintf(stderr, "Could not create instance for "
			"\"%s\" (err 0x%04x).\n", fontname, (int)error);
		goto fail2;
	}

	error = TT_Set_Instance_Resolutions(instance[tfcuridx], res, res);
	if (error) {
		fprintf(stderr, "Could not set device resolutions for "
			"\"%s\" (err 0x%04x).\n", fontname, (int)error);
		goto fail2;
	}

cached:
	if (charset16)
		tf_mcurname = tfloadedfont[tfcuridx];
	else
		tf_curname = tfloadedfont[tfcuridx];
	if (tfc_setsize(char_size[caching]) < 0)
		goto fail2;

	/* success */
	return;

fail2:
	TT_Close_Face(tface);
	tffontcnt--;
fail:
	tf_curname = "";
	tfcuridx = -1;
	return;
}

static struct tfont *
tfc_lookup(code, size, fontname)
	u_int code, size;
	char *fontname;
{
	u_int	i;
	struct tfont *tfc, *htfc;

	if (!fontname)  
		return NULL;    

	for (i = 0; i < tffontcnt; i ++) {
		if (!strcmp(fontname, tfloadedfont[i]))
			tfcuridx = i;
	}

	htfc = &tfcache[TFCACHE_HASH(code, size)];
	for (tfc = htfc->next; tfc != htfc; tfc = tfc->next) {
		if (tfc->code == code
		 && tfc->size == size
		 && strcmp(tfc->fontname, fontname) == 0) {
			tfcachehit++;
			TFC_DELLRU(tfc);
			TFC_INSLRU(tfc);
			return tfc;
		}
	}
	tfcachemiss++;

	return NULL;
}

static struct tfont *
tfc_alloc(code, size, fontname, registry)
	u_int code, size;
	char *fontname;
	char *registry;
{
	struct tfont *tfc;
	short unicode;

	/* if no font was ever loaded, try loading the last resort font */
	if (!tffontcnt) {
		if (verbose)
			fprintf(stderr, "no fontcount\n");
		tfc_setfont(NULL, 0);
	}

	/* font out of bounds, suggests no font was available */
	if (tfcuridx < 0 || tffontcnt <= tfcuridx) {
		if (verbose)
			fprintf(stderr, "tfc_alloc: font index out of bound\n");
		return NULL;
	}

	/* This is required! */
	if (tfc_setsize(char_size[caching]) < 0)
		return NULL;

	tfc = (struct tfont *)malloc(sizeof(*tfc));
	if (tfc == NULL) {
		fprintf(stderr, "tfc_alloc: malloc failed\n");
		return NULL;
	}

#if 1
	unicode = CharToUnicode(code, registry);
#else
	unicode = code;
#endif
	if (!unicode)
		return NULL;
	if ((error = LoadTrueTypeChar(unicode, 0))) {
		fprintf(stderr, "LoadTrueTypeChar fail %d\n", (int)error);
		fprintf(stderr, "fontfile: \"%s\" char: '%c' (0x%04x)\n",
			tfloadedfont[tfcuridx],
			isprint(unicode) ? unicode : '?',
			unicode);
		return NULL;
	}

	TT_Get_Glyph_Metrics(glyph[tfcuridx], &metrics);

	/*
	 * TT_Get_Glyph_Pixmap generates pixmap starting from FreeType
	 * origin (leftmost side, baseline). Because of this 3rd and 4th
	 * arguments are necessary.
	 * For X axis (3rd argument), we have to take metrics.bearingX as
	 * offset.  Y axis must be shifted if there's descent box (image
	 * below the baseline).  4th argument is specified for that.
	 */
	bitmap.rows = (metrics.bbox.yMax - metrics.bbox.yMin) >> 6;
	bitmap.rows += 2;	/* insurance to cope with number-round error */
	bitmap.width = metrics.bbox.xMax - metrics.bbox.xMin;
	bitmap.width >>= 6;
	bitmap.width += 2;	/* insurance to cope with number-round error */
	bitmap.cols = (bitmap.width+3) & -4;
	bitmap.flow   = TT_Flow_Down;
	bitmap.size   = (long)bitmap.cols * bitmap.rows;
	bitmap.bitmap = (void *)malloc((int)bitmap.size);
	memset(bitmap.bitmap, 0, bitmap.size);

	/* be very careful about CEIL/FLOOR. */
	TT_Get_Glyph_Pixmap(glyph[tfcuridx], &bitmap,
		TTCEIL(-metrics.bearingX),
		TTCEIL(metrics.bbox.yMax - metrics.bbox.yMin - metrics.bearingY));

	tfc->code = code;
	tfc->size = size;
	tfc->width = bitmap.width;
	tfc->bwidth = bitmap.cols;
	tfc->height = bitmap.rows;
	/*
	 * ascent must be derived from descent, to avoid number-rounding
	 * errors.
	 */
	tfc->descent =
		TTCEIL(metrics.bbox.yMax - metrics.bbox.yMin - metrics.bearingY) >> 6;
	tfc->ascent = bitmap.rows - tfc->descent;
	tfc->xoff = metrics.bearingX >> 6;
	tfc->fontname = strdup(fontname);
	tfc->charlen = metrics.advance >> 6;
	tfc->ref = 0;
	tfc->dbitmap = bitmap.bitmap;

	TFC_INSHASH(tfc);
	TFC_INSLRU(tfc);
	tfcachecnt++;

	return tfc;
}

static short
CharToUnicode(code, registry)
	u_int code;
	char *registry;
{
	TT_CharMap      char_map;
	unsigned short  i, n;
	unsigned short  platform, encoding;

	/* First, look for a Unicode charmap */
#ifdef HAVE_TT_FACE_PROPERTIES_CHARMAPS
	n = properties[tfcuridx].num_CharMaps;
#else
	n = TT_Get_CharMap_Count(face[tfcuridx]);
#endif

#if 0
	if (verbose) {
		fprintf(stderr, "%d charmaps for font %s code %04x\n",
			n, tfloadedfont[tfcuridx], code & 0xffff);
	}
#endif

	for (i = 0; i < n; i++) {
		TT_Get_CharMap_ID(face[tfcuridx], i, &platform, &encoding);
		if ((platform == 3 && encoding == 1)
		 || (platform == 0 && encoding == 0)) {
			TT_Get_CharMap(face[tfcuridx], i, &char_map);
#ifdef FREETYPE_CHARSET16
			if (code > 256 && strncmp(registry, "jisx0208.1983-", 14) != 0) {
				/*
				 * unicode_map assumes JIS X0208.
				 * it is not usable for other encodings.
				 * try the next CharMap available.
				 */
				continue;
			}
			if (code > 256)
				code = unicode_map[code];
#endif /* FREETYPE_CHARSET16 */
			return TT_Char_Index(char_map, (short) code);
		}
#ifdef FREETYPE_CHARSET16
		else {
			/*
			 * very adhoc processing for truetype fonts generated by cb2ttj.
			 */
			if ((platform == 3 && encoding == 2)
		 		|| (platform == 1 && encoding == 1)) {
					if (code < 256) continue;
					TT_Get_CharMap(face[tfcuridx], i, &char_map);
					return TT_Char_Index(char_map, (u_short) jistosjis(code));
			} else {
				if (platform == 1 && encoding == 0) {
					if (code > 256) continue;
					TT_Get_CharMap(face[tfcuridx], i, &char_map);
					return TT_Char_Index(char_map, (short) code);
				}
			}
		}
#endif
	}

	fprintf(stderr, "Sorry, but the fontfile \"%s\" doesn't "
		"contain any Unicode mapping table\n", tfloadedfont[tfcuridx]);
	return 0;
}

static TT_Error
LoadTrueTypeChar(int idx, int hint)
{
	int             flags;

	flags = TTLOAD_SCALE_GLYPH;
	if (hint)
		flags |= TTLOAD_HINT_GLYPH;

	return TT_Load_Glyph(instance[tfcuridx], glyph[tfcuridx], idx, flags);
}

/*
 * NOTE: this is upper-layer's responsibility to adjust the (bx, by)
 * so that the font bitmap fits into the given XImage.
 * see draw_onechar_tf() for the code.
 */
XImage *
tfc_image(tfc, fore, back, xim, bx, by)
	struct tfont *tfc;
	long fore, back;
	XImage *xim;
	int bx, by;
{
	int x, y;
	static XColor col[5];
	u_char d, *s;
	u_long p;
	XColor *bcol = NULL, *bc;
	u_int charlen;

	charlen = tfc->charlen;

	if (back == -1) {
		u_long r, g, b;
		int dots;

		dots = charlen * tfc->height;
		bcol = malloc(sizeof(XColor) * dots);
		if (bcol == NULL)
			return NULL;
		bc = bcol;
		for (y = 0; y < tfc->height; y++) {
			for (x = 0; x < charlen; x++, bc++) {
				bc->pixel = XGetPixel(xim, bx + tfc->xoff + x,
					by - tfc->ascent + y);
				bc->flags = DoRed|DoGreen|DoBlue;
			}
		}
		XQueryColors(display, colormap, bcol, dots);
		r = g = b = 0;
		for (y = 0; y < tfc->height; y++) {
			for (x = 0; x < charlen; x++) {
				r += bcol[x].red;
				g += bcol[x].green;
				b += bcol[x].blue;
			}
		}
		r /= dots; g /= dots; b /= dots;
		bc = &col[0];
		if (bc->red == r && bc->green == g && bc->blue == b)
			bc->pixel = back;
		else {
			bc->pixel = ~back;
			bc->red   = r; bc->green = g; bc->blue  = b;
		}
	}
	if (fore != col[4].pixel || back != col[0].pixel) {
		col[4].pixel = fore;
		col[4].flags = DoRed|DoGreen|DoBlue;
		if (back != -1) {
			col[3].pixel = back;
			col[3].flags = DoRed|DoGreen|DoBlue;
			XQueryColors(display, colormap, &col[3], 2);
			col[0] = col[3];
		} else {
			XQueryColor(display, colormap, &col[4]);
		}
		for (x = 3; x > 0; x--) {
			col[x].red   = (col[4].red  *x + col[0].red  *(4-x)) /4;
			col[x].green = (col[4].green*x + col[0].green*(4-x)) /4;
			col[x].blue  = (col[4].blue *x + col[0].blue *(4-x)) /4;
			if (!XAllocColor(display, colormap, &col[x])) {
				if (verbose)
					printf("tfc_image: cannot allocate color for level %d (using %d)\n", x, x + 1);
				col[x].pixel = col[x + 1].pixel;
			}
			else regist_alloc_colors(&font_clr, &col[x].pixel, 1);
		}
	}

	/* XXX: need optimization */
	s = tfc->dbitmap;
	bc = bcol;
	for (y = 0; y < tfc->height; y++) {
		for (x = 0; x < tfc->bwidth; x++) {
			d = *s++;
			if (d && x < tfc->width) {
				p = col[d].pixel;
				XPutPixel(xim, bx + tfc->xoff + x,
					by - tfc->ascent + y, p);
			}
		}
	}
	if (mgp_flag & FL_GLYPHEDGE) {
		/*
		 * for debugging treatment of font metrics, for 16bpp displays
		 */
		/* pixmap bounding box */
		for (y = 0; y < tfc->height; y++) {
			XPutPixel(xim, bx + tfc->xoff, by - tfc->ascent + y,
				0xffff);
			XPutPixel(xim, bx + tfc->xoff + tfc->width - 1,
				by - tfc->ascent + y, 0xffff);
		}
		for (x = 0; x < tfc->width; x++) {
			XPutPixel(xim, bx + tfc->xoff + x, by - tfc->ascent,
				0xffff);
			XPutPixel(xim, bx + tfc->xoff + x,
				by - tfc->ascent + tfc->height - 1, 0xffff);
		}
		/* origin */
		XPutPixel(xim, bx, by, 0xaaaa);
		/* baseline */
		for (x = 0; x < tfc->width; x++)
			XPutPixel(xim, bx + tfc->xoff + x, by, 0xaaaa);
	}
	if (bcol)
		free(bcol);
	return xim;
}

static short
jistosjis(code)
	u_int code;
{
	u_char c1 = code >> 8;
	u_char c2 = code % 256;
	int rowOffset = c1 < 95 ? 112: 176;
	int cellOffset = c1 % 2 ? (c2 > 95 ? 32 : 31) : 126;

	return (((c1 + 1) >> 1) + rowOffset) * 256 + c2 + cellOffset;
}


#endif /* FRRETYPE */

