/* This file is part of the KDE project
   Copyright (C) 2001, 2002, 2003 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., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#include <math.h>

#include <qdom.h>

#include "vpainter.h"
#include "vpath.h"
#include "vsegment.h"

#include <kdebug.h>

VSegment::VSegment( unsigned short deg )
{
	m_degree = deg;

	m_nodes = new VNodeData[ degree() ];

	for( unsigned short i = 0; i < degree(); ++i )
		selectPoint( i );

	m_state = normal;

	m_prev = 0L;
	m_next = 0L;
}

VSegment::VSegment( const VSegment& segment )
{
	m_degree = segment.degree();

	m_nodes = new VNodeData[ degree() ];

	m_state = segment.m_state;

	// Copying the pointers m_prev/m_next has some advantages (see VSegment::length()).
	// Inserting a segment into a path overwrites these anyway.
	m_prev = segment.m_prev;
	m_next = segment.m_next;

	// Copy points.
	for( unsigned short i = 0; i < degree(); i++ )
	{
		setPoint( i, segment.point( i ) );
		selectPoint( i, segment.pointIsSelected( i ) );
	}
}

VSegment::~VSegment()
{
	delete[]( m_nodes );
}

void
VSegment::setDegree( unsigned short deg )
{
	// Do nothing if old and new degrees are identical.
	if( degree() == deg )
		return;

	// TODO : this code is fishy, please make it sane

	// Remember old nodes.
	VNodeData* oldNodes = m_nodes;
	KoPoint oldKnot = knot();

	// Allocate new node data.
	m_nodes = new VNodeData[ deg ];

	if( deg == 1 )
		m_nodes[ 0 ].m_vector = oldKnot;
	else
	{
		// Copy old node data (from the knot "backwards".
		unsigned short offset = kMax( 0, deg - m_degree );

		for( unsigned short i = offset; i < deg; ++i )
		{
			m_nodes[ i ].m_vector = oldNodes[ i - offset ].m_vector;
		}

		// Fill with "zeros" if necessary.
		for( unsigned short i = 0; i < offset; ++i )
		{
			m_nodes[ i ].m_vector = KoPoint( 0.0, 0.0 );
		}
	}

	// Set new degree.
	m_degree = deg;

	// Delete old nodes.
	delete[]( oldNodes );
}

void
VSegment::draw( VPainter* painter ) const
{
	// Don't draw a deleted segment.
	if( state() == deleted )
		return;


	if( prev() )
	{
		if( degree() == 3 )
		{
			painter->curveTo( point( 0 ), point( 1 ), point( 2 ) );
		}
		else
		{
			painter->lineTo( knot() );
		}
	}
	else
	{
		painter->moveTo( knot() );
	}
}

bool
VSegment::isFlat( double flatness ) const
{
	// Lines and "begin" segments are flat.
	if(
		!prev() ||
		degree() == 1 )
	{
		return true;
	}


	// Iterate over control points.
	for( unsigned short i = 0; i < degree() - 1; ++i )
	{
		if(
			height( prev()->knot(), point( i ), knot() ) / chordLength()
			>= flatness )
		{
			return false;
		}
	}

	return true;
}

KoPoint
VSegment::pointAt( double t ) const
{
	KoPoint p;

	pointDerivativesAt( t, &p );

	return p;
}

void
VSegment::pointDerivativesAt( double t, KoPoint* p,
							  KoPoint* d1, KoPoint* d2 ) const
{
	if( !prev() )
		return;


	// Optimise the line case.
	if( degree() == 1 )
	{
		const KoPoint diff = knot() - prev()->knot();

		if( p )
			*p = prev()->knot() + diff * t;

		if( d1 )
			*d1 = diff;

		if( d2 )
			*d2 = KoPoint( 0.0, 0.0 );

		return;
	}


	// Beziers.

	// Copy points.
	KoPoint* q = new KoPoint[ degree() + 1 ];

	q[ 0 ] = prev()->knot();

	for( unsigned short i = 0; i < degree(); ++i )
	{
		q[ i + 1 ] = point( i );
	}


	// The De Casteljau algorithm.
	for( unsigned short j = 1; j <= degree(); j++ )
	{
		for( unsigned short i = 0; i <= degree() - j; i++ )
		{
			q[ i ] = ( 1.0 - t ) * q[ i ] + t * q[ i + 1 ];
		}

		// Save second derivative now that we have it.
		if( j == degree() - 2 )
		{
			if( d2 )
				*d2 = degree() * ( degree() - 1 )
					  * ( q[ 2 ] - 2 * q[ 1 ] + q[ 0 ] );
		}

		// Save first derivative now that we have it.
		else if( j == degree() - 1 )
		{
			if( d1 )
				*d1 = degree() * ( q[ 1 ] - q[ 0 ] );
		}
	}

	// Save point.
	if( p )
		*p = q[ 0 ];

	delete[]( q );


	return;
}

KoPoint
VSegment::tangentAt( double t ) const
{
	KoPoint tangent;

	pointTangentNormalAt( t, 0L, &tangent );

	return tangent;
}

void
VSegment::pointTangentNormalAt( double t, KoPoint* p,
								KoPoint* tn, KoPoint* n ) const
{
	// Calculate derivative if necessary.
	KoPoint d;

	pointDerivativesAt( t, p, tn || n ? &d : 0L );


	// Normalize derivative.
	if( tn || n )
	{
		const double norm =
			sqrt( d.x() * d.x() + d.y() * d.y() );

		d = norm ? d * ( 1.0 / norm ) : KoPoint( 0.0, 0.0 );
	}

	// Assign tangent vector.
	if( tn )
		*tn = d;

	// Calculate normal vector.
	if( n )
	{
		// Calculate vector product of "binormal" x tangent
		// (0,0,1) x (dx,dy,0), which is simply (dy,-dx,0).
		n->setX( d.y() );
		n->setY( -d.x() );
	}
}

double
VSegment::length( double t ) const
{
	if( !prev() || t == 0.0 )
	{
		return 0.0;
	}


	// Optimise the line case.
	if( degree() == 1 )
	{
		return
			t * chordLength();
	}


	/* This algortihm is by Jens Gravesen <gravesen AT mat DOT dth DOT dk>.
	 * We calculate the chord length "chord"=|P0P3| and the length of the control point
	 * polygon "poly"=|P0P1|+|P1P2|+|P2P3|. The approximation for the bezier length is
	 * 0.5 * poly + 0.5 * chord. "poly - chord" is a measure for the error.
	 * We subdivide each segment until the error is smaller than a given tolerance
	 * and add up the subresults.
	 */

	// "Copy segment" splitted at t into a path.
	VSubpath path( 0L );
	path.moveTo( prev()->knot() );

	// Optimize a bit: most of the time we'll need the
	// length of the whole segment.
	if( t == 1.0 )
		path.append( this->clone() );
	else
	{
		VSegment* copy = this->clone();
		path.append( copy->splitAt( t ) );
		delete copy;
	}


	double chord;
	double poly;

	double length = 0.0;

	while( path.current() )
	{
		chord = path.current()->chordLength();
		poly = path.current()->polyLength();

		if(
			poly &&
			( poly - chord ) / poly > VGlobal::lengthTolerance )
		{
			// Split at midpoint.
			path.insert(
				path.current()->splitAt( 0.5 ) );
		}
		else
		{
			length += 0.5 * poly + 0.5 * chord;
			path.next();
		}
	}


	return length;
}

double
VSegment::chordLength() const
{
	if( !prev() )
		return 0.0;


	KoPoint d = knot() - prev()->knot();

	return sqrt( d * d );
}

double
VSegment::polyLength() const
{
	if( !prev() )
		return 0.0;


	// Start with distance |first point - previous knot|.
	KoPoint d = point( 0 ) - prev()->knot();

	double length = sqrt( d * d );

	// Iterate over remaining points.
	for( unsigned short i = 1; i < degree(); ++i )
	{
		d = point( i ) - point( i - 1 );
		length += sqrt( d * d );
	}


	return length;
}

double
VSegment::lengthParam( double len ) const
{
	if(
		!prev() ||
		len == 0.0 )		// We divide by len below.
	{
		return 0.0;
	}


	// Optimise the line case.
	if( degree() == 1 )
	{
		return
			len / chordLength();
	}


	// Perform a successive interval bisection.
	double param1 = 0.0;
	double paramMid = 0.5;
	double param2 = 1.0;

	double lengthMid = length( paramMid );

	while( QABS( lengthMid - len ) / len > VGlobal::paramLengthTolerance )
	{
		if( lengthMid < len )
			param1 = paramMid;
		else
			param2 = paramMid;

		paramMid = 0.5 * ( param2 + param1 );

		lengthMid = length( paramMid );
	}

	return paramMid;
}

double
VSegment::nearestPointParam( const KoPoint& p ) const
{
	if( !prev() )
	{
		return 1.0;
	}


	/* This function solves the "nearest point on curve" problem. That means, it
	 * calculates the point q (to be precise: it's parameter t) on this segment, which
	 * is located nearest to the input point P.
	 * The basic idea is best described (because it is freely available) in "Phoenix:
	 * An Interactive Curve Design System Based on the Automatic Fitting of
	 * Hand-Sketched Curves", Philip J. Schneider (Master thesis, University of
	 * Washington).
	 *
	 * For the nearest point q = C(t) on this segment, the first derivative is
	 * orthogonal to the distance vector "C(t) - P". In other words we are looking for
	 * solutions of f(t) = ( C(t) - P ) * C'(t) = 0.
	 * ( C(t) - P ) is a nth degree curve, C'(t) a n-1th degree curve => f(t) is a
	 * (2n - 1)th degree curve and thus has up to 2n - 1 distinct solutions.
	 * We solve the problem f(t) = 0 by using something called "Approximate Inversion Method".
	 * Let's write f(t) explicitly (with c_i = p_i - P and d_j = p_{j+1} - p_j):
	 *
	 *         n                     n-1
	 * f(t) = SUM c_i * B^n_i(t)  *  SUM d_j * B^{n-1}_j(t)
	 *        i=0                    j=0
	 *
	 *         n  n-1
	 *      = SUM SUM w_{ij} * B^{2n-1}_{i+j}(t)
	 *        i=0 j=0
	 *
	 * with w_{ij} = c_i * d_j * z_{ij} and
	 *
	 *          BinomialCoeff( n, i ) * BinomialCoeff( n - i ,j )
	 * z_{ij} = -----------------------------------------------
	 *                   BinomialCoeff( 2n - 1, i + j )
	 *
	 * This Bernstein-Bezier polynom representation can now be solved for it's roots.
	 */


	// Calculate the c_i = point( i ) - P.
	KoPoint* c = new KoPoint[ degree() + 1 ];

	c[ 0 ] = prev()->knot() - p;

	for( unsigned short i = 1; i <= degree(); ++i )
	{
		c[ i ] = point( i - 1 ) - p;
	}


	// Calculate the d_j = point( j + 1 ) - point( j ).
	KoPoint* d = new KoPoint[ degree() ];

	d[ 0 ] = point( 0 ) - prev()->knot();

	for( unsigned short j = 1; j <= degree() - 1; ++j )
	{
		d[ j ] = 3.0 * ( point( j ) - point( j - 1 ) );
	}


	// Calculate the z_{ij}.
	double* z = new double[ degree() * ( degree() + 1 ) ];

	for( unsigned short j = 0; j <= degree() - 1; ++j )
	{
		for( unsigned short i = 0; i <= degree(); ++i )
		{
			z[ j * ( degree() + 1 ) + i ] =
				static_cast<double>(
					VGlobal::binomialCoeff( degree(), i ) *
					VGlobal::binomialCoeff( degree() - i, j ) )
				/
				static_cast<double>(
					VGlobal::binomialCoeff( 2 * degree() - 1, i + j ) );
		}
	}


	// Calculate the dot products of c_i and d_i.
	double* products = new double[ degree() * ( degree() + 1 ) ];

	for( unsigned short j = 0; j <= degree() - 1; ++j )
	{
		for( unsigned short i = 0; i <= degree(); ++i )
		{
			products[ j * ( degree() + 1 ) + i ] =
				d[ j ] * c[ i ];
		}
	}

	// We don't need the c_i and d_i anymore.
	delete[]( d );
	delete[]( c );


	// Calculate the control points of the new 2n-1th degree curve.
	VSubpath newCurve( 0L );
	newCurve.append( new VSegment( 2 * degree() - 1 ) );

	// Set up control points in the ( u, f(u) )-plane.
	for( unsigned short u = 0; u <= 2 * degree() - 1; ++u )
	{
		newCurve.current()->setP(
			u,
			KoPoint(
				static_cast<double>( u ) / static_cast<double>( 2 * degree() - 1 ),
				0.0 ) );
	}


	// Set f(u)-values.
	for( unsigned short k = 0; k <= 2 * degree() - 1; ++k )
	{
		unsigned short min = kMin( k, degree() );

		for(
			unsigned short i = kMax( 0, k - ( degree() - 1 ) );
			i <= min;
			++i )
		{
			unsigned short j = k - i;

			// p_k += products[j][i] * z[j][i].
			newCurve.getLast()->setP(
				k,
				KoPoint(
					newCurve.getLast()->p( k ).x(),
					newCurve.getLast()->p( k ).y() +
						products[ j * ( degree() + 1 ) + i ] *
							z[ j * ( degree() + 1 ) + i ] ) );
		}
	}

	// We don't need the c_i/d_i dot products and the z_{ij} anymore.
	delete[]( products );
	delete[]( z );

kdDebug(38000) << "results" << endl;
for( int i = 0; i <= 2 * degree() - 1; ++i )
{
kdDebug(38000) << newCurve.getLast()->p( i ).x() << " "
<< newCurve.getLast()->p( i ).y() << endl;
}
kdDebug(38000) << endl;

	// Find roots.
	QValueList<double> params;

	newCurve.getLast()->rootParams( params );


	// Now compare the distances of the candidate points.
	double resultParam;
	double distanceSquared;
	double oldDistanceSquared;
	KoPoint dist;

	// First candidate is the previous knot.
	dist = prev()->knot() - p;
	distanceSquared = dist * dist;
	resultParam = 0.0;

	// Iterate over the found candidate params.
	for( QValueListConstIterator<double> itr = params.begin(); itr != params.end(); ++itr )
	{
		pointDerivativesAt( *itr, &dist );
		dist -= p;
		oldDistanceSquared = distanceSquared;
		distanceSquared = dist * dist;

		if( distanceSquared < oldDistanceSquared )
			resultParam = *itr;
	}

	// Last candidate is the knot.
	dist = knot() - p;
	oldDistanceSquared = distanceSquared;
	distanceSquared = dist * dist;

	if( distanceSquared < oldDistanceSquared )
		resultParam = 1.0;


	return resultParam;
}

void
VSegment::rootParams( QValueList<double>& params ) const
{
	if( !prev() )
	{
		return;
	}


	// Calculate how often the control polygon crosses the x-axis
	// This is the upper limit for the number of roots.
	switch( controlPolygonZeros() )
	{
		// No solutions.
		case 0:
			return;
		// Exactly one solution.
		case 1:
			if( isFlat( VGlobal::flatnessTolerance / chordLength() ) )
			{
				// Calculate intersection of chord with x-axis.
				KoPoint chord = knot() - prev()->knot();

kdDebug(38000) << prev()->knot().x()  << " " << prev()->knot().y()
<< knot().x() << " " << knot().y() << " ---> "
<< ( chord.x() * prev()->knot().y() - chord.y() * prev()->knot().x() ) / - chord.y() << endl;
				params.append(
					( chord.x() * prev()->knot().y() - chord.y() * prev()->knot().x() )
					/ - chord.y() );

				return;
			}
			break;
	}

	// Many solutions. Do recursive midpoint subdivision.
	VSubpath path( *this );
	path.insert( path.current()->splitAt( 0.5 ) );

	path.current()->rootParams( params );
	path.next()->rootParams( params );
}

int
VSegment::controlPolygonZeros() const
{
	if( !prev() )
	{
		return 0;
	}


	int signChanges = 0;

	int sign = VGlobal::sign( prev()->knot().y() );
	int oldSign;

	for( unsigned short i = 0; i < degree(); ++i )
	{
		oldSign = sign;
		sign = VGlobal::sign( point( i ).y() );

		if( sign != oldSign )
		{
			++signChanges;
		}
	}


	return signChanges;
}

bool
VSegment::isSmooth( const VSegment& next ) const
{
	// Return false if this segment is a "begin".
	if( !prev() )
		return false;


	// Calculate tangents.
	KoPoint t1;
	KoPoint t2;

	pointTangentNormalAt( 1.0, 0L, &t1 );

	next.pointTangentNormalAt( 0.0, 0L, &t2 );


	// Dot product.
	if( t1 * t2 >= VGlobal::parallelTolerance )
		return true;

	return false;
}

KoRect
VSegment::boundingBox() const
{
	// Initialize with knot.
	KoRect rect( knot(), knot() );


	// Add p0, if it exists.
	if( prev() )
	{
		if( prev()->knot().x() < rect.left() )
			rect.setLeft( prev()->knot().x() );

		if( prev()->knot().x() > rect.right() )
			rect.setRight( prev()->knot().x() );

		if( prev()->knot().y() < rect.top() )
			rect.setTop( prev()->knot().y() );

		if( prev()->knot().y() > rect.bottom() )
			rect.setBottom( prev()->knot().y() );
	}


	for( unsigned short i = 0; i < degree() - 1; ++i )
	{
		if( point( i ).x() < rect.left() )
			rect.setLeft( point( i ).x() );

		if( point( i ).x() > rect.right() )
			rect.setRight( point( i ).x() );

		if( point( i ).y() < rect.top() )
			rect.setTop( point( i ).y() );

		if( point( i ).y() > rect.bottom() )
			rect.setBottom( point( i ).y() );
	}


	return rect;
}

VSegment*
VSegment::splitAt( double t )
{
	if( !prev() )
	{
		return 0L;
	}


	// Create new segment.
	VSegment* segment = new VSegment( degree() );

	// Set segment state.
	segment->m_state = m_state;


	// Lines are easy: no need to modify the current segment.
	if( degree() == 1 )
	{
		segment->setKnot(
			prev()->knot() +
			( knot() - prev()->knot() ) * t );

		return segment;
	}


	// Beziers.

	// Copy points.
	KoPoint* q = new KoPoint[ degree() + 1 ];

	q[ 0 ] = prev()->knot();

	for( unsigned short i = 0; i < degree(); ++i )
	{
		q[ i + 1 ] = point( i );
	}


	// The De Casteljau algorithm.
	for( unsigned short j = 1; j <= degree(); ++j )
	{
		for( unsigned short i = 0; i <= degree() - j; ++i )
		{
			q[ i ] = ( 1.0 - t ) * q[ i ] + t * q[ i + 1 ];
		}

		// Modify the new segment.
		segment->setPoint( j - 1, q[ 0 ] );
	}

	// Modify the current segment (no need to modify the knot though).
	for( unsigned short i = 1; i < degree(); ++i )
	{
		setPoint( i - 1, q[ i ] );
	}


	delete[]( q );


	return segment;
}

double
VSegment::height(
	const KoPoint& a,
	const KoPoint& p,
	const KoPoint& b )
{
	// Calculate determinant of AP and AB to obtain projection of vector AP to
	// the orthogonal vector of AB.
	const double det =
		p.x() * a.y() + b.x() * p.y() - p.x() * b.y() -
		a.x() * p.y() + a.x() * b.y() - b.x() * a.y();

	// Calculate norm = length(AB).
	const KoPoint ab = b - a;
	const double norm = sqrt( ab * ab );

	// If norm is very small, simply use distance AP.
	if( norm < VGlobal::verySmallNumber )
		return
			sqrt(
				( p.x() - a.x() ) * ( p.x() - a.x() ) +
				( p.y() - a.y() ) * ( p.y() - a.y() ) );

	// Normalize.
	return QABS( det ) / norm;
}

bool
VSegment::linesIntersect(
	const KoPoint& a0,
	const KoPoint& a1,
	const KoPoint& b0,
	const KoPoint& b1 )
{
	const KoPoint delta_a = a1 - a0;
	const double det_a = a1.x() * a0.y() - a1.y() * a0.x();

	const double r_b0 = delta_a.y() * b0.x() - delta_a.x() * b0.y() + det_a;
	const double r_b1 = delta_a.y() * b1.x() - delta_a.x() * b1.y() + det_a;

	if( r_b0 != 0.0 && r_b1 != 0.0 && r_b0 * r_b1 > 0.0 )
		return false;

	const KoPoint delta_b = b1 - b0;

	const double det_b = b1.x() * b0.y() - b1.y() * b0.x();

	const double r_a0 = delta_b.y() * a0.x() - delta_b.x() * a0.y() + det_b;
	const double r_a1 = delta_b.y() * a1.x() - delta_b.x() * a1.y() + det_b;

	if( r_a0 != 0.0 && r_a1 != 0.0 && r_a0 * r_a1 > 0.0 )
		return false;

	return true;
}

bool
VSegment::intersects( const VSegment& segment ) const
{
	if(
		!prev() ||
		!segment.prev() )
	{
		return false;
	}


	//TODO: this just dumbs down beziers to lines!
	return linesIntersect( segment.prev()->knot(), segment.knot(), prev()->knot(), knot() );
}

// TODO: Move this function into "userland"
uint
VSegment::nodeNear( const KoPoint& p, double isNearRange ) const
{
	int index = 0;

	for( unsigned short i = 0; i < degree(); ++i )
	{
		if( point( 0 ).isNear( p, isNearRange ) )
		{
			index = i + 1;
			break;
		}
	}

	return index;
}

VSegment*
VSegment::revert() const
{
	if( !prev() )
		return 0L;

	// Create new segment.
	VSegment* segment = new VSegment( degree() );

	segment->m_state = m_state;


	// Swap points.
	for( unsigned short i = 0; i < degree() - 1; ++i )
	{
		segment->setPoint( i, point( degree() - 2 - i ) );
	}

	segment->setKnot( prev()->knot() );


	// TODO swap node attributes (selected)

	return segment;
}

VSegment*
VSegment::prev() const
{
	VSegment* segment = m_prev;

	while( segment && segment->state() == deleted )
	{
		segment = segment->m_prev;
	}

	return segment;
}

VSegment*
VSegment::next() const
{
	VSegment* segment = m_next;

	while( segment && segment->state() == deleted )
	{
		segment = segment->m_next;
	}

	return segment;
}

// TODO: remove this backward compatibility function after koffice 1.3.x
void
VSegment::load( const QDomElement& element )
{
	if( element.tagName() == "CURVE" )
	{
		setDegree( 3 );

		setPoint(
			0,
			KoPoint(
				element.attribute( "x1" ).toDouble(),
				element.attribute( "y1" ).toDouble() ) );

		setPoint(
			1,
			KoPoint(
				element.attribute( "x2" ).toDouble(),
				element.attribute( "y2" ).toDouble() ) );

		setKnot(
			KoPoint(
				element.attribute( "x3" ).toDouble(),
				element.attribute( "y3" ).toDouble() ) );
	}
	else if( element.tagName() == "LINE" )
	{
		setDegree( 1 );

		setKnot(
			KoPoint(
				element.attribute( "x" ).toDouble(),
				element.attribute( "y" ).toDouble() ) );
	}
	else if( element.tagName() == "MOVE" )
	{
		setDegree( 1 );

		setKnot(
			KoPoint(
				element.attribute( "x" ).toDouble(),
				element.attribute( "y" ).toDouble() ) );
	}
}

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

