/*
    Copyright (C) 2003 Nikolas Zimmermann <wildfox@kde.org>
    This file is part of the KDE project

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    aint with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include <math.h>

#include "myboost/shared_ptr.hpp"
#include <fontconfig/fontconfig.h>
#include <fribidi/fribidi.h>
#include <fribidi/fribidi_types.h>

#include "Font.h"
#include "Glyph.h"
#include "Tools.h"
#include "Rectangle.h"
#include "Converter.h"
#include "QtUnicode.h"
#include "GlyphTracer.h"

// Macros
#define FT_TRUNC(x)		((x) >> 6)
#define FT_TOFLOAT(x)	((x) * (1.0 / 64.0))
#define FT_FROMFLOAT(x)	((int) floor ((x) * 64.0 + 0.5))

const double deg2rad = 0.017453292519943295769; // pi/180

using namespace T2P;

// Font engine. The core component.
Converter::Converter(GlyphTracer *tracer) : m_glyphTracer(tracer)
{
	m_init = false;
	m_kerning = true;
	m_library = 0;
}

Converter::~Converter()
{
	if(m_glyphTracer)
		delete m_glyphTracer;

	m_fontCache.clear();
	m_glyphCache.clear();

	// Close FreeType2 library
	if(m_library)
		FT_Done_FreeType(m_library);
}

void Converter::init()
{
	// Initialize FreeType2
	m_init = (FT_Init_FreeType(&m_library) == 0);
}

bool Converter::ready()
{
	return m_init;
}

void Converter::setKerning(bool mode)
{
	m_kerning = mode;
}

// Lookup font in cache or create new font
SharedFont Converter::requestFont(const FontVisualParams *params)
{
	std::string cacheKey = cacheFontKey(params);
	SharedFont cached = m_fontCache.find(cacheKey);

	// If not available in cache, create new one and cache it :)
	if(cached)
	{
		delete params;
		return cached;
	}
	else
	{
		// Create a new Font
		SharedFont newFont(new Font(this));

		// Load the font
		if(!newFont->load(params))
		{
			delete params;
			return SharedFont();
		}

		// Append to cache
		m_fontCache.insert(cacheKey, newFont);
		return newFont;
	}
}

GlyphAffinePair *Converter::requestGlyph(GlyphRenderParams *params, Rectangle &bbox, Affine &affine, bool onlyLatin)
{
	// 1. Select glyph to have glyphIndex information, 
	// 	  needed to generate the cache lookup key
	selectGlyph(params);

	SharedGlyph cached = m_glyphCache.find(cacheGlyphKey(params));

	// If not available in cache, render new one and cache it :)
	// If we're mixing ie. japanese and latin characters (TTB layout),
	// then we also have to re-calculate the glyph again with the appropriate rotation matrix (Niko)
	if(!cached || !onlyLatin)
		cached = calcGlyph(params, affine, onlyLatin);

	// Correct bezier path by setting up the correct scaling matrix
	// and multiplying with the current glyph affine
	double fontSize = params->font()->fontParams()->size();

	T2P::Affine correctAffine;
	correctAffine.scale(0.001 * fontSize, -0.001 * fontSize);
	correctAffine *= affine;

	// Get bbox information
	Point p1(FT_TRUNC(cached->ftBbox()->xMin), FT_TRUNC(cached->ftBbox()->yMin));
	Point p2(FT_TRUNC(cached->ftBbox()->xMax), FT_TRUNC(cached->ftBbox()->yMax));

	bbox.setA(affine.mapPoint(p1));
	bbox.setB(affine.mapPoint(p2));

	return new GlyphAffinePair(cached.get(), correctAffine);
}

void Converter::selectGlyph(GlyphRenderParams *params)
{
	// 1. Select glyph + test if it exists
	//		|
	//		|-> if it doesn't exist, replace by a '?' and check if exists
	//		|-> if it does exist, use it!
	params->setGlyphIndex(FT_Get_Char_Index(params->font()->fontFace(), (FT_ULong) params->character()));

	if(params->glyphIndex() == 0)
		params->setGlyphIndex(FT_Get_Char_Index(params->font()->fontFace(), '?'));

	// 2. Load glyph into font's glyphSlot, according to the GlyphLayoutParams
	int flags = FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP;

	// 3. Don't pass FT_LOAD_VERTICAL_LAYOUT on TTB layouts when rendering
	// 	  a latin glyph because it needs to be rotated...
	if(params->layout()->tb())
	{
		Script script;
		SCRIPT_FOR_CHAR(script, params->character())
		if(script != Latin || params->layout()->glyphOrientationVertical() == 0)
			flags |= FT_LOAD_VERTICAL_LAYOUT;
	}

	FT_Error error = FT_Load_Glyph(params->font()->fontFace(), params->glyphIndex(), flags);
	if(error)
		params->setGlyphIndex(0);
}
	
SharedGlyph Converter::calcGlyph(const GlyphRenderParams *params, Affine &affine, bool onlyLatin)
{
	// 2. Apply kerning if needed
	if(m_kerning && params->lastGlyph() != 0 && params->glyphIndex() != 0)
	{
		FT_Vector kerningVector;
		double kx, ky;

		FT_Get_Kerning(params->font()->fontFace(), params->lastGlyph(), params->glyphIndex(), ft_kerning_default, &kerningVector);

		kx = FT_TRUNC(kerningVector.x);
		ky = FT_TRUNC(kerningVector.y);

		affine.dx() += kx + affine.m21() * ky;

		// Only apply y kerning in TB mode
		if(params->layout()->tb())
			affine.dy() += kx + affine.m22() * ky;
	}

	// 3. Prepare path tracing
	FT_OutlineGlyph ftGlyph;
	FT_Get_Glyph(params->font()->fontFace()->glyph, reinterpret_cast<FT_Glyph *>(&ftGlyph));

	// 3a. Prepare tracing matrix
	Affine traceAffine;
	traceAffine.scale(1000.0 / params->font()->fontFace()->units_per_EM);

	// 3b. Enable character rotation, if needed
	if(params->layout()->tb())
	{
		Script script;
		SCRIPT_FOR_CHAR(script, params->character())
		if(!onlyLatin && script == Latin)
		{
			FT_Matrix matrix;

			double angle = deg2rad * params->layout()->glyphOrientationVertical();
			matrix.xx = (FT_Fixed)( cos(angle) * 0x10000L);
			matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L);
			matrix.yx = (FT_Fixed)( sin(angle) * 0x10000L);
			matrix.yy = (FT_Fixed)( cos(angle) * 0x10000L);

			FT_Glyph_Transform(reinterpret_cast<FT_Glyph>(ftGlyph), &matrix, 0);
		}
	}

	// 4. Begin path tracing
	//    - Setup glyphOutlineing glyph
	//    - Start tracing
	//    - Close path
	FT_Outline *ftOut = &ftGlyph->outline;

	SharedGlyph glyphOutline(new Glyph());
	glyphOutline->setBezierPath(m_glyphTracer->allocBezierPath(ftOut->n_points * 2 + ftOut->n_contours + 1));
	glyphOutline->setAffine(traceAffine);

	FT_Outline_Decompose(ftOut, m_glyphTracer->outlineFuncs(), glyphOutline.get());

	m_glyphTracer->closePath(glyphOutline.get());

	// 5. Determine bounding box (aka "cbox")
	FT_Glyph_Get_CBox(reinterpret_cast<FT_Glyph>(ftGlyph), ft_glyph_bbox_unscaled, glyphOutline->ftBbox());

	// 6. Cache glyph!
	m_glyphCache.insert(cacheGlyphKey(params), glyphOutline);

	FT_Done_Glyph(reinterpret_cast<FT_Glyph>(ftGlyph));

	return glyphOutline;
}

GlyphSet *Converter::calcString(Font *font, const unsigned short *text, unsigned int length, Affine &affine, const GlyphLayoutParams *params, BezierPath *bpath)
{
	unsigned short *bidi = 0;

	// 0. Apply BiDi Rules
	if(params->useBidi())
	{
		FriBidiCharType baseDir = FRIBIDI_TYPE_N;
		FriBidiChar *unicodeIn = new FriBidiChar[length + 1];
		FriBidiChar *unicodeOut = new FriBidiChar[length + 1];
		bidi = new unsigned short[length + 1];	

		for(unsigned int i = 0; i < length; i++)
			unicodeIn[i] = text[i];
	
		fribidi_log2vis(unicodeIn, length, &baseDir, unicodeOut, 0, 0, 0); 
	
		for(unsigned int i = 0; i < length; i++)
			bidi[i] = unicodeOut[i];

		bidi[length] = 0;
		delete []unicodeIn;
		delete []unicodeOut;
	}
	else
		bidi = const_cast<unsigned short *>(text);

	// Detect character rotation if needed,
	// probably working in 0.1% of the cases :/
	Script script;	
	bool onlyLatin = true;
	for(unsigned int i = 0; i < length; i++)
	{
		SCRIPT_FOR_CHAR(script, bidi[i])
		if(script != Latin)
		{
			onlyLatin = false;
			break;
		}
	}

	// 1. Set initial font size
	double fontSize = font->fontParams()->size();
	FT_Set_Char_Size(font->fontFace(), FT_FROMFLOAT(fontSize), FT_FROMFLOAT(fontSize), 0, 0);

	// 2. Compute positioning metrics
	//	  - See http://www.freetype.org/freetype2/docs/tutorial/stepX.html (X = 1 or 2)
	int pixelHeight = (int) (FT_TOFLOAT(font->fontFace()->size->metrics.ascender - font->fontFace()->size->metrics.descender) * affine.m22());
	int pixelBaseline = (int) (FT_TOFLOAT(font->fontFace()->size->metrics.ascender) * affine.m22());
	int pixelUnderlinePosition = (int) (((font->fontFace()->ascender - font->fontFace()->underline_position - font->fontFace()->underline_thickness / 2) * fontSize / font->fontFace()->units_per_EM) * affine.m22());
	int pixelUnderlineThickness = T2PMAX(1, (int) ((font->fontFace()->underline_thickness * fontSize / font->fontFace()->units_per_EM) * affine.m22()));

	// 3. Prepare needed variables for the rendering loop
	// 	  - rendering params (layout, font...)
	// 	  - bounding box (per glyph, overall)
	// 	  - glyph matrix (overall)
	// 	  - resulting glyph sets
	GlyphRenderParams *renderParams = new GlyphRenderParams();
	renderParams->setFont(font);
	renderParams->setLayout(params);
	renderParams->setLastGlyph(0);
	renderParams->setGlyphIndex(0);

	Affine glyphAffine(affine);
	Rectangle bbox, currentBbox;
	GlyphSet *result = new GlyphSet();

	// Align to path start, if bpath != 0
	Point pathPoint, pathTangent, pathNormal;
	double pathLength = 0, pathAdvance = 0;
	double startOffset = params->textPathStartOffset();

	if(bpath)
	{
		pathLength = bpath->length();
		bpath->pointTangentNormalAt(startOffset, &pathPoint, &pathTangent);

		glyphAffine.dx() = pathPoint.x();
		glyphAffine.dy() = pathPoint.y();
		glyphAffine.rotate(atan2(pathTangent.y(), pathTangent.x()));

		std::cout << "[T2P] Starting textPath at: " << pathPoint.x() << ", " << pathPoint.y() << std::endl;
	}

	// <tspan> elements can have different baseline-shift's
	// baseline-shift support (Niko)
	double baseline = 0;
	if(!params->baselineShift().empty())
	{
		if(params->baselineShift() == "sub")
			baseline += fontSize / 5 + 1;
		else if(params->baselineShift() == "super")
			baseline -= fontSize / 3 + 1;
		else
		{
			std::string temp = params->baselineShift();
			if(temp.find("%") > 0)
				baseline -= (atof(temp.c_str()) / 100.0) * fontSize;
			else
				baseline -= atoi(temp.c_str());
		}

		if(!params->tb())
			glyphAffine.translate(0, baseline);
		else
			glyphAffine.translate(-baseline, 0);
	}

	// 4. Enter main rendering loop
	for(unsigned int i = 0; i < length; i++)
	{
		// 4a. Modify character to render
		renderParams->setCharacter(bidi[i]);

		// 4c. Get glyph outline
		GlyphAffinePair *glyphOutline = requestGlyph(renderParams, currentBbox, glyphAffine, onlyLatin);
		m_glyphTracer->correctGlyph(glyphOutline);

		// 4d. Save advance values
		result->m_glyphXAdvance.push_back((font->fontFace()->glyph->advance.x * fontSize / font->fontFace()->units_per_EM) * affine.m11());
		result->m_glyphYAdvance.push_back((font->fontFace()->glyph->advance.y * fontSize / font->fontFace()->units_per_EM) * affine.m22());

		// 4e. Misc
		//	   - union current + overall bounding box
		//	   - add glyph to list of glyphs
		//	   - apply letter & word spacing
		if(renderParams->glyphIndex() != 0)
		{
			// Remember last glyph for kerning
			renderParams->setLastGlyph(renderParams->glyphIndex());

			// Union current + overall bounding box
			bbox.bboxUnion(bbox, currentBbox);

			// Move glyph locations for the next glyph to be
			// rendered apply letter & word spacing
			bool addGlyph = true;
			if(!params->tb())
			{
				if(bpath)
				{
					double advance = startOffset + (pathAdvance / pathLength);
					if(advance < 0.0 || advance > 1.0) // dont render off-path glyphs
						addGlyph = false;

					pathAdvance += (result->m_glyphXAdvance.back() + params->letterSpacing() * affine.m11()) + ((bidi[(i + 1)] == ' ') ? params->wordSpacing() * affine.m11() : 0);

					std::cout << "[T2P] Adjusting textPath advance: " << pathAdvance << " Path Length: " << pathLength << " StartOffset: " << startOffset << " Position in Path (relative 0-1): " << pathAdvance/pathLength << " Adjusted by offset: " << startOffset + (pathAdvance / pathLength) << std::endl;

					bpath->pointTangentNormalAt(startOffset + (pathAdvance / pathLength), &pathPoint, &pathTangent, &pathNormal);

					glyphAffine.m11() = affine.m11();
					glyphAffine.m12() = affine.m12();
					glyphAffine.m21() = affine.m21();
					glyphAffine.m22() = affine.m22();
					glyphAffine.dx() = pathPoint.x();
					glyphAffine.dy() = pathPoint.y();

					glyphAffine.rotateAround(atan2(pathTangent.y(), pathTangent.x()), Point(result->m_glyphXAdvance.back() / 2, result->m_glyphYAdvance.back() / 2)); 
		
					std::cout << "[T2P] Aligning textPath to: " << pathPoint.x() << ", " << pathPoint.y() << std::endl;
					if(!params->tb())
						glyphAffine.translate(0, baseline);
					else
						glyphAffine.translate(-baseline, 0);
				}
				else
					glyphAffine.dx() += (result->m_glyphXAdvance.back() + params->letterSpacing() * affine.m11()) + ((bidi[(i + 1)] == ' ') ? params->wordSpacing() * affine.m11() : 0);
			}
			else
			{
				double advy = result->m_glyphYAdvance.back();

				Script script;
				SCRIPT_FOR_CHAR(script, bidi[i])
				if(!onlyLatin && script == Latin && params->glyphOrientationVertical() != 0)
					advy = result->m_glyphXAdvance.back();

				glyphAffine.dy() += (advy + params->letterSpacing() * affine.m22()) + ((bidi[(i + 1)] == ' ') ? params->wordSpacing() * affine.m22() : 0);
			}

			// Copy bezier path/affine pair
			if(addGlyph)
			{
				result->m_set.push_back(glyphOutline);
				result->m_glyphCount++;
			}
			else
				delete glyphOutline;
		}
	}

	// 5. Fill out resulting data structures
	result->m_underlinePosition = pixelUnderlinePosition;
	result->m_overlinePosition = pixelHeight - pixelUnderlinePosition;
	result->m_strikeThroughPosition = (result->m_underlinePosition + result->m_overlinePosition) / 2;
	result->m_pixelBaseline = pixelBaseline;
	result->m_underlineThickness = pixelUnderlineThickness;

	result->m_xpen = glyphAffine.dx() - affine.dx();
	result->m_ypen = glyphAffine.dy() - affine.dy();

	result->m_bboxX = T2PMAX(1, int(bbox.a().x()));
	result->m_bboxY = T2PMAX(1, int(bbox.a().y() - int(pixelHeight / 2)));
	result->m_height = T2PMAX(1, int(bbox.b().y() - bbox.a().y()));

	// Correct bounding box information also on
	// vertical layouts! (Niko)
	if(!params->tb())
	{
		if(bpath)
			result->m_width = int(pathAdvance);
		else
			result->m_width = T2PMAX(1, int(bbox.b().x() - bbox.a().x()));
	}
	else
		result->m_width = T2PMAX(1, pixelHeight);

	// 6. Cleanup memory
	if(text != bidi)
		delete []bidi;

	delete renderParams;

	// 7. Eventually, dump cache statistics
	// m_glyphCache.dump();
	// m_fontCache.dump();

	return result;
}

std::string Converter::cacheFontKey(const FontVisualParams *params) const
{
	// TODO: Tune the key
	std::string key;

	key += Tools::joinList('|', const_cast<FontVisualParams *>(params)->fontList());
	key += Tools::a2str(params->weight());
	key += Tools::a2str(params->slant());
	key += Tools::a2str(params->size());

	// std::cout << "Font cache key: " << key << std::endl;
	return key;
}

std::string Converter::cacheGlyphKey(const GlyphRenderParams *params) const
{
	// TODO: Tune the key
	std::string key;

	key += params->font()->fontFile();
	key += Tools::a2str(params->character());
	key += Tools::a2str(params->glyphIndex());
	key += Tools::a2str(params->font()->fontParams()->size());
	key += Tools::a2str(params->font()->fontParams()->weight());
	key += Tools::a2str(params->font()->fontParams()->slant());

	// std::cout << "Glyph cache key: " << key << std::endl;
	return key;
}
// vim:ts=4:noet
