// Provides the Scene Loader object
//
// It allows to load an archived scene in the TGZ format (like in scene.js) but
//	also allows to register some callbacks that will be called for the creation
//	or initialization of specific object types when the scene is being de-serialized.
//
// This allows for example the override of the creation of materials or effects
//	to create or initialize them in a specific manner.
//
// See also the Materials Manager that registers its own callback to replace material
//	effects from a JSON file by some other new materials created by the user.
//
o3djs.provide( 'patapi.sceneloader' );

o3djs.require( 'patapi' );

o3djs.require('o3djs.io');
o3djs.require('o3djs.loader');
o3djs.require('o3djs.texture');
o3djs.require('o3djs.error');
o3djs.require('o3djs.pack');
o3djs.require('o3djs.dump');


////////////////////////////////////////////////////////////////////////////
// Declares a class for the scene loader
//
//	_O3DClient, the O3D client
//
patapi.SceneLoader = function( _O3DClient )
{
	this.m_Client = _O3DClient;	// Save the client for later use

	this.m_CreationCallbacks = {};
	this.m_InitCallbacks = {};

	// The dictionary linking Archive URLs to actual archives
	this.m_ArchiveURL2Archive = {};
};

patapi.SceneLoader.prototype = 
{
	// Gets the archive info linked to a given scene URL (you can use that to access your scene archive and query some documents from within) (NOTE: You should only query the archive in the callback that is called once the scene is loaded)
	getArchiveInfo : function( _SceneURL )	{ return this.m_ArchiveURL2Archive[_SceneURL]; },

	// Disposes of used resources
	// Call this when exiting the application
	//
	Dispose : function()
	{
		throw "TODO!"
	},

	// Loads a scene
	//	_Pack, the pack in which to create the objects
	//	_SceneURL, the URL of the scene to load (a .O3DTGZ file)
	//	_ParentTransform, the parent transform to attach the loaded objects to
	//	_optCallback, the optional callback that will be called once the scene is ready (function prototype is "function( _Pack, _ParentTransform, _Exception, _SceneURL, _DeSerializedObjects )" and _Exception is null if no error occurred)
	//	_optTexturesRepository, the optional path to textures directory if the scene can load textures asynchronously (i.e. textures not embedded in the O3TGZ archive)
	//
	// Returns an archive info to monitor progress...
	//
	LoadScene : function( _Pack, _SceneURL, _ParentTransform, _optCallback, _optTexturesRepository )
	{
		var	that = this;

		function OnFinishedCallback( _ArchiveInfo, _Exception )
		{
			if ( _Exception )
			{	// Handle exception
				if ( _optCallback )
					_optCallback( _Pack, null, _Exception, _SceneURL );
				else
					throw "An error occurred while loading the scene archive for scene at \"" + _SceneURL + "\" !\r\n\r\nReason: " + _Exception;

				return;
			}

			// Store the Archive linked to that URL
			that.m_ArchiveURL2Archive[_SceneURL] = _ArchiveInfo;

			// De-serialize the scene
			var Collector = o3djs.error.createErrorCollector( that.m_Client );
			var	DeSerializedObjects = null;
			try
			{
				DeSerializedObjects = that.DeSerializeArchive( _Pack, _ArchiveInfo, _ParentTransform, _optTexturesRepository );
			}
			catch ( _e )
			{
				Collector.errors.push( _e );
			}

			if ( Collector.errors.length > 0 )
			{	// Join errors into a single exception message
				var	Exception = Collector.errors.join( '\n' );
				if ( _optCallback )
					_optCallback( _Pack, null, Exception, _SceneURL );
				else
					throw "An error occurred while loading the scene archive for scene at \"" + _SceneURL + "\" !\r\n\r\nReason: " + Exception;
			}

			Collector.finish();

			// Notify
			if ( _optCallback )
				_optCallback( _Pack, _ParentTransform, null, _SceneURL, DeSerializedObjects );
		}

		return o3djs.io.loadArchive( _Pack, _SceneURL, OnFinishedCallback );
	},

	// De-Serializes a downloaded archive
	//	_Pack, the pack in which to create the objects
	//	_ArchiveInfo, the archive info resulting from the loading of the scene archive
	//	_ParentTransform, the parent transform to attach the loaded objects to
	//	_optTexturesRepository, the optional path to textures repository if the scene can load textures asynchronously (i.e. textures not embedded in the O3TGZ archive)
	//
	// Returns the list of de-serialized objects
	//
	DeSerializeArchive : function( _Pack, _ArchiveInfo, _ParentTransform, _optTexturesRepository )
	{
		var	jsonFile = _ArchiveInfo.getFileByURI( "scene.json" );
		if ( !jsonFile )
			throw "Could not find \"scene.json\" in archive !";

//o3djs.dump.dump( "PARSING JSON FILE:\r\n" + jsonFile.stringValue );

		var Parsed = eval( '(' + jsonFile.stringValue + ')' );	// I LOVE that line ! ^_^
		var DeSerializer = o3djs.serialization.createDeserializer( _Pack, Parsed );

		DeSerializer.addObject( Parsed.o3d_rootObject_root, _ParentTransform );
		DeSerializer.archiveInfo = _ArchiveInfo;
	
		// Bind our registered callbacks to the deserializer to override specific data types
		function	BindCallbacksToDeSerializer( _DeSerializer )
		{
			// Register creation callbacks
			for ( var RegisteredType in this.m_CreationCallbacks )
				_DeSerializer.createCallbacks[RegisteredType] = this.m_CreationCallbacks[RegisteredType];

			// Register initialization callbacks
			for ( var RegisteredType in this.m_InitCallbacks )
				_DeSerializer.initCallbacks[RegisteredType] = this.m_InitCallbacks[RegisteredType];
		}

		BindCallbacksToDeSerializer.call( this, DeSerializer );

		// Setup asynchronous textures loading mechanism
		if ( _optTexturesRepository )
			this.SetupTextureServer_( DeSerializer, _optTexturesRepository );

		// Launch!
		while ( DeSerializer.run() );

		// Collect all objects
		var	DeSerializedObjects = [];
		for ( var ObjectID in DeSerializer.objectsById_ )
			if ( DeSerializer.objectsById_[ObjectID] )
				DeSerializedObjects.push( DeSerializer.objectsById_[ObjectID] );

// 		var objects = pack.getObjects('o3d.animSourceOwner', 'o3d.ParamObject');
// 		if (objects.length > 0)
// 		{	// Rebind the output connections of the animSource to the user's param.
// 			if (opt_options && opt_options.opt_animSource)
// 			{
// 				var animSource = objects[0].getParam('animSource');
// 				var outputConnections = animSource.outputConnections;
// 				for (var ii = 0; ii < outputConnections.length; ++ii)
// 					outputConnections[ii].bind(opt_options.opt_animSource);
// 			}
// 
// 			// Remove special object from pack.
// 			for (var ii = 0; ii < objects.length; ++ii)
// 				pack.removeObject(objects[ii]);
// 		}

		return	DeSerializedObjects;
	},

	// Registers a creation callback for the given type
	//	_O3DType, the type to register a callback for (e.g. "o3d.Material")
	//	_Callback, the callback to register
	//		Callback prototype is "function( _DeSerializer, _JSON )" with :
	//			_DeSerializer, the deserializer from which the object was read
	//			_JSON, the JSON object corresponding to the O3D object to create
	//
	RegisterCreationCallback : function( _O3DType, _Callback )
	{
		this.m_CreationCallbacks[_O3DType] = _Callback;
	},

	// Registers an initialization callback for the given type
	//	_O3DType, the type to register a callback for (e.g. "o3d.Material")
	//	_Callback, the callback to register
	//		Callback prototype is "function( _DeSerializer, _Object, _JSON )" with :
	//			_DeSerializer, the deserializer from which the object was read
	//			_Object, the O3D object that was created from de-serialization
	//			_JSON, the JSON object corresponding to the O3D object
	//
	RegisterInitializationCallback : function( _O3DType, _Callback )
	{
		this.m_InitCallbacks[_O3DType] = _Callback;
	},

	////////////////////////////////////////////////////////////////
	// DELAY-LOADED TEXTURES
	////////////////////////////////////////////////////////////////

	// Loads a texture asynchronously from the texture server
	// NOTE: To call this function, you must have called LoadScene() with a valid _optTexturesRepository parameter !
	//	_Pack, the pack into which the texture should be created
	//	_TextureFileName, the texture file name to load
	//	_bGenerateMipMaps, a boolean telling if we should generate mip-maps
	//	_Callback, a callback that will be called once the texture is ready
	//
	LoadTexture : function( _Pack, _TextureFileName, _bGenerateMipMaps, _Callback )
	{
		if ( !this.m_TexturesLoader )
			throw "You can't call LoadTexture() unless you specify a valid TexturesRepository when calling the LoadScene() function !";

		o3djs.io.loadRawData( _Pack, _TextureFileName, function( _Request, _RawData, _Exception )
		{
			if ( _Exception )
				throw "An error occurred while loading texture RAW DATA \"" + _TextureFileName + "\" : " + _Exception;

			var	Texture = o3djs.texture.createTextureFromRawData( _Pack, _RawData, _bGenerateMipMaps );

			_Pack.removeObject( _RawData );	// Remove once used...

			// Notify the texture is ready
			_Callback( Texture );
		} )
	},

	// Setups the texture server for asynchronously loaded textures
	//	_DeSerializer, the deserializer object to setup
	//	_TexturesRepository, the path to the repository of textures
	//
	SetupTextureServer_ : function( _DeSerializer, _TexturesRepository )
	{
		var	that = this;

		if ( _TexturesRepository.lastIndexOf( '/' ) != _TexturesRepository.length-1 )
			_TexturesRepository = _TexturesRepository.concat( '/' );	// Append a trailing '/' if it's missing

		// Setup a loader for textures
		this.m_TexturesLoader = o3djs.loader.createLoader( function() {} );

		// Register the creation callback for delay-loaded textures
		_DeSerializer.createCallbacks["Patapom.DelayLoadedTexture"] = function( _DeSerializer, _JSON )
		{
			return new patapi.DelayLoadedTexture( that, _DeSerializer.pack, _TexturesRepository );
		}

		// Register the initialization callback so textures can get loaded from the repository
		_DeSerializer.initCallbacks["Patapom.DelayLoadedTexture"] = function( _DeSerializer, _Object, _JSON )
		{
			_Object.StartLoading();
		}
	},

	dummy : function()	{}
};


////////////////////////////////////////////////////////////////////////////
// Declares a class for the delay loaded 2D textures
//
//	_SceneLoader, the owner scene loader
//	_Pack, the pack in which to create the texture
//	_TexturesRepository, the path to the repository of textures
//
patapi.DelayLoadedTexture = function( _SceneLoader, _Pack, _TexturesRepository )
{
	this.m_SceneLoader = _SceneLoader;
	this.m_Pack = _Pack;
	this.m_TexturesRepository = _TexturesRepository;

	this.name = null;
	this.TextureURI = null;			// No texture URI for now...
	this.GenerateMipMaps = false;	// Don't generate mips
	this.ReferencerSamplers = [];	// No referencers for now...
}

patapi.DelayLoadedTexture.prototype =
{
	// Gets or sets the texture URI
	getTextureURI : function()			{ return this.TextureURI; },
	setTextureURI : function( value )	{ this.TextureURI = value; },


	// Starts loading the texture
	StartLoading : function()
	{
		var	that = this;

		if ( this.TextureURI == null )
			throw "Texture URI is not defined !";
		if ( this.ReferencerSamplers.length == 0 )
			return;	// No use to load that texture as it's not referenced by anyone...

		// Strip the texture to get only its name
		var	TextureURI = this.TextureURI.replace( /\\/g, '/' );	// Replace backslashes by forward ones
		var	TextureFileName = TextureURI.lastIndexOf( '/' ) != -1 ? TextureURI.substring( TextureURI.lastIndexOf( '/' ) + 1 ) : TextureURI;

		// Build the actual server-side name
		var	TextureFinalName = this.m_TexturesRepository + TextureFileName;

		// Ask the textures loader to load our texture
		this.m_SceneLoader.LoadTexture( this.m_Pack, TextureFinalName, this.GenerateMipMaps, function( _Texture, _Exception )
		{
			if ( _Exception )
				throw "An error occurred while loading texture \"" + TextureFinalName + "\" : " + _Exception;

// 			if ( that.TextureURI.indexOf( "sol01.dds" ) != -1 )
// 				debugger;

// TEST
// _Texture.generateMips( 0, 4 );
// TEST

			// Assign the texture to all of its referencer samplers
			for ( var RefSamplerKey in that.ReferencerSamplers )
			{
				var	RefSampler = that.ReferencerSamplers[RefSamplerKey];
 				
// POINT testing
// RefSampler.minFilter = 3;
// RefSampler.magFilter = 2;
// RefSampler.mipFilter = 2;
				
					RefSampler.texture = _Texture;
			}
		} );
	},

	dummy : function()	{}
};


////////////////////////////////////////////////////////////////////////////
// Declares a class for the delay loaded 2D textures
//
//	_SceneLoader, the owner scene loader
//	_Pack, the pack in which to create the texture
//	_TexturesRepository, the path to the repository of textures
//
patapi.DelayLoadedTextureCUBE = function( _SceneLoader, _Pack, _TexturesRepository )
{
	this.m_SceneLoader = _SceneLoader;
	this.m_Pack = _Pack;
	this.m_TexturesRepository = _TexturesRepository;

	this.TextureURI = null;			// No texture URI for now...
	this.ReferencerSamplers = [];	// No referencers for now...
}

patapi.DelayLoadedTextureCUBE.prototype =
{
	// Gets or sets the texture URI
	getTextureURI : function()			{ return this.TextureURI; },
	setTextureURI : function( value )	{ this.TextureURI = value; },


	// Starts loading the texture
	StartLoading : function()
	{
		var	that = this;

		if ( this.TextureURI == null )
			throw "Texture URI is not defined !";
		if ( this.ReferencerSamplers.length == 0 )
			return;	// No use to load that texture as it's not referenced by anyone...

		// Strip the texture to get only its name
		var	TextureURI = this.TextureURI.replace( /\\/g, '/' );	// Replace backslashes by forward ones
		var	TextureFileName = TextureURI.lastIndexOf( '/' ) != -1 ? TextureURI.substring( TextureURI.lastIndexOf( '/' ) + 1 ) : TextureURI;

		// Build the actual server-side name
		var	TextureFinalName = this.m_TexturesRepository + TextureFileName;

		// Ask the textures loader to load our texture
		this.m_SceneLoader.LoadTexture( this.m_Pack, TextureFinalName, this.GenerateMipMaps, function( _Texture, _Exception )
		{
			if ( _Exception )
				throw "An error occurred while loading texture \"" + TextureFinalName + "\" : " + _Exception;

			// Assign the texture to all of its referencer samplers
			for ( var RefSamplerKey in that.ReferencerSamplers )
			{
				var	RefSampler = that.ReferencerSamplers[RefSamplerKey];
					RefSampler.texture = _Texture;
			}
		} );
	},

	dummy : function()	{}
};