/* This file is part of the KDE project
   Copyright (C) 2002, The Karbon Developers

   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
   along 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 <tqdom.h>
#include <tqfile.h>

#include <kdebug.h>
#include <KoPoint.h>
#include <KoRect.h>

#include "vpath.h"
#include "vtext.h"
#include "vtext_iface.h"
#include "vstroke.h"
#include "vfill.h"
#include "vvisitor.h"
#include "vsegment.h"
#include "vgroup.h"
#include "vpainter.h"
#include "commands/vtransformcmd.h"

#ifdef HAVE_KARBONTEXT

#include <ft2build.h>
#include <fontconfig/fontconfig.h>


#include FT_FREETYPE_H
#include FT_OUTLINE_H
#include FT_GLYPH_H

#define FT_TOFLOAT(x) ((x) * (1.0 / 64.0))
#define FT_FROMFLOAT(x) ((int) floor ((x) * 64.0 + 0.5))


// Trace routines for ttf / ps font -> VSubpath

int traceMoveto( FT_Vector *to, VPath *composite )
{
	double tox = ( to->x / 64.0 );
	double toy = ( -to->y / 64.0 );

	//TQString add = "M" + TQString::number(tox) + "," + TQString::number(toy) + " ";
	//kdDebug(38000) << add.latin1() << endl;
	composite->moveTo( KoPoint( tox, toy ) );

	return 0;
}

int traceLineto( FT_Vector *to, VPath *composite )
{
	double tox = ( to->x / 64.0 );
	double toy = ( -to->y / 64.0 );

	//TQString add = "L" + TQString::number(tox) + "," + TQString::number(toy) + " ";
	//kdDebug(38000) << add.latin1() << endl;

	composite->lineTo( KoPoint( tox, toy ) );

	return 0;
}

int traceQuadraticBezier( FT_Vector *control, FT_Vector *to, VPath *composite )
{
	double x1 = ( control->x / 64.0 );
	double y1 = ( -control->y / 64.0 );
	double x2 = ( to->x / 64.0 );
	double y2 = ( -to->y / 64.0 );

	//TQString add = "Q" + TQString::number(x1) + "," + TQString::number(y1) + "," + TQString::number(x2) + "," + TQString::number(y2) + " ";
	//kdDebug(38000) << add.latin1() << endl;
	composite->curveTo( KoPoint( x1, y1 ), KoPoint( x2, y2 ), KoPoint( x2, y2 ) );
	//composite->curve2To( KoPoint( x1, y1 ), KoPoint( x2, y2 ) );

	return 0;
}

int traceCubicBezier( FT_Vector *p, FT_Vector *q, FT_Vector *to, VPath *composite )
{
	double x1 = ( p->x / 64.0 );
	double y1 = ( -p->y / 64.0 );
	double x2 = ( q->x / 64.0 );
	double y2 = ( -q->y / 64.0 );
	double x3 = ( to->x / 64.0 );
	double y3 = ( -to->y / 64.0 );

	//TQString add = "C" + TQString::number(x1) + "," + TQString::number(y1) + "," + TQString::number(x2) + "," + TQString::number(y2) + "," + TQString::number(x3) + "," + TQString::number(y3);
	//kdDebug(38000) << add.latin1() << endl;

	composite->curveTo( KoPoint( x1, y1 ), KoPoint( x2, y2 ), KoPoint( x3, y3 ) );

	return 0;
}

FT_Outline_Funcs OutlineMethods =
{
	(FT_Outline_MoveTo_Func) traceMoveto,
	(FT_Outline_LineTo_Func) traceLineto,
	(FT_Outline_ConicTo_Func) traceQuadraticBezier,
	(FT_Outline_CubicTo_Func) traceCubicBezier,
	0,
	0
};

#endif // HAVE_KARBONTEXT

VText::VText( VObject* parent, VState state )
	: VObject( parent, state ), m_basePath( 0L )
{
	m_glyphs.setAutoDelete( true );
	m_boundingBoxIsInvalid = true;
	m_stroke = new VStroke( this );
	m_fill = new VFill();
	m_position			= (VText::Position)0;
	m_alignment			= (VText::Alignment)0;
	m_shadow			= false;
	m_translucentShadow	= false;
	m_shadowAngle		= 0;
	m_shadowDistance	= 0;
	m_offset			= 0.0;
}


VText::VText( const TQFont &font, const VSubpath& basePath, Position position, Alignment alignment, const TQString& text )
	: VObject( 0L ), m_font( font ), m_basePath( basePath ), m_position( position ), m_alignment( alignment ), m_text( text )
{
	m_glyphs.setAutoDelete( true );
	m_boundingBoxIsInvalid = true;
	m_stroke = new VStroke( this );
	m_fill = new VFill();
	m_offset = 0.0;
}

VText::VText( const VText& text )
	: VObject( text ), m_font( text.m_font ), m_basePath( text.m_basePath ), m_position( text.m_position ), m_alignment( text.m_alignment ), m_text( text.m_text ), m_shadow( text.m_shadow ), m_translucentShadow( text.m_translucentShadow ), m_shadowDistance( text.m_shadowDistance ), m_shadowAngle( text.m_shadowAngle ), m_offset( text.m_offset )
{
	m_stroke = new VStroke( *text.m_stroke );
	m_stroke->setParent( this );
	m_fill = new VFill( *text.m_fill );

	// copy glyphs
	VPathListIterator itr( text.m_glyphs );
	for( ; itr.current() ; ++itr )
	{
		VPath* c = itr.current()->clone();
		c->setParent( this );
		m_glyphs.append( c );
	}

	m_boundingBoxIsInvalid = true;
}

VText::~VText()
{
}

DCOPObject* VText::dcopObject()
{
	if( !m_dcop )
		m_dcop = new VTextIface( this );

	return m_dcop;
}


void
VText::draw( VPainter* painter, const KoRect* /*rect*/ ) const
{
	if(
		state() == deleted ||
		state() == hidden ||
		state() == hidden_locked )
	{
		return;
	}

	painter->save();

	VPathListIterator itr( m_glyphs );

	if( state() != edit )
	{
		// paint fill:
		painter->newPath();

		if ( m_shadow )
		{
			VColor color;
			if ( m_translucentShadow )
			{
				color.set( 0., 0., 0. );
				color.setOpacity( .3 );
			}
			else
			{
				color.set( .3, .3, .3 );
				color.setOpacity( 1. );
			}
			int shadowDx = int( m_shadowDistance * cos( m_shadowAngle / 360. * 6.2832 ) );
			int shadowDy = int( m_shadowDistance * sin( m_shadowAngle / 360. * 6.2832 ) );

			VTransformCmd trafo( 0L, TQWMatrix() );
			for( itr.toFirst(); itr.current(); ++itr )
			{
				trafo.setMatrix( TQWMatrix( 1, 0, 0, 1, shadowDx, shadowDy ) );
				trafo.visit( *( itr.current() ) );
				itr.current()->setFill( VFill( color ) );
				itr.current()->setStroke( VStroke( color ) );
				itr.current()->draw( painter );
				trafo.setMatrix( TQWMatrix( 1, 0, 0, 1, -shadowDx, -shadowDy ) );
				trafo.visit( *( itr.current() ) );
			}
		}

		for( itr.toFirst(); itr.current(); ++itr )
		{
			itr.current()->setFill( *m_fill );
			itr.current()->setStroke( *m_stroke );
			itr.current()->draw( painter );
		}
	}

	// draw simplistic contour:
	if( state() == edit )//|| state() == selected )
	{
		painter->newPath();
		painter->setRasterOp( TQt::XorROP );
		painter->setPen( TQt::yellow );
		painter->setBrush( TQt::NoBrush );

		for( itr.toFirst(); itr.current(); ++itr )
			itr.current()->draw( painter );

		painter->strokePath();
	}

	painter->restore();
}

const KoRect&
VText::boundingBox() const
{
	if( m_boundingBoxIsInvalid )
	{
		VPathListIterator itr( m_glyphs );
		itr.toFirst();
		// clear:
		m_boundingBox = itr.current() ? itr.current()->boundingBox() : KoRect();
		for( ++itr; itr.current(); ++itr )
			if( !itr.current()->boundingBox().isEmpty() )
				m_boundingBox |= itr.current()->boundingBox();

		// take line width into account:
		m_boundingBox.setCoords(
			m_boundingBox.left()   - 0.5 * stroke()->lineWidth(),
			m_boundingBox.top()    - 0.5 * stroke()->lineWidth(),
			m_boundingBox.right()  + 0.5 * stroke()->lineWidth(),
			m_boundingBox.bottom() + 0.5 * stroke()->lineWidth() );

		m_boundingBoxIsInvalid = false;
	}

	return m_boundingBox;
}

VText*
VText::clone() const
{
	return new VText( *this );
}

VGroup* VText::toVGroup() const
{
	VGroup* group = new VGroup( parent() );

	VPathListIterator itr( m_glyphs );
	for( itr.toFirst(); itr.current(); ++itr )
	{
		VPath* c = itr.current()->clone();
		c->setParent( group );
		group->append( c );
	}

	group->setFill( *m_fill );
	group->setStroke( *m_stroke );

	return group;
} // VText::toVGroup

void
VText::save( TQDomElement& element ) const
{
	if( state() != deleted )
	{
		TQDomElement me = element.ownerDocument().createElement( "TEXT" );

		VPath path( 0L );
		path.combinePath( m_basePath );
		path.save( me );

		VObject::save( me );

		// save font properties
		me.setAttribute( "text",				m_text );
		me.setAttribute( "family",				m_font.family() );
		me.setAttribute( "size",				m_font.pointSize() );
		me.setAttribute( "italic",				m_font.italic() );
		me.setAttribute( "bold",				m_font.bold() );
		me.setAttribute( "position",			m_position );
		me.setAttribute( "alignment",			m_alignment );
		me.setAttribute( "shadow",				m_shadow );
		me.setAttribute( "translucentshadow",	m_translucentShadow );
		me.setAttribute( "shadowangle",			m_shadowAngle );
		me.setAttribute( "shadowdist",			m_shadowDistance );
		me.setAttribute( "offset",				m_offset );
		element.appendChild( me );

		// save all glyphs / paths
		VPathListIterator itr = m_glyphs;
		for( itr.toFirst(); itr.current(); ++itr )
			itr.current()->save( me );
	}
}

void
VText::load( const TQDomElement& element )
{
	m_glyphs.clear();

	m_font.setFamily( element.attribute( "family", "Times" ) );
	m_font.setPointSize( element.attribute( "size", "12" ).toInt() );
	m_font.setItalic( element.attribute( "italic" ).toInt() == 1 );
	m_font.setWeight( TQFont::Normal );
	m_font.setBold( element.attribute( "bold" ).toInt() == 1 );
	m_position			= (Position)element.attribute( "position", "0" ).toInt();
	m_alignment			= (Alignment)element.attribute( "alignment", "0" ).toInt();
	m_shadow			= ( element.attribute( "shadow" ).toInt() == 1 );
	m_translucentShadow	= ( element.attribute( "translucentshadow" ).toInt() == 1 );
	m_shadowAngle		= element.attribute( "shadowangle" ).toInt();
	m_shadowDistance	= element.attribute( "shadowdist" ).toInt();
	m_offset			= element.attribute( "offset" ).toDouble();
	m_text = element.attribute( "text", "" );

	VObject::load( element );

	TQDomNodeList list = element.childNodes();
	TQDomElement e = list.item( 0 ).toElement();
	
	// element to start with reading glyph paths and stroke, fill, etc.
	uint startElement = 0;

	if( e.tagName() == "PATH" )
	{
		VPath path( 0L );
		path.load( e );
		m_basePath = *path.paths().getFirst();
		startElement++;
	}

	// load text glyphs:
	for( uint i = startElement; i < list.count(); ++i )
	{
		if( list.item( i ).isElement() )
		{
			e = list.item( i ).toElement();
			if( e.tagName() == "PATH" )
			{
				VPath *composite = new VPath( this );
				composite->load( e );
				m_glyphs.append( composite );
			}
			if( e.tagName() == "STROKE" )
				m_stroke->load( e );
			if( e.tagName() == "FILL" )
				m_fill->load( e );
		}
	}
	// if no glyphs yet, trace them
#ifdef HAVE_KARBONTEXT
	if( m_glyphs.count() == 0 )
		traceText();
#endif
	m_boundingBoxIsInvalid = true;
	//m_fill->setFillRule( VFill::evenOdd );
}

void
VText::setText( const TQString& text )
{
	if( m_text != text )
	{
		m_text = text;
		m_glyphs.clear();
#ifdef HAVE_KARBONTEXT
		traceText();
#endif
	}
}

void
VText::setState( const VState state )
{
	VObject::setState( state );

	VPathListIterator itr( m_glyphs );
	for( itr.toFirst(); itr.current(); ++itr )
	{
		itr.current()->setState( state );
	}
}

void
VText::accept( VVisitor& visitor )
{
	visitor.visitVText( *this );
}

#ifdef HAVE_KARBONTEXT

void
VText::traceText()
{
	if( m_basePath.count() == 0 )
	{
		kdDebug(38000) << "Can't draw a text without base path (was: " << m_text << ")." << endl;
		return;
	}

	// TODO : set more options
	int slant = FC_SLANT_ROMAN;
	if( m_font.italic() )
		slant = FC_SLANT_ITALIC;

	int weight = 0;
	if( m_font.bold() )
		weight = FC_WEIGHT_BOLD;

	// Build FontConfig request pattern
	int id = -1;
	TQString filename = buildRequest( m_font.family(), weight, slant, m_font.pointSize(), id );
	m_glyphs.clear();

	kdDebug(38000) << "Loading " << filename.latin1() << " for requested font \"" << TQString(m_font.family()).latin1() << "\", " << m_font.pointSize() << " pt." << endl;

	FT_UInt glyphIndex;
	FT_Face fontFace;
	// TODO : this lib should probably be a singleton (Rob)
	FT_Library library;
	FT_Init_FreeType( &library );
	FT_Error error = FT_New_Face( library, TQFile::encodeName(filename), id, &fontFace );

	if( error )
	{
		kdDebug(38000) << "traceText(), could not load font. Aborting!" << endl;
		return;
	}

	bool foundCharmap = false;

	// Try to choose unicode charmap
	for( int charmap = 0; charmap < fontFace->num_charmaps; charmap++ )
	{
		if( fontFace->charmaps[charmap]->encoding == ft_encoding_unicode )
		{
			FT_Error error = FT_Set_Charmap( fontFace, fontFace->charmaps[charmap] );
			if( error )
			{
				kdDebug(38000) << "traceText(), unable to select unicode charmap." << endl;
				continue;
			}
			foundCharmap = true;
		}
	}

	// Choose first charmap if no unicode charmap was found
	if( ! foundCharmap )
	{
		error = FT_Set_Charmap( fontFace, fontFace->charmaps[0] );
		if( error )
		{
			kdDebug(38000) << "traceText(), unable to select charmap. Aborting!" << endl;
			FT_Done_Face( fontFace );
			FT_Done_FreeType( library );
			return;
		}
	}

	error = FT_Set_Char_Size( fontFace, FT_FROMFLOAT( m_font.pointSize() ), FT_FROMFLOAT( m_font.pointSize() ), 0, 0 );
	if( error )
	{
		kdDebug(38000) << "traceText(), unable to set font size. Aborting!" << endl;
		FT_Done_Face( fontFace );
		FT_Done_FreeType( library );
		return;
	}

		// storing glyphs.
	float l = 0;
	TQValueList<float> glyphXAdvance;
	TQValueList<float> glyphYAdvance;
	for( unsigned int i = 0; i < m_text.length(); i++ )
	{
		// get the glyph index for the current character
		TQChar character = m_text.at( i );
		glyphIndex = FT_Get_Char_Index( fontFace, character.unicode() );
		if( ! glyphIndex ) 
		{
			kdDebug(38000) << "traceText(), unable get index of char : " << character << endl;
			continue;
		}
		//kdDebug(38000) << "glyphIndex : " << glyphIndex << endl;
		FT_Error error = FT_Load_Glyph( fontFace, glyphIndex, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP );
		if( error )
		{
			kdDebug(38000) << "traceText(), unable to load glyph : " << error << endl;
			continue;
		}

		// decompose to vpaths
		FT_OutlineGlyph g;
		error = FT_Get_Glyph( fontFace->glyph, reinterpret_cast<FT_Glyph *>( &g ) );
		if( error )
		{
			kdDebug(38000) << "traceText(), unable to get glyph: " << error << endl;
			continue;
		}

		VPath *composite = new VPath( this );
		error = FT_Outline_Check( &g->outline );
		if( error ) 
		{
			kdDebug(38000) << "traceText(), outline is broken : " << error << endl;
			continue;
		}

		error = FT_Outline_Decompose(&g->outline, &OutlineMethods, composite );
		if( error )
		{
			kdDebug(38000) << "traceText(), unable to decompose outline : " << error << endl;
			continue;
		}

		m_glyphs.append( composite );
		glyphXAdvance.append( FT_TOFLOAT( fontFace->glyph->advance.x ) );
		glyphYAdvance.append( FT_TOFLOAT( fontFace->glyph->advance.y ) );
		l += FT_TOFLOAT( fontFace->glyph->advance.x );
		FT_Done_Glyph( reinterpret_cast<FT_Glyph>( g ) );
	}

	 // Placing the stored glyphs.
	float pathLength = 0;
	VSubpathIterator pIt( m_basePath );
	
	VSegment* seg;
	for( ; pIt.current(); ++pIt )
		if( (seg = pIt.current() ) ) 
			pathLength += seg->length();

	kdDebug(38000) << "traceText(), using offset : " << m_offset << endl;
	float x = m_offset * pathLength;
	
	switch( m_alignment )
	{
		case Left: x += 0; break;
		case Center: x -=  0.5 * l; break;
		case Right: x -= l; break;
	}
	float y = 0;
	float dx = 0;
	float sp = 0;
	KoPoint point;
	KoPoint normal;
	KoPoint tangent;
	VSubpathIterator pathIt( m_basePath );
	VSegment* oldSeg = pathIt.current();
	seg = ++pathIt;
	KoPoint extPoint;
	bool ext = false;
	float fsx = 0;
	float yoffset = ( m_position == Above ? 0 : ( m_position == On ? m_font.pointSize() / 3 : m_font.pointSize() / 1.5 ) );
	kdDebug(38000) << "Position: " << m_position << " -> " << yoffset << endl;
	for( unsigned int i = 0; i < m_text.length(); i++ )
	{
		VPath* composite = m_glyphs.at( i );
		if( ! composite ) 
			continue;
		// Step 1: place (0, 0) to the rotation center of the glyph.
		dx = *glyphXAdvance.at( i ) / 2;
		x += dx;
		VTransformCmd trafo( 0L, TQWMatrix( 1, 0, 0, 1, -dx, y + yoffset ) );
		trafo.visit( *composite );

		// Step 2: find the position where to draw.
		//   3 possibilities: before, on, and after the basePath...
		if ( x < 0 )
		{
			if( !ext )
				seg->pointTangentNormalAt( 0, &extPoint, &tangent, &normal );
			point = extPoint + x * tangent;
			ext = true;
		}
		else
		{
			while ( seg && x > fsx + seg->length() )
			{
				fsx += seg->length();
				oldSeg = seg;
				seg = ++pathIt;
			}
			if( seg )
			{
				ext = false;
				sp = ( x - fsx ) / seg->length();
				seg->pointTangentNormalAt( sp, &point, &tangent, &normal );
			}
			else
			{
				if( !ext )
					oldSeg->pointTangentNormalAt( 1, &extPoint, &tangent, &normal );
				point = extPoint + ( x - fsx ) * tangent;
				ext = true;
			}
		}

		// Step 3: transform glyph and append it. That's it, we've got
		// text following a path. Really easy, isn't it ;) ?
		trafo.setMatrix( TQWMatrix( tangent.x(), tangent.y(), tangent.y(), -tangent.x(), point.x(), point.y() ) );
		trafo.visit( *composite );
		composite->setState( state() );

		//kdDebug(38000) << "Glyph: " << (TQString)character << " [String pos: " << x << ", " << y << " / Canvas pos: " << point.x() << ", " << point.y() << "]" << endl;

		x += dx;
		y += *glyphYAdvance.at( i );
	}
	FT_Done_Face( fontFace );
	FT_Done_FreeType( library );
	m_boundingBoxIsInvalid = true;
}

// This routine is copied from KSVGFont (Rob)
TQString
VText::buildRequest( TQString family, int weight, int slant, double size, int &id )
{
	// Strip those stupid [Xft or whatever]...
	int pos;
	if( ( pos = family.find( '[' ) ) )
		family = family.left( pos );

	// Use FontConfig to locate & select fonts and use  FreeType2 to open them
	FcPattern *pattern;
	TQString fileName;

	pattern = FcPatternBuild( 0, FC_WEIGHT, FcTypeInteger, weight,
							  FC_SLANT, FcTypeInteger, slant,
							  FC_SIZE, FcTypeDouble, size, NULL );

	// Add font name
	FcPatternAddString( pattern, FC_FAMILY, reinterpret_cast<const FcChar8 *>( family.latin1() ) );

	// Disable hinting
	FcPatternAddBool( pattern, FC_HINTING, FcFalse );
	// Enforce scalability
	FcPatternAddBool( pattern, FC_SCALABLE, FcTrue );

	// Perform the default font pattern modification operations.
	FcDefaultSubstitute( pattern );
	FcConfigSubstitute( FcConfigGetCurrent(), pattern, FcMatchPattern );

	FcResult result;

	// we dont want to use bitmap fonts, so get a list of fonts sorted by closeness to pattern
	// and use the best matching scalable font
	FcFontSet *fset = FcFontSort( 0, pattern, FcFalse, 0L, &result );

	// Destroy pattern
	FcPatternDestroy( pattern );

	if( fset )
	{
		FcBool scalable;
		FcChar8 *temp;
	
		// iterate over font list and take best scaleable font
		for( int i = 0; i < fset->nfont; ++i )
		{
			pattern = fset->fonts[i];
			if( FcResultMatch != FcPatternGetBool( pattern, FC_SCALABLE, 0, &scalable ) )
				continue;
			if( scalable == FcTrue )
			{
				// Get index & filename
				if(	FcPatternGetString(pattern, FC_FILE, 0, &temp) != FcResultMatch ||
					FcPatternGetInteger(pattern, FC_INDEX, 0, &id) != FcResultMatch )
				{
					kdDebug(38000) << "VText::buildRequest(), could not load font file for requested font \"" << family.latin1() << "\"" << endl;
					return TQString();
				}
		
				fileName = TQFile::decodeName(reinterpret_cast<const char *>( temp ));
				
				// get family name of matched font
				TQString newFamily;

				if( FcResultMatch == FcPatternGetString( pattern, FC_FAMILY, 0, &temp ) )
					m_font.setFamily( reinterpret_cast<const char *>( temp ) );

				break;
			}
		}
		
		FcFontSetDestroy( fset );
	}

	
	return fileName;
}

#endif // HAVE_KARBONTEXT

void VText::setOffset( double offset )
{
	if( offset < 0.0 )
		m_offset = 0.0;
	else if( offset > 1.0 )
		m_offset = 1.0;
	else
		m_offset = offset;
}

