/*
 * Copyright (C) 2000, Matias Atria
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <config.h>
#include <stdlib.h>

#include "mdvi.h"
#include "private.h"

static ListHead fontlist;

extern char *_mdvi_fallback_font;

extern void vf_free_macros(DviFont *);

#define finfo	search.info
#define TYPENAME(font)	\
	((font)->finfo ? (font)->finfo->name : "none")

int	font_reopen(DviFont *font)
{
	if(font->in)
		fseek(font->in, (long)0, SEEK_SET);
	else if((font->in = fopen(font->filename, "rb")) == NULL) {
		DEBUG((DBG_FILES, "reopen(%s) -> Error\n", font->filename));
		return -1;
	}
	DEBUG((DBG_FILES, "reopen(%s) -> Ok.\n", font->filename));
	return 0;
}

/* used from context: params and device */
static int load_font_file(DviParams *params, DviFont *font)
{
	int	status;

	if(SEARCH_DONE(font->search))
		return -1;
	if(font->in == NULL && font_reopen(font) < 0)
		return -1;
	DEBUG((DBG_FONTS, "%s: loading %s font from `%s'\n",
		font->fontname,
		font->finfo->name, font->filename));
	do {
		status = font->finfo->load(params, font);
	} while(status < 0 && mdvi_font_retry(params, font) == 0);
	if(status < 0)
		return -1;
	if(font->in) {
		fclose(font->in);
		font->in = NULL;
	}
	DEBUG((DBG_FONTS, "reload_font(%s) -> %s\n",
		font->fontname, status < 0 ? "Error" : "Ok"));
	return 0;
}

void	font_drop_one(DviFontRef *ref)
{
	DviFont *font;

	font = ref->ref;
	mdvi_free(ref);
	/* drop all children */
	for(ref = font->subfonts; ref; ref = ref->next) {
		/* just adjust the reference counts */
		ref->ref->links--;
	}
	if(--font->links == 0) {
		/*
		 * this font doesn't have any more references, but
		 * we still keep it around in case a virtual font
		 * requests it.
		 */
		if(font->in) {
			fclose(font->in);
			font->in = NULL;
		}
		if(LIST(font) != fontlist.tail) {
			/* move it to the end of the list */
			listh_remove(&fontlist, LIST(font));
			listh_append(&fontlist, LIST(font));
		}
	}
	DEBUG((DBG_FONTS, "%s: reference dropped, %d more left\n",
		font->fontname, font->links));
}

void	font_drop_chain(DviFontRef *head)
{
	DviFontRef *ptr;

	for(; (ptr = head); ) {
		head = ptr->next;
		font_drop_one(ptr);
	}
}

int	font_free_unused(DviDevice *dev)
{
	DviFont	*font, *next;
	int	count = 0;

	DEBUG((DBG_FONTS, "destroying unused fonts\n"));
	for(font = (DviFont *)fontlist.head; font; font = next) {
		DviFontRef *ref;

		next = font->next;
		if(font->links)
			continue;
		count++;
		DEBUG((DBG_FONTS, "removing unused %s font `%s'\n",
			TYPENAME(font), font->fontname));
		listh_remove(&fontlist, LIST(font));
		if(font->in)
			fclose(font->in);
		/* get rid of subfonts (but can't use `drop_chain' here) */
		for(; (ref = font->subfonts); ) {
			font->subfonts = ref->next;
			mdvi_free(ref);
		}
		/* remove this font */
		font_reset_font_glyphs(dev, font, MDVI_FONTSEL_GLYPH);
		/* let the font destroy its private data */
		if(font->finfo->freedata)
			font->finfo->freedata(font);
		/* destroy characters */
		if(font->chars)
			mdvi_free(font->chars);
		mdvi_free(font->fontname);
		mdvi_free(font->filename);
		mdvi_free(font);
	}
	DEBUG((DBG_FONTS, "%d unused fonts removed\n", count));
	return count;
}

/* used from context: params and device */
DviFontRef *
font_reference(
	DviParams *params, 	/* rendering parameters */
	Int32 id, 		/* external id number */
	const char *name, 	/* font name */
	Int32 sum, 		/* checksum (from DVI of VF) */
	int hdpi, 		/* resolution */
	int vdpi,
	Int32 scale)		/* scaling factor (from DVI or VF) */
{
	DviFont	*font;
	DviFontRef *ref;
	DviFontRef *subfont_ref;

	/* see if there is a font with the same characteristics */
	for(font = (DviFont *)fontlist.head; font; font = font->next) {
		if(strcmp(name, font->fontname) == 0
		   && (!sum || !font->checksum || font->checksum == sum)
		   && font->hdpi == hdpi
		   && font->vdpi == vdpi
		   && font->scale == scale)
		   	break;
	}
	/* try to load the font */
	if(font == NULL) {
		font = mdvi_add_font(name, sum, hdpi, vdpi, scale);
		if(font == NULL)
			return NULL;
		listh_append(&fontlist, LIST(font));
	}
	if(!font->links && !font->chars && load_font_file(params, font) < 0) {
		DEBUG((DBG_FONTS, "font_reference(%s) -> Error\n", name));
		return NULL;
	}
	ref = xalloc(DviFontRef);
	ref->ref = font;

	font->links++;
	for(subfont_ref = font->subfonts; subfont_ref; subfont_ref = subfont_ref->next) {
		/* just adjust the reference counts */
		subfont_ref->ref->links++;
	}

	ref->fontid = id;

	if(LIST(font) != fontlist.head) {
		listh_remove(&fontlist, LIST(font));
		listh_prepend(&fontlist, LIST(font));
	}

	DEBUG((DBG_FONTS, "font_reference(%s) -> %d links\n",
		font->fontname, font->links));
	return ref;
}

void	font_transform_glyph(DviOrientation orient, DviGlyph *g)
{
	BITMAP	*map;
	int	x, y;

	map = (BITMAP *)g->data;
	if(MDVI_GLYPH_ISEMPTY(map))
		map = NULL;

	/* put the glyph in the right orientation */
	switch(orient) {
	case MDVI_ORIENT_TBLR:
		break;
	case MDVI_ORIENT_TBRL:
		g->x = g->w - g->x;
		if(map) bitmap_flip_horizontally(map);
		break;
	case MDVI_ORIENT_BTLR:
		g->y = g->h - g->y;
		if(map) bitmap_flip_vertically(map);
		break;
	case MDVI_ORIENT_BTRL:
		g->x = g->w - g->x;
		g->y = g->h - g->y;
		if(map) bitmap_flip_diagonally(map);
		break;
	case MDVI_ORIENT_RP90:
		if(map) bitmap_rotate_counter_clockwise(map);
		y = g->y;
		x = g->w - g->x;
		g->x = y;
		g->y = x;
		SWAPINT(g->w, g->h);
		break;
	case MDVI_ORIENT_RM90:
		if(map) bitmap_rotate_clockwise(map);
		y = g->h - g->y;
		x = g->x;
		g->x = y;
		g->y = x;
		SWAPINT(g->w, g->h);
		break;
	case MDVI_ORIENT_IRP90:
		if(map) bitmap_flip_rotate_counter_clockwise(map);
		y = g->y;
		x = g->x;
		g->x = y;
		g->y = x;
		SWAPINT(g->w, g->h);
		break;
	case MDVI_ORIENT_IRM90:
		if(map) bitmap_flip_rotate_clockwise(map);
		y = g->h - g->y;
		x = g->w - g->x;
		g->x = y;
		g->y = x;
		SWAPINT(g->w, g->h);
		break;
	}
}

static int load_one_glyph(DviContext *dvi, DviFont *font, int code)
{
	BITMAP *map;
	DviFontChar *ch;
	int	status;

#ifndef NODEBUG
	ch = FONTCHAR(font, code);
	DEBUG((DBG_GLYPHS, "loading glyph code %d in %s (at %u)\n",
		code, font->fontname, ch->offset));
#endif
	if(font->finfo->getglyph == NULL) {
		/* font type does not need to load glyphs (e.g. vf) */
		return 0;
	}

	status = font->finfo->getglyph(&dvi->params, font, code);
	if(status < 0)
		return -1;
	/* get the glyph again (font->chars may have changed) */
	ch = FONTCHAR(font, code);
#ifndef NODEBUG
	map = (BITMAP *)ch->glyph.data;
	if(DEBUGGING(BITMAP_DATA)) {
		DEBUG((DBG_BITMAP_DATA,
			"%s: new %s bitmap for character %d:\n",
			font->fontname, TYPENAME(font), code));
		if(MDVI_GLYPH_ISEMPTY(map))
			DEBUG((DBG_BITMAP_DATA, "blank bitmap\n"));
		else
			bitmap_print(stderr, map);
	}
#endif
	/* check if we have to scale it */
	if(!font->finfo->scalable && font->hdpi != font->vdpi) {
		int	hs, vs, d;

		/* we scale it ourselves */
		d = Max(font->hdpi, font->vdpi);
		hs = d / font->hdpi;
		vs = d / font->vdpi;
		if(ch->width && ch->height && (hs > 1 || vs > 1)) {
			int	h, v;
			DviGlyph glyph;

			DEBUG((DBG_FONTS,
				"%s: scaling glyph %d to resolution %dx%d\n",
				font->fontname, code, font->hdpi, font->vdpi));
			h = dvi->params.hshrink;
			v = dvi->params.vshrink;
			d = dvi->params.density;
			dvi->params.hshrink = hs;
			dvi->params.vshrink = vs;
			dvi->params.density = 50;
			/* shrink it */
			font->finfo->shrink0(dvi, font, ch, &glyph);
			/* restore parameters */
			dvi->params.hshrink = h;
			dvi->params.vshrink = v;
			dvi->params.density = d;
			/* update glyph data */
			if(!MDVI_GLYPH_ISEMPTY(ch->glyph.data))
				bitmap_destroy((BITMAP *)ch->glyph.data);
			ch->glyph.data = glyph.data;
			ch->glyph.x = glyph.x;
			ch->glyph.y = glyph.y;
			ch->glyph.w = glyph.w;
			ch->glyph.h = glyph.h;
		}

	}
	font_transform_glyph(dvi->params.orientation, &ch->glyph);

	return 0;
}

DviFontChar *font_get_glyph(DviContext *dvi, DviFont *font, int code)
{
	DviFontChar *ch;

again:
	/* if we have not loaded the font yet, do so now */
	if(!font->chars && load_font_file(&dvi->params, font) < 0)
		return NULL;

	/* get the unscaled glyph, maybe loading it from disk */
	ch = FONTCHAR(font, code);
	if(!ch || !glyph_present(ch))
		return NULL;
	if(!ch->loaded && load_one_glyph(dvi, font, code) == -1) {
		if(font->chars == NULL) {
			/* we need to try another font class */
			goto again;
		}
		return NULL;
	}
	/* yes, we have to do this again */
	ch = FONTCHAR(font, code);

	/* Got the glyph. If we also have the right scaled glyph, do no more */
	if(!ch->width || !ch->height ||
	   font->finfo->getglyph == NULL ||
	   (dvi->params.hshrink == 1 && dvi->params.vshrink == 1))
		return ch;

	/* If the glyph is empty, we just need to shrink the box */
	if(ch->missing || MDVI_GLYPH_ISEMPTY(ch->glyph.data)) {
		if(MDVI_GLYPH_UNSET(ch->shrunk.data))
			mdvi_shrink_box(dvi, font, ch, &ch->shrunk);
		return ch;
	} else if(MDVI_ENABLED(dvi, MDVI_PARAM_ANTIALIASED)) {
		if(ch->grey.data &&
		   !MDVI_GLYPH_ISEMPTY(ch->grey.data) &&
		   ch->fg == dvi->curr_fg &&
		   ch->bg == dvi->curr_bg)
		   	return ch;
		if(ch->grey.data &&
		   !MDVI_GLYPH_ISEMPTY(ch->grey.data)) {
			if(dvi->device.free_image)
				dvi->device.free_image(ch->grey.data);
			ch->grey.data = NULL;
		}
		font->finfo->shrink1(dvi, font, ch, &ch->grey);
	} else if(!ch->shrunk.data)
		font->finfo->shrink0(dvi, font, ch, &ch->shrunk);

	return ch;
}

void	font_reset_one_glyph(DviDevice *dev, DviFontChar *ch, int what)
{
	if(!glyph_present(ch))
		return;
	if(what & MDVI_FONTSEL_BITMAP) {
		if(MDVI_GLYPH_NONEMPTY(ch->shrunk.data))
			bitmap_destroy((BITMAP *)ch->shrunk.data);
		ch->shrunk.data = NULL;
	}
	if(what & MDVI_FONTSEL_GREY) {
		if(MDVI_GLYPH_NONEMPTY(ch->grey.data)) {
			if(dev->free_image)
				dev->free_image(ch->grey.data);
		}
		ch->grey.data = NULL;
	}
	if(what & MDVI_FONTSEL_GLYPH) {
		if(MDVI_GLYPH_NONEMPTY(ch->glyph.data))
			bitmap_destroy((BITMAP *)ch->glyph.data);
		ch->glyph.data = NULL;
		ch->loaded = 0;
	}
}

void	font_reset_font_glyphs(DviDevice *dev, DviFont *font, int what)
{
	int	i;
	DviFontChar *ch;

	if(what & MDVI_FONTSEL_GLYPH)
		what |= MDVI_FONTSEL_BITMAP|MDVI_FONTSEL_GREY;
	if(font->subfonts) {
		DviFontRef *ref;

		for(ref = font->subfonts; ref; ref = ref->next)
			font_reset_font_glyphs(dev, ref->ref, what);
	}
	if(font->in) {
		DEBUG((DBG_FILES, "close(%s)\n", font->filename));
		fclose(font->in);
		font->in = NULL;
	}
	if(font->finfo->getglyph == NULL)
		return;
	DEBUG((DBG_FONTS, "resetting glyphs in font `%s'\n", font->fontname));
	for(ch = font->chars, i = font->loc; i <= font->hic; ch++, i++) {
		if(glyph_present(ch))
			font_reset_one_glyph(dev, ch, what);
	}
	if((what & MDVI_FONTSEL_GLYPH) && font->finfo->reset)
		font->finfo->reset(font);
}

void	font_reset_chain_glyphs(DviDevice *dev, DviFontRef *head, int what)
{
	DviFontRef *ref;

	for(ref = head; ref; ref = ref->next)
		font_reset_font_glyphs(dev, ref->ref, what);
}

static int compare_refs(const void *p1, const void *p2)
{
	return ((*(DviFontRef **)p1)->fontid - (*(DviFontRef **)p2)->fontid);
}

void	font_finish_definitions(DviContext *dvi)
{
	int	count;
	DviFontRef **map, *ref;

	/* first get rid of unused fonts */
	font_free_unused(&dvi->device);

	if(dvi->fonts == NULL) {
		mdvi_warning(_("%s: no fonts defined\n"), dvi->filename);
		return;
	}
	map = xnalloc(DviFontRef *, dvi->nfonts);
	for(count = 0, ref = dvi->fonts; ref; ref = ref->next)
		map[count++] = ref;
	/* sort the array by font id */
	qsort(map, dvi->nfonts, sizeof(DviFontRef *), compare_refs);
	dvi->fontmap = map;
}

DviFontRef *font_find_flat(DviContext *dvi, Int32 id)
{
	DviFontRef *ref;

	for(ref = dvi->fonts; ref; ref = ref->next)
		if(ref->fontid == id)
			break;
	return ref;
}

DviFontRef *font_find_mapped(DviContext *dvi, Int32 id)
{
	int	lo, hi, n;
	DviFontRef **map;

	/* do a binary search */
	lo = 0; hi = dvi->nfonts;
	map = dvi->fontmap;
	while(lo < hi) {
		int	sign;

		n = (hi + lo) >> 1;
		sign = (map[n]->fontid - id);
		if(sign == 0)
			break;
		else if(sign < 0)
			lo = n;
		else
			hi = n;
	}
	if(lo >= hi)
		return NULL;
	return map[n];
}

