/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

/***************************************************************************/
/*                                                                         */
/*  ahglobal.c                                                             */
/*                                                                         */
/*    Routines used to compute global metrics automatically (body).        */
/*                                                                         */
/*  Copyright 2000-2001, 2002 Catharon Productions Inc.                    */
/*  Author: David Turner                                                   */
/*                                                                         */
/*  This file is part of the Catharon Typography Project and shall only    */
/*  be used, modified, and distributed under the terms of the Catharon     */
/*  Open Source License that should come with this file under the name     */
/*  `CatharonLicense.txt'.  By continuing to use, modify, or distribute    */
/*  this file you indicate that you have read the license and              */
/*  understand and accept it fully.                                        */
/*                                                                         */
/*  Note that this license is compatible with the FreeType license.        */
/*                                                                         */
/***************************************************************************/


#include <ft2build.h>
#include "ags/lib/freetype-2.1.3/autohint/ahglobal.h"
#include "ags/lib/freetype-2.1.3/autohint/ahglyph.h"

#include "common/debug.h"

#define MAX_TEST_CHARACTERS  12

namespace AGS3 {
namespace FreeType213 {


static const char* blue_chars[AH_BLUE_MAX] = {
	"THEZOCQS",
	"HEZLOCUS",
	"xzroesc",
	"xzroesc",
	"pqgjy"
};

/* simple insertion sort */
static void sort_values(FT_Int count, FT_Pos *table) {
	FT_Int i, j;
	FT_Pos swap;

	for (i = 1; i < count; i++) {
		for (j = i; j > 0; j--) {
			if (table[j] > table[j - 1])
				break;

			swap = table[j];
			table[j] = table[j - 1];
			table[j - 1] = swap;
		}
	}
}

static FT_Error ah_hinter_compute_blues(AH_Hinter hinter) {
	AH_Blue blue;
	AH_Globals globals = &hinter->globals->design;
	FT_Pos flats[MAX_TEST_CHARACTERS];
	FT_Pos rounds[MAX_TEST_CHARACTERS];
	FT_Int num_flats;
	FT_Int num_rounds;

	FT_Face face;
	FT_GlyphSlot glyph;
	FT_Error error;
	FT_CharMap charmap;

	face = hinter->face;
	glyph = face->glyph;

	/* save current charmap */
	charmap = face->charmap;

	/* do we have a Unicode charmap in there? */
	error = FT_Select_Charmap(face, FT_ENCODING_UNICODE);
	if (error)
		goto Exit;

	/* we compute the blues simply by loading each character from the */
	/* 'blue_chars[blues]' string, then compute its top-most and      */
	/* bottom-most points                                             */

	debug(6, "blue zones computation");
	debug(6, "------------------------------------------------");

	for (blue = AH_BLUE_CAPITAL_TOP; blue < AH_BLUE_MAX; blue++) {
		const char *p = blue_chars[blue];
		const char *limit = p + MAX_TEST_CHARACTERS;
		FT_Pos *blue_ref, *blue_shoot;

		debugN(6, "blue %3d: ", blue);

		num_flats = 0;
		num_rounds = 0;

		for (; p < limit; p++) {
			FT_UInt glyph_index;
			FT_Vector *extremum;
			FT_Vector *points;
			FT_Vector *point_limit;
			FT_Vector *point;
			FT_Bool round;

			/* exit if we reach the end of the string */
			if (!*p)
				break;

			debugN(6, "`%c'", *p);

			/* load the character in the face -- skip unknown or empty ones */
			glyph_index = FT_Get_Char_Index(face, (FT_UInt)*p);
			if (glyph_index == 0)
				continue;

			error = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE);
			if (error || glyph->outline.n_points <= 0)
				continue;

			/* now compute min or max point indices and coordinates */
			points = glyph->outline.points;
			point_limit = points + glyph->outline.n_points;
			point = points;
			extremum = point;
			point++;

			if (AH_IS_TOP_BLUE(blue)) {
				for (; point < point_limit; point++)
					if (point->y > extremum->y)
						extremum = point;
			} else {
				for (; point < point_limit; point++)
					if (point->y < extremum->y)
						extremum = point;
			}

			debugN(6, "%5d", (int)extremum->y);

			/* now, check whether the point belongs to a straight or round  */
			/* segment; we first need to find in which contour the extremum */
			/* lies, then see its previous and next points                  */
			{
				FT_Int idx = (FT_Int)(extremum - points);
				FT_Int n;
				FT_Int first, last, prev, next, end;
				FT_Pos dist;

				last = -1;
				first = 0;

				for (n = 0; n < glyph->outline.n_contours; n++) {
					end = glyph->outline.contours[n];
					if (end >= idx) {
						last = end;
						break;
					}
					first = end + 1;
				}

				/* XXX: should never happen! */
				if (last < 0)
					continue;

				/* now look for the previous and next points that are not on the */
				/* same Y coordinate.  Threshold the `closeness'...              */

				prev = idx;
				next = prev;

				do {
					if (prev > first)
						prev--;
					else
						prev = last;

					dist = points[prev].y - extremum->y;
					if (dist < -5 || dist > 5)
						break;

				} while (prev != idx);

				do {
					if (next < last)
						next++;
					else
						next = first;

					dist = points[next].y - extremum->y;
					if (dist < -5 || dist > 5)
						break;

				} while (next != idx);

				/* now, set the `round' flag depending on the segment's kind */
				round = FT_BOOL(
					FT_CURVE_TAG(glyph->outline.tags[prev]) != FT_CURVE_TAG_ON ||
					FT_CURVE_TAG(glyph->outline.tags[next]) != FT_CURVE_TAG_ON);

				debugN(6, "%c ", round ? 'r' : 'f');
			}

			if (round)
				rounds[num_rounds++] = extremum->y;
			else
				flats[num_flats++] = extremum->y;
		}

		debugN(6, "\n");

		/* we have computed the contents of the `rounds' and `flats' tables, */
		/* now determine the reference and overshoot position of the blue;   */
		/* we simply take the median value after a simple short              */
		sort_values(num_rounds, rounds);
		sort_values(num_flats, flats);

		blue_ref = globals->blue_refs + blue;
		blue_shoot = globals->blue_shoots + blue;
		if (num_flats == 0 && num_rounds == 0) {
			*blue_ref = -10000;
			*blue_shoot = -10000;
		} else if (num_flats == 0) {
			*blue_ref =	*blue_shoot = rounds[num_rounds / 2];
		} else if (num_rounds == 0) {
			*blue_ref =	*blue_shoot = flats[num_flats / 2];
		} else {
			*blue_ref = flats[num_flats / 2];
			*blue_shoot = rounds[num_rounds / 2];
		}

		/* there are sometimes problems: if the overshoot position of top     */
		/* zones is under its reference position, or the opposite for bottom  */
		/* zones.  We must thus check everything there and correct the errors */
		if (*blue_shoot != *blue_ref) {
			FT_Pos ref = *blue_ref;
			FT_Pos shoot = *blue_shoot;
			FT_Bool over_ref = FT_BOOL(shoot > ref);

			if (AH_IS_TOP_BLUE(blue) ^ over_ref)
				*blue_shoot = *blue_ref = (shoot + ref) / 2;
		}

		debug(6, "-- ref = %ld, shoot = %ld", *blue_ref, *blue_shoot);
	}

	/* reset original face charmap */
	FT_Set_Charmap(face, charmap);
	error = 0;

Exit:
	return error;
}

static FT_Error ah_hinter_compute_widths(AH_Hinter hinter) {
	/* scan the array of segments in each direction */
	AH_Outline outline = hinter->glyph;
	AH_Segment segments;
	AH_Segment limit;
	AH_Globals globals = &hinter->globals->design;
	FT_Pos *widths;
	FT_Int dimension;
	FT_Int *p_num_widths;
	FT_Error error = 0;
	FT_Pos edge_distance_threshold = 32000;

	globals->num_widths = 0;
	globals->num_heights = 0;

	/* For now, compute the standard width and height from the `o'       */
	/* character.  I started computing the stem width of the `i' and the */
	/* stem height of the "-", but it wasn't too good.  Moreover, we now */
	/* have a single character that gives us standard width and height.  */
	{
		FT_UInt glyph_index;

		glyph_index = FT_Get_Char_Index(hinter->face, 'o');
		if (glyph_index == 0)
			return 0;

		error = FT_Load_Glyph(hinter->face, glyph_index, FT_LOAD_NO_SCALE);
		if (error)
			goto Exit;

		error = ah_outline_load(hinter->glyph, hinter->face);
		if (error)
			goto Exit;

		ah_outline_compute_segments(hinter->glyph);
		ah_outline_link_segments(hinter->glyph);
	}

	segments = outline->horz_segments;
	limit = segments + outline->num_hsegments;
	widths = globals->heights;
	p_num_widths = &globals->num_heights;

	for (dimension = 1; dimension >= 0; dimension--) {
		AH_Segment seg = segments;
		AH_Segment link;
		FT_Int num_widths = 0;

		for (; seg < limit; seg++) {
			link = seg->link;
			/* we only consider stem segments there! */
			if (link && link->link == seg && link > seg) {
				FT_Pos dist;

				dist = seg->pos - link->pos;
				if (dist < 0)
					dist = -dist;

				if (num_widths < AH_MAX_WIDTHS)
					widths[num_widths++] = dist;
			}
		}

		sort_values(num_widths, widths);
		*p_num_widths = num_widths;

		/* we will now try to find the smallest width */
		if (num_widths > 0 && widths[0] < edge_distance_threshold)
			edge_distance_threshold = widths[0];

		segments = outline->vert_segments;
		limit = segments + outline->num_vsegments;
		widths = globals->widths;
		p_num_widths = &globals->num_widths;
	}

	/* Now, compute the edge distance threshold as a fraction of the */
	/* smallest width in the font. Set it in `hinter.glyph' too!     */
	if (edge_distance_threshold == 32000)
		edge_distance_threshold = 50;

	/* let's try 20% */
	hinter->glyph->edge_distance_threshold = edge_distance_threshold / 5;

Exit:
	return error;
}

FT_Error ah_hinter_compute_globals(AH_Hinter hinter) {
	return ah_hinter_compute_widths(hinter) || ah_hinter_compute_blues(hinter);
}


} // End of namespace FreeType213
} // End of namespace AGS3
