/**
 * @author mikael emtinger / http://gomo.se/
 */

THREE.Animation = function( root, data ) {

	this.root = root;
	this.data = data;
	this.hierarchy = [];

	this.startTime = 0;
	this.isPlaying = false;
	this.loop = true;

	this.offset = 0;

	// need to initialize data?

	if( !this.data.initialized ) {

		THREE.AnimationHandler.initData( this.data );

	}


	// setup hierarchy

	if ( root instanceof THREE.SkinnedMesh ) {

		for( var b = 0; b < this.root.bones.length; b++ ) {

			this.hierarchy.push( this.root.bones[ b ] );

		}

	} else {

		// parse hierarchy and match against animation (somehow)

	}

}


/*
 * Play
 */

THREE.Animation.prototype.play = function( loop ) {

	if( !this.isPlaying ) {

		this.isPlaying = true;
		this.startTime = new Date().getTime() * 0.001;

		// reset key cache

		for ( var h = 0; h < this.hierarchy.length; h++ ) {

			this.hierarchy[ h ].useQuaternion    = true;
			this.hierarchy[ h ].matrixAutoUpdate = true;

			if ( this.hierarchy[ h ].prevKey === undefined ) {

				this.hierarchy[ h ].prevKey = { pos: 0, rot: 0, scl: 0 };
				this.hierarchy[ h ].nextKey = { pos: 0, rot: 0, scl: 0 };

			}

			this.hierarchy[ h ].prevKey.pos = this.data.hierarchy[ h ].keys[ 0 ];
			this.hierarchy[ h ].prevKey.rot = this.data.hierarchy[ h ].keys[ 0 ];
			this.hierarchy[ h ].prevKey.scl = this.data.hierarchy[ h ].keys[ 0 ];

			this.hierarchy[ h ].nextKey.pos = this.getNextKeyWith( "pos", h, 1 );
			this.hierarchy[ h ].nextKey.rot = this.getNextKeyWith( "rot", h, 1 );
			this.hierarchy[ h ].nextKey.scl = this.getNextKeyWith( "scl", h, 1 );
		}

		this.update();

		THREE.AnimationHandler.add( this );

	}

};


/*
 * Pause
 */

THREE.Animation.prototype.pause = function() {

	THREE.AnimationHandler.remove( this );

	// todo
}


/*
 * Stop
 */

THREE.Animation.prototype.stop = function() {

	this.isPlaying = false;
	THREE.AnimationHandler.remove( this );
}


/*
 * Update
 */

THREE.Animation.prototype.update = function( time ) {

	// TODO: add input time

	// early out

	if( !this.isPlaying ) return;


	// vars

	var types = [ "pos", "rot", "scl" ];
	var scale;
	var relative;
	var vector;
	var prevXYZ, nextXYZ;
	var object;
	var frame;
	var JIThierarchy = this.data.JIT.hierarchy;

	// update

	var currentTime         = new Date().getTime() * 0.001 - this.startTime + this.offset;
	var unloopedCurrentTime = currentTime;


	// looped?

	if ( currentTime > this.data.length ) {

		while( currentTime > this.data.length ) {

			currentTime -= this.data.length;

		}

		this.startTime = new Date().getTime() * 0.001 - currentTime;
		currentTime    = new Date().getTime() * 0.001 - this.startTime;

	}

	frame = Math.min( parseInt( currentTime * this.data.fps ), parseInt( this.data.length * this.data.fps ) );

	// update

	for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {

		object = this.hierarchy[ h ];

		if ( JIThierarchy[ h ][ frame ] !== undefined ) {

			object.skinMatrix = JIThierarchy[ h ][ frame ];
			object.matrixAutoUpdate = false;
			object.matrixNeedsUpdate = false;

			object.skinMatrix.flattenToArrayOffset( this.root.boneMatrices, h * 16 );

		} else {

			for ( var t = 0; t < 3; t++ ) {

				// get keys

				var type    = types[ t ];
				var prevKey = object.prevKey[ type ];
				var nextKey = object.nextKey[ type ];

				// switch keys?

				if ( nextKey.time < unloopedCurrentTime ) {

					// did we loop?

					if ( currentTime < unloopedCurrentTime ) {

						if ( this.loop ) {

							prevKey = this.data.hierarchy[ h ].keys[ 0 ];
							nextKey = this.getNextKeyWith( type, h, 1 );

						} else {

							this.stop();
							return;

						}

					} else {

						do {

							prevKey = nextKey;
							nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 );

						} while( nextKey.time < currentTime )

					}

					object.prevKey[ type ] = prevKey;
					object.nextKey[ type ] = nextKey;

				}

				// interpolate rot (quaternion slerp)

				object.matrixAutoUpdate = true;
				object.matrixNeedsUpdate = true;

				scale = ( currentTime - prevKey.time ) / ( nextKey.time - prevKey.time );
				prevXYZ = prevKey[ type ];
				nextXYZ = nextKey[ type ];

				if ( type === "rot" ) {

					if ( scale < 0 || scale > 1 ) {

						console.log( "Scale out of bounds:" + scale ); 
						scale = scale < 0 ? 0 : 1;

					}

					THREE.Quaternion.slerp( prevXYZ, nextXYZ, object.quaternion, scale );

				}

				// lerp pos/scl 

				else {

					vector   = type === "pos" ? object.position : object.scale; 
					vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale;
					vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale;
					vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale;

				}

			}

		}

	}

	// update JIT?

	if ( JIThierarchy[ 0 ][ frame ] === undefined ) {

		this.hierarchy[ 0 ].update( undefined, true );

		for ( var h = 0; h < this.hierarchy.length; h++ ) {

			JIThierarchy[ h ][ frame ] = this.hierarchy[ h ].skinMatrix.clone();

		}

	}

};


/*
 * Update Object
 */

THREE.Animation.prototype.updateObject = function( h, currentTime, unloopedCurrentTime ) {}


THREE.Animation.prototype.getNextKeyWith = function( type, h, key ) {

	var keys = this.data.hierarchy[ h ].keys;

	for ( ; key < keys.length; key++ ) {

		if ( keys[ key ][ type ] !== undefined ) {

			return keys[ key ];

		}

	}

	return this.data.hierarchy[ h ].keys[ 0 ];

}
