#ifndef CURVE_H_
#define CURVE_H_

#include "Ref.h"

namespace gameplay
{

/**
 * Represents an n-dimensional curve.
 */
class Curve : public Ref
{
    friend class AnimationTarget;
    friend class Animation;
    friend class AnimationClip;
    friend class AnimationController;
    friend class MeshSkin;

public:

    /**
     * Types of interpolation.
     *
     * Defines how the points in the curve are connected.
     *
     * Note: InterpolationType::BEZIER requires control points and InterpolationType::HERMITE requires tangents.
     */
    enum InterpolationType
    {
        /**
         * Bezier Interpolation. 
         *
         * Requires that two control points are set for each segment.
         */
        BEZIER,

        /**
         * B-Spline Interpolation. 
         *
         * Uses the points as control points, and the curve is guaranteed to only pass through the
         * first and last point.
         */
        BSPLINE,

        /**
         * Flat Interpolation. 
         * 
         * A form of Hermite interpolation that generates flat tangents for you. The tangents have a value equal to 0.
         */
        FLAT,

        /**
         * Hermite Interpolation. 
         *
         * Requires that two tangents for each segment.
         */
        HERMITE,

        /**
         * Linear Interpolation.
         */
        LINEAR,

        /** 
         * Smooth Interpolation. 
         *
         * A form of Hermite interpolation that generates tangents for each segment based on the points prior to and after the segment.
         */
        SMOOTH,

        /**
         * Discrete Interpolation.
         */ 
        STEP,

        /**
         * Quadratic-In Interpolation.
         */
        QUADRATIC_IN, 
        
        /**
         * Quadratic-Out Interpolation.
         */
        QUADRATIC_OUT,

        /**
         * Quadratic-In-Out Interpolation.
         */
        QUADRATIC_IN_OUT,

        /**
         * Quadratic-Out-In Interpolation.
         */
        QUADRATIC_OUT_IN,

        /**
         * Cubic-In Interpolation.
         */
        CUBIC_IN,
        
        /**
         * Cubic-Out Interpolation.
         */
        CUBIC_OUT,
        
        /**
         * Cubic-In-Out Interpolation.
         */
        CUBIC_IN_OUT,
        
        /**
         * Cubic-Out-In Interpolation.
         */
        CUBIC_OUT_IN,

        /**
         * Quartic-In Interpolation.
         */
        QUARTIC_IN,

        /**
         * Quartic-Out Interpolation.
         */
        QUARTIC_OUT,

        /**
         * Quartic-In-Out Interpolation.
         */
        QUARTIC_IN_OUT,

        /**
         * Quartic-Out-In Interpolation.
         */
        QUARTIC_OUT_IN,

        /**
         * Quintic-In Interpolation.
         */
        QUINTIC_IN,
        
        /**
         * Quintic-Out Interpolation.
         */
        QUINTIC_OUT,
        
        /**
         * Quintic-In-Out Interpolation.
         */
        QUINTIC_IN_OUT,
        
        /**
         * Quintic-Out-In Interpolation.
         */
        QUINTIC_OUT_IN,
        
        /**
         * Sine-In Interpolation.
         */
        SINE_IN,
        
        /**
         * Sine-Out Interpolation.
         */
        SINE_OUT,
        
        /**
         * Sine-In-Out Interpolation.
         */
        SINE_IN_OUT,
        
        /**
         * Sine-Out-In Interpolation.
         */
        SINE_OUT_IN,

        /**
         * Exponential-In Interpolation.
         */
        EXPONENTIAL_IN,

        /**
         * Exponential-Out Interpolation.
         */
        EXPONENTIAL_OUT,

        /**
         * Exponential-In-Out Interpolation.
         */
        EXPONENTIAL_IN_OUT,

        /**
         * Exponential-Out-In Interpolation.
         */
        EXPONENTIAL_OUT_IN,

        /**
         * Circular-In Interpolation.
         */
        CIRCULAR_IN,

        /**
         * Circular-Out Interpolation.
         */
        CIRCULAR_OUT,

        /**
         * Circular-In-Out Interpolation.
         */
        CIRCULAR_IN_OUT,

        /**
         * Circular-Out-In Interpolation.
         */
        CIRCULAR_OUT_IN,

        /**
         * Elastic-In Interpolation.
         */
        ELASTIC_IN,

        /**
         * Elastic-Out Interpolation.
         */
        ELASTIC_OUT,

        /**
         * Elastic-In-Out Interpolation.
         */
        ELASTIC_IN_OUT,

        /**
         * Elastic-Out-In Interpolation.
         */
        ELASTIC_OUT_IN,

        /**
         * Overshoot-In Interpolation.
         */
        OVERSHOOT_IN,

        /**
         * Overshoot-Out Interpolation.
         */
        OVERSHOOT_OUT,

        /**
         * Overshoot-In-Out Interpolation.
         */
        OVERSHOOT_IN_OUT,

        /**
         * Overshoot-Out-In Interpolation.
         */
        OVERSHOOT_OUT_IN,

        /**
         * Bounce-In Interpolation.
         */
        BOUNCE_IN,

        /**
         * Bounce-Out Interpolation.
         */
        BOUNCE_OUT,

        /**
         * Bounce-In-Out Interpolation.
         */
        BOUNCE_IN_OUT,

        /**
         * Bounce-Out-In Interpolation.
         */
        BOUNCE_OUT_IN
    };

    /**
     * Creates a new curve.
     * 
     * @param pointCount The number of points in the curve.
     * @param componentCount The number of float component values per key value.
     */
    static Curve* create(unsigned int pointCount, unsigned int componentCount);

    /**
     * Gets the number of points in the curve.
     *
     * @return The number of points in the curve.
     */
    unsigned int getPointCount() const;

    /**
     * Gets the number of float component values per points.
     *
     * @return The number of float component values per point.
     */
    unsigned int getComponentCount() const;

    /**
     * Returns the start time for the curve.
     *
     * @return The curve's start time.
     */
    float getStartTime() const;

    /**
     * Returns the end time for the curve.
     *
     * @return The curve's end time.
     */
    float getEndTime() const;

    /**
     * Sets the given point values on the curve the curve.
     *
     * @param index The index of the point.
     * @param time The time for the key.
     * @param value The point to add.
     * @param type The curve interpolation type.
     */
    void setPoint(unsigned int index, float time, float* value, InterpolationType type);

    /**
     * Sets the given point on the curve for the specified index and the specified parameters.
     *
     * @param index The index of the point.
     * @param time The time of the point within the curve.
     * @param value The value of the point to copy the data from.
     * @param type The curve interpolation type.
     * @param inValue The tangent approaching the point.
     * @param outValue The tangent leaving the point.
     */
    void setPoint(unsigned int index, float time, float* value, InterpolationType type, float* inValue, float* outValue);

    /**
     * Sets the tangents for a point on the curve specified by the index.
     *
     * @param index The index of the point.
     * @param type The curve interpolation type.
     * @param inValue The tangent approaching the point.
     * @param outValue The tangent leaving the point.
     */
    void setTangent(unsigned int index, InterpolationType type, float* inValue, float* outValue);
    
    /**
     * Evaluates the curve at the given position value (between 0.0 and 1.0 inclusive).
     *
     * @param time The position to evaluate the curve at.
     * @param dst The evaluated value of the curve at the given time.
     */
    void evaluate(float time, float* dst) const;

    /**
     * Linear interpolation function.
     */
    static float lerp(float t, float from, float to);

private:

    /**
     * Defines a single point within a curve.
     */
    class Point
    {
    public:

        /** The time of the point within the curve. */
        float time;
        /** The value of the point. */
        float* value;
        /** The value of the tangent when approaching this point (from the previous point in the curve). */
        float* inValue;
        /** The value of the tangent when leaving this point (towards the next point in the curve). */
        float* outValue;
        /** The type of interpolation to use between this point and the next point. */
        InterpolationType type;

        /**
         * Constructor.
         */
        Point();

        /**
         * Destructor.
         */
        ~Point();
    };

    /**
     * Constructor.
     */
    Curve();

    /**
     * Constructs a new curve and the specified parameters.
     *
     * @param pointCount The number of points in the curve.
     * @param componentCount The number of float component values per key value.
     */
    Curve(unsigned int pointCount, unsigned int componentCount);

    /**
     * Constructor.
     */
    Curve(const Curve& copy);

    /**
     * Destructor.
     */
    ~Curve();

    /**
     * Copy assignment operator.
     */
    Curve& operator=(const Curve&);

    /**
     * Bezier interpolation function.
     */
    void interpolateBezier(float s, Point* from, Point* to, float* dst) const;

    /**
     * Bspline interpolation function.
     */
    void interpolateBSpline(float s, Point* c0, Point* c1, Point* c2, Point* c3, float* dst) const;

    /**
     * Hermite interpolation function.
     */
    void interpolateHermite(float s, Point* from, Point* to, float* dst) const;

    /**
     * Hermite interpolation function.
     */
    void interpolateHermiteFlat(float s, Point* from, Point* to, float* dst) const;

    /**
     * Hermite interpolation function.
     */
    void interpolateHermiteSmooth(float s, unsigned int index, Point* from, Point* to, float* dst) const;

    /** 
     * Linear interpolation function.
     */ 
    void interpolateLinear(float s, Point* from, Point* to, float* dst) const;

    /**
     * Quaternion interpolation function.
     */
    void interpolateQuaternion(float s, float* from, float* to, float* dst) const;
    
    /**
     * Determines the current keyframe to interpolate from based on the specified time.
     */ 
    int determineIndex(float time) const;

    /**
     * Sets the offset for the beginning of a Quaternion piece of data within the curve's value span at the specified
     * index. The next four components of data starting at the given index will be interpolated as a Quaternion.
     * This function will assert an error if the given index is greater than the component size subtracted by the four components required
     * to store a quaternion.
     * 
     * @param index The index of the Quaternion rotation data.
     */
    void setQuaternionOffset(unsigned int index);

    /**
     * Gets the InterpolationType value for the given string ID
     *
     * @param interpolationId The string representation of the InterpolationType
     * @return the InterpolationType value; -1 if the string does not represent an InterpolationType.
     */
    static int getInterpolationType(const char* interpolationId);

    unsigned int _pointCount;           // Number of points on the curve.
    unsigned int _componentCount;       // Number of components on the curve.
    unsigned int _componentSize;        // The component size (in bytes).
    unsigned int* _quaternionOffset;    // Offset for the rotation component.
    Point* _points;                     // The points on the curve.
};

inline static float bezier(float eq0, float eq1, float eq2, float eq3, float from, float out, float to, float in);

inline static float bspline(float eq0, float eq1, float eq2, float eq3, float c0, float c1, float c2, float c3);

inline static float hermite(float h00, float h01, float h10, float h11, float from, float out, float to, float in);

inline static float hermiteFlat(float h00, float h01, float from, float to);

inline static float hermiteSmooth(float h00, float h01, float h10, float h11, float from, float out, float to, float in);

inline static float lerpInl(float s, float from, float to);

}

#include "Curve.inl"

#endif
