// Provides the Mini Pack object
//
// The mini pack works exactly as a standard O3D pack but contains additional helpers.
// This is very useful to track a set of objects that were created and to unload these objects as a whole.
//
//
// For example, you simply need to create a new MiniPack object and pass it to the Scene Loader to load
//	all the objects of the scene in your own mini pack.
// Then, if you wish to release the entire scene, you just need to call Dispose() or destroy() on the MiniPack
//	which will remove all the objects of the scene from the actual O3D pack, without touching the other objects.
//
// You can also register dependencies on O3D objects with callback functions that will be called once the
//	object is destroyed by the "Dispose()" method (cf. AddObjectDependency() at the bottom of this file).
//
//
o3djs.provide( 'patapi.minipack' );

o3djs.require( 'patapi' );

o3djs.require('o3djs.io');
o3djs.require('o3djs.error');
o3djs.require('o3djs.pack');
o3djs.require('o3djs.dump');


////////////////////////////////////////////////////////////////////////////
// Declares a class for the Mini Pack
//
//	_Name, the name of the mini pack
//	_Client, the client object that will create our internal pack
//
patapi.MiniPack = function( _Name, _Client )
{
	this.m_Name = _Name;
	this.m_Client = _Client;
	this.m_Pack = this.m_Client.createPack();
};

patapi.MiniPack.prototype = 
{
	// Gets the name of the minipack
	getName : function()			{ return this.m_Name; },

	// Gets the list of created objects in this pack
	getCreatedObjects : function()	{ return this.m_Pack.objects; },

	// Disposes of created objects
	//
	Dispose : function()
	{
//		this.Diagnostic();

		// Remove all the objects from the pack
		var	Objects = this.m_Pack.objects;
		for ( var ObjectIndex=0; ObjectIndex < Objects.length; ObjectIndex++ )
		{
			var	Object = Objects[ObjectIndex];

			// Handle transforms
			if ( "isAClassName" in Object && Object.isAClassName( "o3d.Transform" ) )
				Object.parent = null;	// Clear the parent as well so all references are released !

			// Check for dependencies on that object
			var	Dependencies = patapi.helpers.GetObjectDependencies( Object );
			if ( Dependencies )
				for ( var DependencyIndex=0; DependencyIndex < Dependencies.length; DependencyIndex++ )
				{
//window.alert( "Dependencies on object \"" + Object.name + "\" #" + DependencyIndex + " [Name] = " + Dependencies[DependencyIndex]["Name"] + " typeof([Callback]) = " + typeof(Dependencies[DependencyIndex]["Callback"]) );
//window.alert( "Dependencies on object \"" + Object.name + "\" #" + DependencyIndex + " [Name] = " + Dependencies[DependencyIndex]["Name"] + " [Callback] = " + Dependencies[DependencyIndex]["Callback"] );
					try
					{
						Dependencies[DependencyIndex]["Callback"]( Object );
					}
					catch ( _e )
					{
//window.alert( "Exception while calling back dependency function on object \"" + Object.name + "!\r\n" + _e );
					};
				}
		}

		// Perform actual destroy
		this.m_Pack.destroy();
		this.m_Pack = null;
	},

	// Creates an object of the provided type
	//	_TypeName, the type of object to create
	//	returns the created object
	//
	createObject : function( _TypeName )
	{
		return	this.m_Pack.createObject( _TypeName );
	},

	// Removes a given object
	//	_Object, the object to remove
	//
	removeObject : function( _Object )
	{
		return	this.m_Pack.removeObject( _Object );
	},


	// Creates a Bitmap from a raw image file data
	//
	createBitmapsFromRawData : function( rawData )
	{
		return	this.m_Pack.createBitmapsFromRawData( rawData );
	},

	// Creates raw data from a file at the given URL
	//
	createRawDataFromDataURL : function( dataUrl )
	{
		return	this.m_Pack.createRawDataFromDataURL( dataUrl );
	},

	// Creates a 2D texture
	//
	createTexture2D : function( width, height, format, levels, enable_render_surfaces )
	{
		return	this.m_Pack.createTexture2D( width, height, format, levels, enable_render_surfaces );
	},

	// Creates a CUBE texture
	//
	createTextureCUBE : function( edge_length, format, levels, enable_render_surfaces )
	{
		return	this.m_Pack.createTextureCUBE( edge_length, format, levels, enable_render_surfaces );
	},

	// Creates a texture from RAW data [DEPRECATED]
	//
	createTextureFromRawData : function( raw_data, generate_mips )
	{
		throw "createTextureFromRawData is DEPRECATED !"
	
		return	this.m_Pack.createTextureFromRawData( raw_data, generate_mips );
	},

	// Creates a Depth/Stencil surface
	//
	createDepthStencilSurface : function( width, height )
	{
		return	this.m_Pack.createDepthStencilSurface( width, height );
	},

	// Finds the objects of the given name and of given type within the objects created in the minipack
	//	_Name, the name of the objects to find
	//	_optClassTypeName, the optional class name of the objects to finds (if not specified, then "o3d.ObjectBase" is used)
	// returns the list of matching objects
	//
	getObjects : function( _Name, _optClassTypeName )
	{
		_optClassTypeName = _optClassTypeName ? _optClassTypeName : "o3d.ObjectBase";

		return	this.m_Pack.getObjects( _Name, _optClassTypeName );	// Use the original pack function, which is faster...

// 		var	Result = [];
// 		for ( var ObjectIndex=0; ObjectIndex < this.m_CreatedObjects.length; ObjectIndex++ )
// 		{
// 			var	Object = this.m_CreatedObjects[ObjectIndex];
// 			if ( "name" in Object && Object.name == _Name )
// 			{	// The name is good...
// 				if ( !_optClassTypeName || Object.isAClassName( _optClassTypeName ) )
// 					Result.push( Object );	// Okay, another match!
// 			}
// 		}
// 
// 		return	Result;
	},

	// Finds the objects of given type within the objects created in the minipack
	//	_ClassTypeName, the class name of the objects to finds
	// returns the list of found objects
	//
	getObjectsByClassName : function( _ClassTypeName )
	{
		return	this.m_Pack.getObjectsByClassName( _ClassTypeName );
	},

	// Creates a file request
	//	_Type, the type of file to request for
	//
	createFileRequest : function( _Type )
	{
		return	this.m_Pack.createFileRequest( _Type );
	},

	// Creates an archive request
	//
	createArchiveRequest : function()
	{
		return	this.m_Pack.createArchiveRequest();
	},

	// Prepares EVERY OBJECTS in the pack for rendering
	//	_ViewInfo, viewInfo as returned from o3djs.rendergraph.createView.
	//	_optEffectPack Pack to create effects in. If this is not specifed, our pack will be used.
	//
	preparePack : function( _ViewInfo, _optEffectPack )
	{
		var	TargetMaterialPack = _optEffectPack || this;
		var	Objects = this.m_Pack.objects;
		for ( var ObjectIndex=0; ObjectIndex < Objects.length; ObjectIndex++ )
		{
			var	Object = Objects[ObjectIndex];
			if ( Object.isAClassName( 'o3d.Material' ) )
			{	// Prepare the material by setting its draw list
				o3djs.material.prepareMaterial( TargetMaterialPack, _ViewInfo, Object );
			}
			else if ( Object.isAClassName( 'o3d.Shape' ) )
			{	// Prepare the shape's draw elements
				// (code stolen from o3djs/shape.js)

				// Create the necessary draw elements for each primitive of this shape
				Object.createDrawElements( this.m_Pack, null );

				// Build extents and register draw elements
				var Primitives = Object.elements;
				for ( var PrimitiveIndex = 0; PrimitiveIndex < Primitives.length; ++PrimitiveIndex )
				{
					var Primitive = Primitives[PrimitiveIndex];
					var boundingBox = Primitive.getBoundingBox(0);
					var minExtent = boundingBox.minExtent;
					var maxExtent = boundingBox.maxExtent;
					Primitive.boundingBox = boundingBox;
					Primitive.cull = true;
					Primitive.zSortPoint = o3djs.math.mulScalarVector( 0.5, o3djs.math.addVector( minExtent, maxExtent ) );

					// NOTE: I deliberately ignored the collada section with that "2 texture streams" bullshit as I don't care about collada
				}
			}
		}
	},

	// Prepares a list of objects in the pack for rendering
	//	_DrawListOrViewInfo, either a draw list or a view info to assign to materials
	//	_Objects, the list of objects to prepare
	//	_optCallback, an optional callback to call for every object. Prototype : function( _Object ) and returns true to keep processing the object
	//
	PrepareObjects : function( _DrawListOrViewInfo, _Objects, _optCallback )
	{
// 		if ( !_Objects )
// 			_Objects = this.m_Pack.objects;	// Use our own list of objects if empty...

		var	DrawList = null;
		if ( typeof(_DrawListOrViewInfo.isAClassName) !== typeof(undefined) && _DrawListOrViewInfo.isAClassName( 'o3d.DrawList' ) )
			DrawList = _DrawListOrViewInfo;

		var	ObjectsLength = _Objects.length;
		for ( var ObjectIndex=0; ObjectIndex < ObjectsLength; ObjectIndex++ )
		{
			var	Object = _Objects[ObjectIndex];

			// Call the callback for that object
			if ( _optCallback && !_optCallback( Object ) )
				continue;	// Skip that object...

			if ( Object.isAClassName( 'o3d.Material' ) )
			{	// Prepare the material by setting its draw list
				if ( DrawList )
					Object.drawList = DrawList;
				else
					o3djs.material.prepareMaterial( this, _DrawListOrViewInfo, Object );
			}
			else if ( Object.isAClassName( 'o3d.Shape' ) )
			{	// Prepare the shape's draw elements
				// (code stolen from o3djs/shape.js)

				// Create the necessary draw elements for each primitive of this shape
				Object.createDrawElements( this.m_Pack, null );

				// Build extents and register draw elements
				var Primitives = Object.elements;
				for ( var PrimitiveIndex = 0; PrimitiveIndex < Primitives.length; PrimitiveIndex++ )
				{
					var Primitive = Primitives[PrimitiveIndex];
					var boundingBox = Primitive.getBoundingBox(0);
					var minExtent = boundingBox.minExtent;
					var maxExtent = boundingBox.maxExtent;
					Primitive.boundingBox = boundingBox;
					Primitive.cull = true;
					Primitive.zSortPoint = o3djs.math.mulScalarVector( 0.5, o3djs.math.addVector( minExtent, maxExtent ) );

					// NOTE: I deliberately ignored the collada section with that "2 texture streams" bullshit as I don't care about collada
				}
			}
		}
	},

	// Performs diagnostic of the pack
	//
	Diagnostic : function()
	{
		var	Text = "Diagnostic for pack \"" + this.m_Name + "\" :\n\n";

		// Count objects of each sort in the actual pack
		var	Objects = this.m_Pack.objects;
		var	Count = {};

		Text += "Pack has " + Objects.length + " objects.\n";
		for ( var ObjectIndex=0; ObjectIndex < Objects.length; ObjectIndex++ )
		{
			var	ClassName = Objects[ObjectIndex].className;
			if ( !Count[ClassName] )
				Count[ClassName] = 0;
			Count[ClassName]++;
		}
		for ( var Class in Count )
			Text += "\t. " + Class + " = " + Count[Class] + "\n";

		// Show diagnostic
		window.alert( Text );
	},


	//////////////////////////////////////////////////////////////////
	// Simple proxy methods
	destroy : function()								{ this.Dispose(); },
	isAClassName : function( _ClassName )				{ return this.m_Pack.isAClassName( _ClassName ); }
};


// The "static" equivalent of the PrepareObject() method of the minipack
//	_Pack, the pack in which objects should be created
//	_DrawListOrViewInfo, either a draw list or a view info to assign to materials
//	_Objects, the list of objects to prepare
//	_optCulling, an optional flag to control culling (true if not set)
//	_optBBoxMin, an optional vector to override the bounding box MIN corner
//	_optBBoxMax, an optional vector to override the bounding box MAX corner
//
patapi.helpers.PrepareObjectsForPack = function( _Pack, _DrawListOrViewInfo, _Objects, _optCulling, _optBBoxMin, _optBBoxMax )
{
	var	DrawList = null;
	if ( typeof(_DrawListOrViewInfo.isAClassName) !== typeof(undefined) && _DrawListOrViewInfo.isAClassName( 'o3d.DrawList' ) )
		DrawList = _DrawListOrViewInfo;

	var	ObjectsLength = _Objects.length;
	for ( var ObjectIndex=0; ObjectIndex < ObjectsLength; ObjectIndex++ )
	{
		var	Object = _Objects[ObjectIndex];

		if ( Object.isAClassName( 'o3d.Material' ) )
		{	// Prepare the material by setting its draw list
			if ( DrawList )
				Object.drawList = DrawList;
			else
				o3djs.material.prepareMaterial( _Pack, _DrawListOrViewInfo, Object );
		}
		else if ( Object.isAClassName( 'o3d.Shape' ) )
		{	// Prepare the shape's draw elements
			// (code stolen from o3djs/shape.js)

			// Create the necessary draw elements for each primitive of this shape
			Object.createDrawElements( _Pack, null );

			// Build extents and register draw elements
			var Primitives = Object.elements;
			for ( var PrimitiveIndex = 0; PrimitiveIndex < Primitives.length; PrimitiveIndex++ )
			{
				var Primitive = Primitives[PrimitiveIndex];
				var boundingBox = Primitive.getBoundingBox(0);
				var minExtent = _optBBoxMin ? _optBBoxMin : boundingBox.minExtent;
				var maxExtent = _optBBoxMax ? _optBBoxMax : boundingBox.maxExtent;
				Primitive.boundingBox = boundingBox;
				Primitive.cull = _optCulling ? _optCulling : true;
				Primitive.zSortPoint = o3djs.math.mulScalarVector( 0.5, o3djs.math.addVector( minExtent, maxExtent ) );

				// NOTE: I deliberately ignored the collada section with that "2 texture streams" bullshit as I don't care about collada
			}
		}
	}
}
