package org.papervision3d.materials
	{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Graphics;
	import flash.display.Loader;
	import flash.events.*;
	import flash.geom.Matrix;
	import flash.net.URLRequest;
	import flash.system.LoaderContext;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	
	import org.papervision3d.core.log.PaperLogger;
	import org.papervision3d.core.proto.MaterialObject3D;
	import org.papervision3d.core.render.command.RenderTriangle;
	import org.papervision3d.core.render.data.RenderSessionData;
	import org.papervision3d.core.render.draw.ITriangleDrawer;
	import org.papervision3d.events.FileLoadEvent;

	/**
	* The BitmapFileMaterial class creates a texture by loading a bitmap from an external file.
	*
	* Materials collect data about how objects appear when rendered.
	*/
	public class BitmapFileMaterial extends BitmapMaterial implements ITriangleDrawer
	{
		// ___________________________________________________________________ PUBLIC
		
		/**
		* The URL that has been requested.
		*/
		public var url :String = "";

		/**
		* Whether or not the texture has been loaded.
		*/
		public var loaded :Boolean;

		/**
		* Function to call when the last image has loaded.
		*/
		static public var callback :Function;

		/**
		* The color to use in materials before loading has finished.
		*/
		static public var LOADING_COLOR :int = MaterialObject3D.DEFAULT_COLOR;
		
		/**
		 * The color to use for the lines when there is an error.
		 */
		static public var ERROR_COLOR:int = MaterialObject3D.DEBUG_COLOR;
		
		/**
		 * A temporary bitmap to use if the file hasn't loaded yet. 
		 */
		static public var loadingBitmap : BitmapData = new BitmapData(1,1,false,0x000000); 
		
		
		/**
		* A texture object.
		*/		
		override public function get texture():Object
		{
			return this._texture;
		}
		
		/**
		 * Sets to check for the policy file or not.
		 */
		 
		 public var checkPolicyFile:Boolean = false;
		/**
		* @private
		*/
		override public function set texture( asset:Object ):void
		{
			if( asset is String == false )
			{
				PaperLogger.error("BitmapFileMaterial.texture requires a String for the texture");
				return;
			}
			
			bitmap   = createBitmapFromURL( String(asset) );
			_texture = asset;
		}
		
		/**
		 * Internal
		 * 
		 * Used to define if the loading had failed.
		 */
		protected var errorLoading:Boolean = false;

		// ___________________________________________________________________ NEW

		/**
		* The BitmapFileMaterial class creates a texture by loading a bitmap from an external file.
		*
		* @param	url					The URL of the requested bitmap file.
		*/
		public function BitmapFileMaterial( url :String="", precise:Boolean=false )
		{
			super(null, precise);
			
			// save URL reference
			this.url = url;

			// set the loaded flag
			this.loaded = false;

			// Loading color
			this.fillAlpha = 1;
			this.fillColor = LOADING_COLOR;
			
			// start the loading by setting the texture
			if( url.length > 0 ) texture = url;
		}

		// ___________________________________________________________________ CREATE BITMAP

		/**
		* [internal-use]
		*
		* @param	asset
		* @return
		*/
		protected function createBitmapFromURL( asset:String ):BitmapData
		{
			// Empy string?
			if( asset == "" )
			{
				return null;
			}
			// Already loaded?
			else 
			{
				
				var bmp:BitmapData = getBitmapForFilename( asset );
				if(bmp)
				{
					bitmap = super.createBitmap( bmp );
					
					// this fixes the problem where the event is not getting 
					// picked up because we usually add event listeners to the 
					// BitmapFileMaterial after we've instantiated it!
					setupAsyncLoadCompleteCallback();
					//this.loadComplete();

					return bmp;
				}
				else
				{
					queueBitmap( asset );
				}
			}
			return loadingBitmap;
		}

		// ___________________________________________________________________ QUEUE BITMAP

		private function queueBitmap( file:String ):void
		{
			// New filename?
			if( ! _subscribedMaterials[ file ] )
			{
				// Queue file
				_waitingBitmaps.push( file );

				// Init subscription
				_subscribedMaterials[ file ] = new Array();
			}

			// Subscribe material
			_subscribedMaterials[ file ].push( this );

			// Launch loading if needed
			if( _loadingIdle )
				loadNextBitmap();
		}

		// ___________________________________________________________________ LOAD NEXT BITMAP

		protected function loadNextBitmap():void
		{
			// Retrieve next filename in queue
			var file:String = _waitingBitmaps[0];

			var request:URLRequest = new URLRequest( file );
			bitmapLoader = new Loader();
			
			bitmapLoader.contentLoaderInfo.addEventListener( ProgressEvent.PROGRESS, loadBitmapProgressHandler );
			bitmapLoader.contentLoaderInfo.addEventListener( Event.COMPLETE, loadBitmapCompleteHandler );
			bitmapLoader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, loadBitmapErrorHandler );
			
			try
			{
				// Load bitmap
				var loaderContext:LoaderContext=new LoaderContext();
				loaderContext.checkPolicyFile=checkPolicyFile;

				bitmapLoader.load( request, loaderContext);

				// Save original url
				_loaderUrls[ bitmapLoader ] = file;

				// Busy loading
				_loadingIdle = false;

				PaperLogger.info( "BitmapFileMaterial: Loading bitmap from " + file );
			}
			catch( error:Error )
			{
				// Remove from queue
				_waitingBitmaps.shift();

				// Loading finished
				_loadingIdle = true;

				PaperLogger.info( "[ERROR] BitmapFileMaterial: Unable to load file " + error.message );
			}
		}
		
		// ___________________________________________________________________ LOAD BITMAP ERROR HANDLER

		protected function loadBitmapErrorHandler( e:IOErrorEvent ):void
		{
			
			var failedAsset:String = String(_waitingBitmaps.shift());
			// force the IOErrorEvent to trigger on any reload.
			// ie: no reload on retry if we don't clear these 2 statics below.
			//_loadedBitmaps[failedAsset] = null;
			_subscribedMaterials[failedAsset] = null;
			
			this.errorLoading = true;
			this.lineColor = ERROR_COLOR;
			this.lineAlpha = 1;
			this.lineThickness = 1;
			PaperLogger.error( "BitmapFileMaterial: Unable to load file " + failedAsset );
			
			removeLoaderListeners(); 
			
			// Queue finished?
			if( _waitingBitmaps.length > 0 )
			{
				// Continue loading
				loadNextBitmap();
			}
			else
			{
				// Loading finished
				_loadingIdle = true;
				
				if( Boolean( callback ) ) callback();
			}
						
			var event:FileLoadEvent = new FileLoadEvent(FileLoadEvent.LOAD_ERROR, failedAsset, -1, -1, e.text);
			
			dispatchEvent(event);
		}
		
		// ___________________________________________________________________ LOAD BITMAP PROGRESS HANDLER

		protected function loadBitmapProgressHandler( e:ProgressEvent ):void
		{
			var progressEvent:FileLoadEvent = new FileLoadEvent( FileLoadEvent.LOAD_PROGRESS, url, e.bytesLoaded, e.bytesTotal);
			dispatchEvent( progressEvent );
		}

		// ___________________________________________________________________ LOAD BITMAP COMPLETE HANDLER

		protected function loadBitmapCompleteHandler( e:Event ):void
		{
			
			var loadedBitmap:Bitmap = Bitmap( bitmapLoader.content );
			
			removeLoaderListeners(); 
			
			// Retrieve original url
			var url:String = _loaderUrls[ bitmapLoader ];

			// Retrieve loaded bitmapdata
			var bmp:BitmapData = super.createBitmap( loadedBitmap.bitmapData );
				
			// Update subscribed materials
			for each( var material:BitmapFileMaterial in _subscribedMaterials[ url ] )
			{
				material.bitmap = bmp;
				material.maxU = this.maxU;
				material.maxV = this.maxV;
				material.resetMapping();
				material.loadComplete();
			}
			
			// clear the loader from the list of materials
			_subscribedMaterials[url] = null;
			
			// Include in library
			//_loadedBitmaps[ url ] = bmp;
			_bitmapMaterials[this] = true; 
			// Remove from queue
			_waitingBitmaps.shift();

			// Queue finished?
			if( _waitingBitmaps.length > 0 )
			{
				// Continue loading
				loadNextBitmap();
			}
			else
			{
				// Loading finished
				_loadingIdle = true;
				
				if( Boolean( callback ) ) callback();
			}
		}

	// ___________________________________________________________________ SET UP ASYNCHRONOUS LOAD COMPLETE CALLBACK

		protected function setupAsyncLoadCompleteCallback() : void
		{
			var timer : Timer = new Timer(1,1); 
			timer.addEventListener(TimerEvent.TIMER_COMPLETE, dispatchAsyncLoadCompleteEvent); 
			timer.start(); 
			
		}
		
		// ___________________________________________________________________ DISPATCH ASYNCHRONOUS LOAD COMPLETE CALLBACK

		protected function dispatchAsyncLoadCompleteEvent(e : TimerEvent) : void
		{
			loadComplete(); 
			
		}

		
		// ___________________________________________________________________ LOAD COMPLETE

		protected function loadComplete():void
		{
			
			this.fillAlpha = 0;
			this.fillColor = 0;
			this.loaded = true;
			
			// add the bitmap into the dictionary for this material. 
			//_bitmapsByMaterial[this] = bitmap; 
			
			
			// Dispatch event
			var fileEvent:FileLoadEvent = new FileLoadEvent( FileLoadEvent.LOAD_COMPLETE, this.url );
			this.dispatchEvent( fileEvent );
		}
		
		protected function removeLoaderListeners() : void
		{
			bitmapLoader.contentLoaderInfo.removeEventListener( ProgressEvent.PROGRESS, loadBitmapProgressHandler );
			bitmapLoader.contentLoaderInfo.removeEventListener( Event.COMPLETE, loadBitmapCompleteHandler );
			bitmapLoader.contentLoaderInfo.removeEventListener( IOErrorEvent.IO_ERROR, loadBitmapErrorHandler );
			
		}
		
		
		/**
		 *  drawFace3D
		 */
		override public function drawTriangle(tri:RenderTriangle, graphics:Graphics, renderSessionData:RenderSessionData, altBitmap:BitmapData=null, altUV:Matrix=null):void
		{
			if (bitmap == null || errorLoading)
			{
				if(errorLoading){
					graphics.lineStyle(lineThickness,lineColor,lineAlpha);
				}
				
				graphics.beginFill( fillColor, fillAlpha );
				graphics.moveTo( tri.v0.x, tri.v0.y );
				graphics.lineTo( tri.v1.x, tri.v1.y );
				graphics.lineTo( tri.v2.x, tri.v2.y );
				graphics.lineTo( tri.v0.x, tri.v0.y );
				graphics.endFill();
				
				if(errorLoading){
					graphics.lineStyle();
				}
				
				renderSessionData.renderStatistics.triangles++;
			}
			super.drawTriangle(tri, graphics, renderSessionData);
		}
		
		
		protected function getBitmapForFilename(filename:String) : BitmapData
		{
			for (var ref : * in _bitmapMaterials)
			{
				var bfm : BitmapFileMaterial = ref as BitmapFileMaterial; 
				if(bfm.url == filename) return bfm.bitmap;
				
			}
			return null; 
		}

		// ___________________________________________________________________ PRIVATE


		//bitmap Loader
		protected var bitmapLoader : Loader; 

		// Filenames in the queue
		static protected var _waitingBitmaps :Array = new Array();

		// URLs per loader
		static protected var _loaderUrls :Dictionary = new Dictionary(true);

		// bitmaps by material
		static protected var _bitmapMaterials : Dictionary = new Dictionary(true); 

		// Materials subscribed  to the loading queue
		static protected var _subscribedMaterials :Object = new Object();

		// Loading status
		static protected var _loadingIdle :Boolean = true;
		
		public function get subscribedMaterials() : Object
		{
			
			return _subscribedMaterials; 
		
		}
		public function get bitmapMaterials() : Dictionary
		{
			
			return _bitmapMaterials; 
		
		}		
		
		override public function destroy() : void 
		{
			if(bitmapLoader) bitmapLoader.unload(); 	
			super.destroy(); 
		}
	}
}