/*
    Scan Tailor - Interactive post-processing tool for scanned pages.
    Copyright (C) 2015  Joseph Artsimovich <joseph.artsimovich@gmail.com>

    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/>.
*/

#ifndef XSPLINE_H_
#define XSPLINE_H_

#include "math_config.h"
#include "spfit/FittableSpline.h"
#include "QuadraticFunction.h"
#include "VirtualFunction.h"
#include "NumericTraits.h"
#include <QPointF>
#include <QLineF>
#include <vector>

/**
 * \brief An open X-Spline.
 *
 * \see Help -> About -> References -> [18]
 */
class MATH_EXPORT XSpline : public spfit::FittableSpline
{
public:
	struct MATH_EXPORT PointAndDerivs
	{
		QPointF point; /**< Point on a spline. */
		QPointF firstDeriv; /**< First derivative with respect to t. */
		QPointF secondDeriv; /**< Second derivative with respect to t. */

		/**
		 * \brief Curvature at a given point on the spline.
		 *
		 * The sign indicates curving direction.  Positive signs normally
		 * indicate anti-clockwise direction, though in 2D computer graphics
		 * it's usually the other way around, as the Y axis points down.
		 * In other words, if you rotate your coordinate system so that
		 * the X axis aligns with the tangent vector, curvature will be
		 * positive if the spline curves towards the positive Y direction.
		 */
		double signedCurvature() const;
	};

	virtual int numControlPoints() const;

	/**
	 * Returns the number of segments, that is spans between adjacent control points.
	 * Because this class only deals with open splines, the number of segments
	 * will always be max(0, numControlPoints() - 1).
	 */
	int numSegments() const;

	double controlPointIndexToT(int idx) const;

	/**
	 * \brief Appends a control point to the end of the spline.
	 *
	 * Tension values lie in the range of [-1, 1].
	 * \li tension < 0 produces interpolating patches.
	 * \li tension == 0 produces sharp angle interpolating patches.
	 * \li tension > 0 produces approximating patches.
	 */
	void appendControlPoint(QPointF const& pos, double tension);

	/**
	 * \brief Inserts a control at a specified position.
	 *
	 * \p idx is the position where the new control point will end up in.
	 * The following control points will be shifted.
	 */
	void insertControlPoint(int idx, QPointF const& pos, double tension);

	void eraseControlPoint(int idx);

	virtual QPointF controlPointPosition(int idx) const;

	virtual void moveControlPoint(int idx, QPointF const& pos);

	double controlPointTension(int idx) const;

	void setControlPointTension(int idx, double tension);

	/**
	 * \brief Calculates a point on the spline at position t.
	 *
	 * \param t Position on a spline in the range of [0, 1].
	 * \return Point on a spline.
	 *
	 * \note Calling this function with less then 2 control points
	 *       leads to undefined behaviour.
	 */
	QPointF pointAt(double t) const;
	
	/**
	 * \brief Calculates a point on the spline plus the first and the second derivatives at position t.
	 */
	PointAndDerivs pointAndDtsAt(double t) const;

	/** \see spfit::FittableSpline::linearCombinationAt() */
	virtual void linearCombinationAt(double t, std::vector<LinearCoefficient>& coeffs) const;

	/**
	 * Returns a function equivalent to:
	 * \code
	 * sum((cp[i].x - cp[i - 1].x)^2 + (cp[i].y - cp[i - 1].y)^2)
	 * \endcode
	 * Except the returned function is a function of control point displacements,
	 * not positions.
	 * The sum is calculated across all segments.
	 */
	QuadraticFunction controlPointsAttractionForce() const;

	/**
	 * Same as the above one, but you provide a range of segments to consider.
	 * The range is half-closed: [seg_begin, seg_end)
	 */
	QuadraticFunction controlPointsAttractionForce(int seg_begin, int seg_end) const;

	/**
	 * Returns a function equivalent to:
	 * \code
	 * sum(Vec2d(pointAt(controlPointIndexToT(i)) - pointAt(controlPointIndexToT(i - 1))).squaredNorm())
	 * \endcode
	 * Except the returned function is a function of control point displacements,
	 * not positions.
	 * The sum is calculated across all segments.
	 */
	QuadraticFunction junctionPointsAttractionForce() const;

	/**
	 * Same as the above one, but you provide a range of segments to consider.
	 * The range is half-closed: [seg_begin, seg_end)
	 */
	QuadraticFunction junctionPointsAttractionForce(int seg_begin, int seg_end) const;
	
	/**
	 * \brief Finds a point on the spline that's closest to a given point.
	 *
	 * \param to The point which we trying to minimize the distance to.
	 * \param t If provided, the t value corresponding to the found point will be written there.
	 * \param accuracy The maximum distance from the found point to the spline.
	 * \return The closest point found.
	 */
	QPointF pointClosestTo(QPointF to, double* t, double accuracy = 0.2) const;

	QPointF pointClosestTo(QPointF to, double accuracy = 0.2) const;

	/** \see spfit::FittableSpline::sample() */
	virtual void sample(
		VirtualFunction3<void, QPointF, double, SampleFlags>& sink,
		SamplingParams const& params = SamplingParams(),
		double from_t = 0.0, double to_t = 1.0) const;

	std::vector<QPointF> toPolyline(
		SamplingParams const& params = SamplingParams(),
		double from_t = 0.0, double to_t = 1.0) const;

	void swap(XSpline& other) { m_controlPoints.swap(other.m_controlPoints); }
private:
	struct ControlPoint
	{
		QPointF pos;

		/**
		 * Tension is in range of [-1 .. 1] and corresponds to sk as defined in section 5 of [18],
		 * not to be confused with sk defined in section 4, which has a range of [0 .. 1].
		 */
		double tension;

		ControlPoint() : tension(0) {}

		ControlPoint(QPointF const& p, double tns) : pos(p), tension(tns) {}
	};

	struct TensionDerivedParams;
	class GBlendFunc;
	class HBlendFunc;
	struct DecomposedDerivs;
	
	QPointF pointAtImpl(int segment, double t) const;

	int linearCombinationFor(LinearCoefficient* coeffs, int segment, double t) const;

	DecomposedDerivs decomposedDerivs(double t) const;

	DecomposedDerivs decomposedDerivsImpl(int segment, double t) const;

	void maybeAddMoreSamples(
		VirtualFunction3<void, QPointF, double, SampleFlags>& sink,
		double max_sqdist_to_spline, double max_sqdist_between_samples,
		double num_segments, double r_num_segments,
		double prev_t, QPointF const& prev_pt,
		double next_t, QPointF const& next_pt) const;

	static double sqDistToLine(QPointF const& pt, QLineF const& line);

	std::vector<ControlPoint> m_controlPoints;
};

inline void swap(XSpline& o1, XSpline& o2)
{
	o1.swap(o2);
}

#endif
