/// <reference path="./Vector3.ts"/>
/// <reference path="./Hand.ts"/>
/// <reference path="./InteractionBox.ts"/>
/// <reference path="./Pointable.ts"/>
/// <reference path="./Finger.ts"/>
/// <reference path="./Tool.ts"/>
/// <reference path="./Matrix.ts"/>
/// <reference path="./Gesture.ts"/>
/// <reference path="./Controller.ts"/>
/**
 * The Frame class represents a set of hand and finger tracking
 * data detected in a single frame.
 *
 * <p>The Leap detects hands, fingers and tools within the tracking area,
 * reporting their positions, orientations and motions in frames at
 * the Leap frame rate.</p>
 *
 * <p>Access Frame objects through a listener of a Leap Controller.
 * Add a listener to receive events when a new Frame is available.</p>
 *
 * @author logotype
 *
 */
export class Frame
{
    /**
     * The list of Finger objects detected in this frame, given in arbitrary order.<br/>
     * The list can be empty if no fingers are detected.
     */
    public fingers:Finger[] = [];

    /**
     * The list of Hand objects detected in this frame, given in arbitrary order.<br/>
     * The list can be empty if no hands are detected.
     */
    public hands:Hand[] = [];

    /**
     * The Pointable object with the specified ID in this frame.
     *
     * <p>Use the <code>Frame.pointable()</code> to retrieve the Pointable
     * object from this frame using an ID value obtained from a previous frame.
     * This always returns a Pointable object, but if no finger
     * or tool with the specified ID is present, an invalid Pointable
     * object is returned.</p>
     *
     * <p>Note that ID values persist across frames, but only until tracking
     * of a particular object is lost. If tracking of a finger or tool is
     * lost and subsequently regained, the new Pointable object representing
     * that finger or tool may have a different ID than that representing
     * the finger or tool in an earlier frame.</p>
     *
     * @see Pointable
     *
     */
    public pointables:Pointable[] = [];

    /**
     * The gestures recognized or continuing in this frame.
     *
     * <p>Circle and swipe gestures are updated every frame.
     * Tap gestures only appear in the list when they start.</p>
     */
    public _gestures:Gesture[] = [];

    /**
     * A unique ID for this Frame.
     * <p>Consecutive frames processed by the Leap have consecutive increasing values.</p>
     */
    public id:number;

    /**
     * The current framerate (in frames per second) of the Leap Motion Controller.
     * <p>This value may fluctuate depending on available computing resources,
     * activity within the device field of view, software tracking settings,
     * and other factors.</p>
     * <p>An estimate of frames per second of the Leap Motion Controller.</p>
     */
    public currentFramesPerSecond:number;

    /**
     * The current InteractionBox for the frame.
     * <p>See the InteractionBox class documentation for more details on how this class should be used.</p>
     * @see InteractionBox
     */
    public interactionBox:InteractionBox;

    /**
     * The frame capture time in microseconds elapsed since the Leap started.
     */
    public timestamp:number;

    /**
     * The list of Tool objects detected in this frame, given in arbitrary order.
     *
     * @see Tool
     */
    public tools:Tool[] = [];

    /**
     * Rotation matrix.
     */
    public rotation:Matrix;

    /**
     * Scale factor since last Frame.
     */
    public scaleFactorNumber:number;

    /**
     * Translation since last Frame.
     */
    public translationVector:Vector3;

    /**
     * Reference to the current Controller.
     */
    public controller:Controller;

    /**
     * Constructs a Frame object.
     *
     * <p>Frame instances created with this constructor are invalid.
     * Get valid Frame objects by calling the <code>LeapMotion.frame()</code> function.</p>
     *
     */
    constructor()
    {
    }

    /**
     * The Hand object with the specified ID in this frame.
     *
     * <p>Use the <code>Frame.hand()</code> to retrieve the Hand object
     * from this frame using an ID value obtained from a previous frame.
     * This always returns a Hand object, but if no hand
     * with the specified ID is present, an invalid Hand object is returned.</p>
     *
     * <p>Note that ID values persist across frames, but only until tracking
     * of a particular object is lost. If tracking of a hand is lost
     * and subsequently regained, the new Hand object representing
     * that physical hand may have a different ID than that
     * representing the physical hand in an earlier frame.</p>
     *
     * @param id The ID value of a Hand object from a previous frame.
     * @return The Hand object with the matching ID if one exists
     * in this frame; otherwise, an invalid Hand object is returned.
     * @see Hand
     *
     */
    public hand( id:number ):Hand
    {
        var returnValue:Hand = Hand.invalid();
        var length:number = this.hands.length;

        for ( var i:number = 0; i < length; i++ )
        {
            if ( this.hands[ i ].id === id )
            {
                returnValue = this.hands[ i ];
                break;
            }
        }

        return returnValue;
    }

    /**
     * The Finger object with the specified ID in this frame.
     *
     * <p>Use the <code>Frame.finger()</code> to retrieve the Finger
     * object from this frame using an ID value obtained from a
     * previous frame. This always returns a Finger object,
     * but if no finger with the specified ID is present, an
     * invalid Finger object is returned.</p>
     *
     * <p>Note that ID values persist across frames, but only until
     * tracking of a particular object is lost. If tracking of a
     * finger is lost and subsequently regained, the new Finger
     * object representing that physical finger may have a different
     * ID than that representing the finger in an earlier frame.</p>
     *
     * @param id The ID value of a Finger object from a previous frame.
     * @return The Finger object with the matching ID if one exists
     * in this frame; otherwise, an invalid Finger object is returned.
     * @see Finger
     *
     */
    public finger( id:number ):Finger
    {
        var returnValue:Finger = Finger.invalid();
        var length:number = this.fingers.length;

        for ( var i:number = 0; i < length; i++ )
        {
            if ( this.fingers[ i ].id === id )
            {
                returnValue = this.fingers[ i ];
                break;
            }
        }

        return returnValue;
    }

    /**
     * The Tool object with the specified ID in this frame.
     *
     * <p>Use the <code>Frame.tool()</code> to retrieve the Tool
     * object from this frame using an ID value obtained from
     * a previous frame. This always returns a Tool
     * object, but if no tool with the specified ID is present,
     * an invalid Tool object is returned.</p>
     *
     * <p>Note that ID values persist across frames, but only until
     * tracking of a particular object is lost. If tracking of a
     * tool is lost and subsequently regained, the new Tool
     * object representing that tool may have a different ID
     * than that representing the tool in an earlier frame.</p>
     *
     * @param id The ID value of a Tool object from a previous frame.
     * @return The Tool object with the matching ID if one exists in
     * this frame; otherwise, an invalid Tool object is returned.
     * @see Tool
     *
     */
    public tool( id:number ):Tool
    {
        var returnValue:Tool = Tool.invalid();
        var length:number = this.fingers.length;

        for ( var i:number = 0; i < length; i++ )
        {
            if ( this.tools[ i ].id === id )
            {
                returnValue = this.tools[ i ];
                break;
            }
        }

        return returnValue;
    }

    /**
     * The Pointable object with the specified ID in this frame.
     *
     * <p>Use the <code>Frame.pointable()</code> to retrieve the Pointable
     * object from this frame using an ID value obtained from a previous frame.
     * This always returns a Pointable object, but if no finger
     * or tool with the specified ID is present, an invalid
     * Pointable object is returned.</p>
     *
     * <p>Note that ID values persist across frames, but only until tracking
     * of a particular object is lost. If tracking of a finger or tool is
     * lost and subsequently regained, the new Pointable object representing
     * that finger or tool may have a different ID than that representing
     * the finger or tool in an earlier frame.</p>
     *
     * @param id The ID value of a Pointable object from a previous frame.
     * @return The Pointable object with the matching ID if one exists
     * in this frame; otherwise, an invalid Pointable object is returned.
     *
     */
    public pointable( id:number ):Pointable
    {
        var returnValue:Pointable = Pointable.invalid();
        var length:number = this.pointables.length;

        for ( var i:number = 0; i < length; i++ )
        {
            if ( this.pointables[ i ].id === id )
            {
                returnValue = this.pointables[ i ];
                break;
            }
        }

        return returnValue;
    }

    /**
     * The Gesture object with the specified ID in this frame.
     *
     * <p>Use the <code>Frame.gesture()</code> to return a Gesture object in this frame
     * using an ID obtained in an earlier frame. The always returns a
     * Gesture object, but if there was no update for the gesture in this frame,
     * then an invalid Gesture object is returned.</p>
     *
     * <p>All Gesture objects representing the same recognized movement share the same ID.</p>
     *
     * @param id The ID of an Gesture object from a previous frame.
     * @return The Gesture object in the frame with the specified ID if one
     * exists; Otherwise, an Invalid Gesture object.
     *
     */
    public gesture( id:number ):Gesture
    {
        var returnValue:Gesture = Gesture.invalid();
        var length:number = this._gestures.length;

        for ( var i:number = 0; i < length; i++ )
        {
            if ( this._gestures[ i ].id === id )
            {
                returnValue = this._gestures[ i ];
                break;
            }
        }

        return returnValue;
    }

    /**
     * Returns a Gesture vector containing all gestures that have occurred
     * since the specified frame.
     *
     * <p>If no frame is specifed, the gestures recognized or continuing in
     * this frame will be returned.</p>
     *
     * @param sinceFrame An earlier Frame object. The starting frame must
     * still be in the frame history cache, which has a default length of 60 frames.
     * @return The list of gestures.
     *
     */
    public gestures( sinceFrame:Frame = null ):Gesture[]
    {
        if( !sinceFrame )
        {
            // The gestures recognized or continuing in this frame.
            return this._gestures;
        }
        else
        {
            // Returns a Gesture vector containing all gestures that have occurred since the specified frame.
            var gesturesSinceFrame:Gesture[] = [];

            for ( var i:number = 0; i < this.controller.frameHistory.length; i++ )
            {
                for ( var j:number = 0; j < this.controller.frameHistory[ i ]._gestures.length; ++j )
                    gesturesSinceFrame.push( this.controller.frameHistory[ i ]._gestures[ j ] );

                if( sinceFrame === this.controller.frameHistory[ i ] )
                    break;
            }

            return gesturesSinceFrame;
        }
    }

    /**
     * The axis of rotation derived from the overall rotational
     * motion between the current frame and the specified frame.
     *
     * <p>The returned direction vector is normalized.</p>
     *
     * <p>The Leap derives frame rotation from the relative change
     * in position and orientation of all objects detected in
     * the field of view.</p>
     *
     * <p>If either this frame or sinceFrame is an invalid Frame
     * object, or if no rotation is detected between the
     * two frames, a zero vector is returned.</p>
     *
     * @param sinceFrame The starting frame for computing the relative rotation.
     * @return A normalized direction Vector representing the axis of the
     * heuristically determined rotational change between the current
     * frame and that specified in the sinceFrame parameter.
     *
     */
    public rotationAxis( sinceFrame:Frame ):Vector3
    {
        if( sinceFrame && sinceFrame.rotation )
        {
            var vector:Vector3 = new Vector3( this.rotation.zBasis.y - sinceFrame.rotation.yBasis.z, this.rotation.xBasis.z - sinceFrame.rotation.zBasis.x, this.rotation.yBasis.x - sinceFrame.rotation.xBasis.y );
            return vector.normalized();
        }
        else
        {
            return new Vector3( 0, 0, 0 );
        }
    }

    /**
     * The angle of rotation around the rotation axis derived from the
     * overall rotational motion between the current frame and the specified frame.
     *
     * <p>The returned angle is expressed in radians measured clockwise around
     * the rotation axis (using the right-hand rule) between the
     * start and end frames. The value is always between 0 and pi radians (0 and 180 degrees).</p>
     *
     * <p>The Leap derives frame rotation from the relative change in position
     * and orientation of all objects detected in the field of view.</p>
     *
     * <p>If either this frame or sinceFrame is an invalid Frame object,
     * then the angle of rotation is zero.</p>
     *
     * @param sinceFrame The starting frame for computing the relative rotation.
     * @param axis Optional. The axis to measure rotation around.
     * @return A positive value containing the heuristically determined rotational
     * change between the current frame and that specified in the sinceFrame parameter.
     *
     */
    public rotationAngle( sinceFrame:Frame, axis:Vector3 = null ):number
    {
        if( !this.isValid() || !sinceFrame.isValid() )
            return 0.0;

        var returnValue:number = 0.0;
        var rotationSinceFrameMatrix:Matrix = this.rotationMatrix( sinceFrame );
        var cs:number = ( rotationSinceFrameMatrix.xBasis.x + rotationSinceFrameMatrix.yBasis.y + rotationSinceFrameMatrix.zBasis.z - 1 ) * 0.5;
        var angle:number = Math.acos( cs );
        returnValue = isNaN( angle ) ? 0.0 : angle;

        if( axis )
        {
            var rotAxis:Vector3 = this.rotationAxis( sinceFrame );
            returnValue *= rotAxis.dot( axis.normalized() );
        }

        return returnValue;
    }

    /**
     * The transform matrix expressing the rotation derived from
     * the change in orientation of this hand, and any associated
     * fingers and tools, between the current frame and the specified frame.
     *
     * <p>If a corresponding Hand object is not found in sinceFrame,
     * or if either this frame or sinceFrame are invalid Frame objects,
     * then this method returns an identity matrix.</p>
     *
     * @param sinceFrame
     * @return
     *
     */
    public rotationMatrix( sinceFrame:Frame ):Matrix
    {
        if( sinceFrame && sinceFrame.rotation )
        {
            return sinceFrame.rotation.multiply( new Matrix( new Vector3( this.rotation.xBasis.x, this.rotation.yBasis.x, this.rotation.zBasis.x ), new Vector3( this.rotation.xBasis.y, this.rotation.yBasis.y, this.rotation.zBasis.y ), new Vector3( this.rotation.xBasis.z, this.rotation.yBasis.z, this.rotation.zBasis.z ) ) );
        }
        else
        {
            return Matrix.identity();
        }
    }

    /**
     * The scale factor derived from the overall motion between the
     * current frame and the specified frame.
     *
     * <p>The scale factor is always positive. A value of 1.0 indicates no
     * scaling took place. Values between 0.0 and 1.0 indicate contraction
     * and values greater than 1.0 indicate expansion.</p>
     *
     * <p>The Leap derives scaling from the relative inward or outward
     * motion of all objects detected in the field of view (independent
     * of translation and rotation).</p>
     *
     * <p>If either this frame or sinceFrame is an invalid Frame object,
     * then this method returns 1.0.</p>
     *
     * @param sinceFrame The starting frame for computing the relative scaling.
     * @return A positive value representing the heuristically determined
     * scaling change ratio between the current frame and that specified
     * in the sinceFrame parameter.
     *
     */
    public scaleFactor( sinceFrame:Frame ):number
    {
        var returnValue:number;
        if ( sinceFrame && sinceFrame.scaleFactorNumber )
            returnValue = Math.exp( this.scaleFactorNumber - sinceFrame.scaleFactorNumber );
        else
            returnValue = 1;

        return returnValue;
    }

    /**
     * The change of position derived from the overall linear motion
     * between the current frame and the specified frame.
     *
     * <p>The returned translation vector provides the magnitude and
     * direction of the movement in millimeters.</p>
     *
     * <p>The Leap derives frame translation from the linear motion
     * of all objects detected in the field of view.</p>
     *
     * <p>If either this frame or sinceFrame is an invalid Frame object,
     * then this method returns a zero vector.</p>
     *
     * @param sinceFrame The starting frame for computing the translation.
     * @return A Vector representing the heuristically determined change
     * in hand position between the current frame and that specified
     * in the sinceFrame parameter.
     *
     */
    public translation( sinceFrame:Frame ):Vector3
    {
        var returnValue:Vector3;

        if ( sinceFrame.translationVector )
            returnValue = new Vector3( this.translationVector.x - sinceFrame.translationVector.x, this.translationVector.y - sinceFrame.translationVector.y, this.translationVector.z - sinceFrame.translationVector.z );
        else
            returnValue = new Vector3( 0, 0, 0 );

        return returnValue;
    }

    /**
     * Compare Frame object equality.
     *
     * <p>Two Frame objects are equal if and only if both Frame objects
     * represent the exact same frame of tracking data and both
     * Frame objects are valid.</p>
     *
     * @param other The Frame to compare with.
     * @return True; if equal. False otherwise.
     *
     */
    public isEqualTo( other:Frame ):boolean
    {
        var returnValue:boolean = true;

        if( this.id != other.id || !this.isValid() || other.isValid() )
            returnValue = false;

        return returnValue;
    }

    /**
     * Reports whether this Frame instance is valid.
     *
     * <p>A valid Frame is one generated by the LeapMotion object that contains
     * tracking data for all detected entities. An invalid Frame contains
     * no actual tracking data, but you can call its functions without risk
     * of a null pointer exception. The invalid Frame mechanism makes it
     * more convenient to track individual data across the frame history.</p>
     *
     * <p>For example, you can invoke: <code>var finger:Finger = leap.frame(n).finger(fingerID);</code>
     * for an arbitrary Frame history value, "n", without first checking whether
     * frame(n) returned a null object.<br/>
     * (You should still check that the returned Finger instance is valid.)</p>
     *
     * @return True, if this is a valid Frame object; false otherwise.
     *
     */
    public isValid():boolean
    {
        if( !this.id )
            return false;

        return true;
    }

    /**
     * Returns an invalid Frame object.
     *
     * <p>You can use the instance returned by this in comparisons
     * testing whether a given Frame instance is valid or invalid.
     * (You can also use the <code>Frame.isValid()</code> function.)</p>
     *
     * @return The invalid Frame instance.
     *
     */
    public static invalid():Frame
    {
        return new Frame();
    }
}