package net.flashpunk.tweens.motion 
{
    import flash.geom.Point;
    import net.flashpunk.FP;
    
    /**
     * A series of points which will determine a path from the
     * beginning point to the end poing using quadratic curves.
     */
    public class QuadPath extends Motion
    {
        /**
         * Constructor.
         * @param    complete    Optional completion callback.
         * @param    type        Tween type.
         */
        public function QuadPath(complete:Function = null, type:uint = 0) 
        {
            super(0, complete, type, null);
            _curveT[0] = 0;
        }
        
        /**
         * Starts moving along the path.
         * @param    duration    Duration of the movement.
         * @param    ease        Optional easer function.
         */
        public function setMotion(duration:Number, ease:Function = null):void
        {
            updatePath();
            _target = duration;
            _speed = _distance / duration;
            _ease = ease;
            start();
        }
        
        /**
         * Starts moving along the path at the speed.
         * @param    speed        Speed of the movement.
         * @param    ease        Optional easer function.
         */
        public function setMotionSpeed(speed:Number, ease:Function = null):void
        {
            updatePath();
            _target = _distance / speed;
            _speed = speed;
            _ease = ease;
            start();
        }
        
        /**
         * Adds the point to the path.
         * @param    x        X position.
         * @param    y        Y position.
         */
        public function addPoint(x:Number = 0, y:Number = 0):void
        {
            _updateCurve = true;
            if (!_points.length) _curve[0] = new Point(x, y);
            _points[_points.length] = new Point(x, y);
        }
        
        /**
         * Gets the point on the path.
         * @param    index        Index of the point.
         * @return    The Point object.
         */
        public function getPoint(index:uint = 0):Point
        {
            if (!_points.length) throw new Error("No points have been added to the path yet.");
            return _points[index % _points.length];
        }
        
        /** @private Starts the Tween. */
        override public function start():void 
        {
            _index = 0;
            super.start();
        }
        
        /** @private Updates the Tween. */
        override public function update():void 
        {
            super.update();
            if (_index < _curve.length - 1)
            {
                while (_t > _curveT[_index + 1]) _index ++;
            }
            var td:Number = _curveT[_index],
                tt:Number = _curveT[_index + 1] - td;
            td = (_t - td) / tt;
            _a = _curve[_index];
            _b = _points[_index + 1];
            _c = _curve[_index + 1];
            x = _a.x * (1 - td) * (1 - td) + _b.x * 2 * (1 - td) * td + _c.x * td * td;
            y = _a.y * (1 - td) * (1 - td) + _b.y * 2 * (1 - td) * td + _c.y * td * td;
        }
        
        /** @private Updates the path, preparing the curve. */
        private function updatePath():void
        {
            if (_points.length < 3)    throw new Error("A QuadPath must have at least 3 points to operate.");
            if (!_updateCurve) return;
            _updateCurve = false;
            
            // produce the curve points
            var p:Point,
                c:Point,
                l:Point = _points[1],
                i:uint = 2;
            while (i < _points.length)
            {
                p = _points[i];
                if (_curve.length > i - 1) c = _curve[i - 1];
                else c = _curve[i - 1] = new Point;
                if (i < _points.length - 1)
                {
                    c.x = l.x + (p.x - l.x) / 2;
                    c.y = l.y + (p.y - l.y) / 2;
                }
                else
                {
                    c.x = p.x;
                    c.y = p.y;
                }
                l = p;
                i ++;
            }
            
            // find the total distance of the path
            i = 0;
            _distance = 0;
            while (i < _curve.length - 1)
            {
                _curveD[i] = curveLength(_curve[i], _points[i + 1], _curve[i + 1]);
                _distance += _curveD[i ++];
            }
            
            // find t for each point on the curve
            i = 1;
            var d:Number = 0;
            while (i < _curve.length - 1)
            {
                d += _curveD[i];
                _curveT[i ++] = d / _distance;
            }
            _curveT[_curve.length - 1] = 1;
        }
        
        /**
         * Amount of points on the path.
         */
        public function get pointCount():Number { return _points.length; }
        
        /** @private Calculates the lenght of the curve. */
        private function curveLength(start:Point, control:Point, finish:Point):Number
        {
            var a:Point = FP.point,
                b:Point = FP.point2;
            a.x = start.x - 2 * control.x + finish.x;
            a.y = start.y - 2 * control.y + finish.y;
            b.x = 2 * control.x - 2 * start.x;
            b.y = 2 * control.y - 2 * start.y;
            var A:Number = 4 * (a.x * a.x + a.y * a.y),
                B:Number = 4 * (a.x * b.x + a.y * b.y),
                C:Number = b.x * b.x + b.y * b.y,
                ABC:Number = 2 * Math.sqrt(A + B + C),
                A2:Number = Math.sqrt(A),
                A32:Number = 2 * A * A2,
                C2:Number = 2 * Math.sqrt(C),
                BA:Number = B / A2;
            return (A32 * ABC + A2 * B * (ABC - C2) + (4 * C * A - B * B) * Math.log((2 * A2 + BA + ABC) / (BA + C2))) / (4 * A32);
        }
        
        // Path information.
        /** @private */ private var _points:Vector.<Point> = new Vector.<Point>;
        /** @private */ private var _distance:Number = 0;
        /** @private */ private var _speed:Number = 0;
        /** @private */ private var _index:uint = 0;
        
        // Curve information.
        /** @private */ private var _updateCurve:Boolean = true;
        /** @private */ private var _curve:Vector.<Point> = new Vector.<Point>;
        /** @private */ private var _curveT:Vector.<Number> = new Vector.<Number>;
        /** @private */ private var _curveD:Vector.<Number> = new Vector.<Number>;
        
        // Curve points.
        /** @private */ private var _a:Point;
        /** @private */ private var _b:Point;
        /** @private */ private var _c:Point;
    }
}