// Provides the Animations Manager object
//
o3djs.provide( 'patapi.animationsmanager' );

o3djs.require( 'patapi' );

o3djs.require('o3djs.util');
o3djs.require('o3djs.math');
o3djs.require('o3djs.pack');


////////////////////////////////////////////////////////////////////////////
// Declares a class for the effects manager
//
//	_O3DClient, the O3D client
//	_O3DPack, the O3D pack into which create the objects
//
patapi.AnimationsManager = function( _O3DClient, _O3DPack )
{
	this.m_Client = _O3DClient;			// Save the client for later use
	this.m_Pack = _O3DPack;				// Save the pack for later use

	this.m_RegisteredAnimations = [];	// The array of registered animations
	this.m_AnimName2Animation = {};		// The table mapping animation name to the animation
	this.m_Transform2Animation = {};	// The table mapping animated transform to the animation
};

patapi.AnimationsManager.prototype = 
{
	// Disposes of used resources
	// Call this when exiting the application
	//
	Dispose : function()
	{
		throw "TODO!"
	},

	// Creates a wrapped animation
	//	_Name, the name of the animation to create
	//	_Transform, the animated transform
	//	_StartTime, _StopTime, the time interval where the animation should be active
	//	returns: the created wrapped animation
	//
	CreateAnimation : function( _Name, _Transform, _StartTime, _StopTime )
	{
		return new patapi.Animation( this, _Name, _Transform, _StartTime, _StopTime );
	},

	// Creates a wrapped animation
	//	_Animation, the animation to register
	//
	RegisterAnimation : function( _Animation )
	{
		if ( !_Animation )
			return;

		this.m_RegisteredAnimations.push( _Animation );
		this.m_AnimName2Animation[_Animation.getName()] = _Animation;

		// This piece of shit of code is ugly but I had to wrap the O3D transform in a javascript object otherwise IE bugs !
		var	Transform = _Animation.getTransform();
		this.m_Transform2Animation[Transform.clientId] = _Animation;

		return	_Animation;
	},

	// Unregisters a wrapped animation
	//	_Animation, the animation to un-register
	//
	UnRegisterAnimation : function( _Animation )
	{
		if ( !_Animation )
			return;

		// Retrieve animation index
		for ( var AnimIndex=0; AnimIndex < this.m_RegisteredAnimations.length; AnimIndex++ )
			if ( this.m_RegisteredAnimations[AnimIndex] == _Animation )
			{	// Found it !
				this.m_RegisteredAnimations.splice( AnimIndex, 1 );
				break;
			}

		delete this.m_AnimName2Animation[_Animation.getName()];
		delete this.m_Transform2Animation[_Animation.getTransform().clientId]
	},

	// Function to call on every render
	// It will update every animation
	//
	Update : function( _DeltaTime )
	{
		for ( var AnimIndex=0; AnimIndex < this.m_RegisteredAnimations.length; AnimIndex++ )
			this.m_RegisteredAnimations[AnimIndex].Update( _DeltaTime );
	},

	// Finds an animation by name
	//	_Name, the name of the animation to find
	//	return the requested animation or "undefined" if none could be found
	//
	FindAnimationByName : function( _Name )
	{
		return	this.m_AnimName2Animation[_Name];
	},

	// Finds an animation by transform
	//	_Transform, the animated transform of the animation to find
	//	return the requested animation or "undefined" if none could be found
	//
	FindAnimationByTransform : function( _Transform )
	{
		return	this.m_Transform2Animation[_Transform.clientId];
	},

	// Binds the translator to a scene loader so it intercepts the creation and initialization
	//	of animated transforms and registers them automatically
	//	_SceneLoader, the patapi scene loader to bind to
	//
	BindToSceneLoader : function( _SceneLoader )
	{
		var	that = this;

		_SceneLoader.RegisterInitializationCallback( "o3d.Transform", function( _DeSerializer, _Transform, _JSON )
		{
			if ( !_JSON.custom || !('AnimDuration' in _JSON.custom) )
				return;	// Standard transform, not an animation...

			// Immediately create and store an animation
			that.RegisterAnimation( that.CreateAnimation( _Transform.name, _Transform, 0.0, parseFloat( _JSON.custom['AnimDuration'] ) ) );

			// Add a dependency to that animation
			patapi.helpers.AddObjectDependency( _Transform, "AnimationManager", function( _DisposedObject ) { that.UnRegisterAnimation( that.FindAnimationByTransform( _DisposedObject ) ); } )
		} );

		_SceneLoader.RegisterInitializationCallback( "o3d.FunctionEval", function( _DeSerializer, _FunctionEval, _JSON )
		{
			if ( !_JSON.custom || !('BoundTransform' in _JSON.custom) )
				return;	// Standard function eval, not bound to a transform...

			var	Animation = that.FindAnimationByName( _JSON.custom['BoundTransform'] );
			if ( Animation == null )
				return;	// Not registered...

			// Re-bind function eval's input to animation's time parameter
			var	InputParamFloat = _FunctionEval.getParam( 'input' );
			InputParamFloat.bind( Animation.m_TimeParam );
		} );
	}
};


// An enumerate to give informations on the way to play an animation
//
patapi.ANIMATION_PLAY_TYPE =
{
	ONCE		: 0,	// Plays the animation only once
	LOOP		: 1,	// Plays the animation continuously
	PING_PONG	: 2		// Plays the animation back and forth
};

////////////////////////////////////////////////////////////////////////////
// Declares a class holding informations about an animation
//
//	_Manager, the animation manager
//	_Name, the name to give to the animation
//	_Transform, the animated transform
//	_StartTime, _StopTime, the time interval where the animation should be active
//
patapi.Animation = function( _Manager, _Name, _Transform, _StartTime, _StopTime )
{
	this.m_Manager = _Manager;
	this.m_Name = _Name;
	this.m_Transform = _Transform;

	// Create the float param that will drive the animation
	this.m_TimeParam = this.m_Manager.m_Pack.createObject( 'o3d.ParamFloat' );

	// Initialize play params
	this.m_bPlaying = false;
	this.m_PlayType = patapi.ANIMATION_PLAY_TYPE.ONCE;
	this.m_TimeStart = _StartTime;
	this.m_TimeStop = _StopTime;
	this.m_TimeFactor = 1.0;
	this.m_CallbackStart = null;
	this.m_CallbackStop = null;
};

patapi.Animation.prototype =
{
	// Gets the animation name
	getName : function()						{ return this.m_Name; },

	// Returns the animated O3D transform
	getTransform : function()					{ return this.m_Transform; },

	// Gets the start time of the animation
	getStartTime : function()					{ return this.m_TimeStart; },

	// Gets the stop time of the animation
	getStopTime : function()					{ return this.m_TimeStop; },

	// Gets or sets the animation time
	getAnimTime : function()					{ return this.m_TimeParam.value; },
	setAnimTime : function( _Time )				{ this.m_TimeParam.value = _Time; },

	// Gets the playing state
	isPlaying : function()						{ return this.m_bPlaying; },

	// Gets or sets the time factor
	getTimeFactor : function()					{ return this.m_TimeFactor; },
	setTimeFactor : function( _Value )			{ this.m_TimeFactor = _Value; },

	// Sets the callback that will be called when the anim time goes below start time (will be called at the beginning of each loop if the animation is looping)
	// Callback prototype is : function( _Animation, _Time )
	setCallbackStart : function( _Callback )	{ this.m_CallbackStart = _Callback; },

	// Sets the callback that will be called when the anim time goes above stop time (will be called at the end of each loop if the animation is looping)
	// Callback prototype is : function( _Animation, _Time )
	setCallbackStop : function( _Callback )		{ this.m_CallbackStop = _Callback; },



	// Plays the animation
	//	_optPlayType, an optional play type (default is ONCE)
	//	_optTimeFactor, an optional time factor for the animation (default is 1)
	//
	Play : function( _optPlayType, _optTimeFactor )
	{
		this.m_PlayType = _optPlayType ? _optPlayType : patapi.ANIMATION_PLAY_TYPE.ONCE;
		this.setTimeFactor( _optTimeFactor ? _optTimeFactor : 1.0 );
		this.m_bPlaying = true;
	},

	// Pauses the animation
	//
	Pause : function()
	{
		this.m_bPlaying = false;
	},

	// Stops the animation, rewinding to the beginning for a new play
	//
	Stop : function()
	{
		this.m_bPlaying = false;
		this.m_TimeParam.value = this.m_TimeStart;	// Reset time
	},

	// Updates the animation by the provided delta time
	//
	Update : function( _DeltaTime )
	{
		if ( !this.m_bPlaying )
			return;

		var	NewTime = this.m_TimeParam.value + this.m_TimeFactor * _DeltaTime;

		// Check if we're still in animation bounds
		var	Delta = NewTime - this.m_TimeStop;
		if ( Delta < 0.0 )
		{	// Check for lower bound
			Delta = this.m_TimeStart - NewTime;
			if ( Delta > 0.0 )
			{	// We exceeded start time !

				// Handle new time
				switch ( this.m_PlayType )
				{
				case patapi.ANIMATION_PLAY_TYPE.ONCE:
					NewTime = this.m_TimeStart;	// Clamp to start time
					break;

				case patapi.ANIMATION_PLAY_TYPE.LOOP:
					NewTime = this.m_TimeStart + ((NewTime - this.m_TimeStart) % (this.m_TimeStop - this.m_TimeStart));	// Clamp within range
					break;

				case patapi.ANIMATION_PLAY_TYPE.PING_PONG:
					var	DeltaTime = ((NewTime - this.m_TimeStart) % (this.m_TimeStop - this.m_TimeStart));	// Clamp within range
					NewTime = this.m_TimeStart + DeltaTime;
					this.m_TimeFactor *= -1.0;	// Play backward
					break;
				}

				// Notify
				if ( this.m_CallbackStart )
					this.m_CallbackStart( this, NewTime );
			}
		}
		else
		{	// We exceeded stop time !

			// Handle new time
			switch ( this.m_PlayType )
			{
			case patapi.ANIMATION_PLAY_TYPE.ONCE:
				NewTime = this.m_TimeStop;	// Clamp to stop time
				break;

			case patapi.ANIMATION_PLAY_TYPE.LOOP:
				NewTime = this.m_TimeStart + ((NewTime - this.m_TimeStart) % (this.m_TimeStop - this.m_TimeStart));	// Clamp within range
				break;

			case patapi.ANIMATION_PLAY_TYPE.PING_PONG:
				var	DeltaTime = ((NewTime - this.m_TimeStart) % (this.m_TimeStop - this.m_TimeStart));	// Clamp within range
				NewTime = this.m_TimeStop - DeltaTime;
				this.m_TimeFactor *= -1.0;	// Play backward
				break;
			}

			// Notify
			if ( this.m_CallbackStop )
				this.m_CallbackStop( this, NewTime );
		}

		// Update time
		this.m_TimeParam.value = NewTime;
	},

	// Binds the current animation to the provided transform
	//	_Transform, the O3D transform whose local matrix will get its value from this animation
	//
	Bind2Transform : function( _Transform )
	{
 		var	AnimatedMatrix = this.m_Transform.getParam( 'localMatrix' );
		_Transform.getParam( 'localMatrix' ).bind( AnimatedMatrix.inputConnection );
	}
};
