package com.squidder.base.baseLoader {
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.system.LoaderContext;
	import flash.utils.Dictionary;
	
	import com.squidder.base.baseLoader.business.BaseLoaderTypeManager;
	import com.squidder.base.baseLoader.events.BaseLoaderItemEvent;
	import com.squidder.base.baseLoader.events.BaseLoaderProgressEvent;
	import com.squidder.base.baseLoader.types.BaseLoaderItem;
	import com.squidder.base.baseLoader.types.LoadedItem;
	import com.squidder.base.baseProgressManager.BaseProgressManager;	

	/**
	 * Base loader is a scaled down bulk loader. It provides only basic, non-weighted progress
	 * indicitaion, which should be fine in most situations. For more advanced feedback,
	 * check out the BulkLoader class.
	 * @author Jon Reiling
	 */
	public class BaseLoader extends EventDispatcher {

		public static const VERSION : Number = 1.0;
		
		private var _loadingItems : Array;
		private var _numberOfConnections : int;
		private var _loadedItems : int;
		private var _baseLoaderProgressManager : BaseProgressManager;
		private var _assetsById : Dictionary;

		/**
		 * Constructor.
		 * @param numberOfConnections Number of simultaneous connections to run.
		 */
		public function BaseLoader( numberOfConnections : int = 4 ) {

			_loadingItems = new Array( );
			_assetsById = new Dictionary( true );
			_baseLoaderProgressManager = new BaseProgressManager( );

			_numberOfConnections = numberOfConnections;
			_loadedItems = 0;
		}

		/**
		 * Add an asset to be loaded in.
		 * @param url URL of the asset to be loaded in.
		 * @param propsObj Property of object of the asset to be loaded in.
		 * @return The BaseLoaderItem that was created by this function.
		 */
		public function addAsset( url : String , propsObj : Object = null ) : BaseLoaderItem {
			
			var props : BaseLoaderProperties = new BaseLoaderProperties( );
			props.id = ( propsObj == null || propsObj["id"] == null ) ? url : propsObj["id"];
			props.loaderContext = ( propsObj == null || propsObj[ "loaderContext" ] == null ) ? new LoaderContext( ) : propsObj[ "loaderContext" ];
			props.isPersistent = ( props == null || propsObj[ "isPersistent" ] == null ) ? false : true;
			
			var typeString : String = ( propsObj[ "type" ] != null ) ? propsObj[ "type" ] : url;
			var itemClass : Class = BaseLoaderTypeManager.getType( typeString );
			var item : BaseLoaderItem = new itemClass( url , props );
			item.addEventListener( Event.COMPLETE , _handleItemComplete , false , 0 , true );
			item.addEventListener( ProgressEvent.PROGRESS , _handleItemProgress , false , 0 , true );
			item.addEventListener( IOErrorEvent.IO_ERROR , _handleItemError , false , 0 , true );
			
			_loadingItems.push( item );
			_baseLoaderProgressManager.addItem( _loadingItems[ props.id ] );
			_assetsById[ props.id ] = item;
			
			return item;
		}
		
		/**
		 * Add an asset that has already been loaded in elsewhere. This is useful when
		 * maintaining the transparency of accessing all assets through the getAsset function.
		 * @param content Asset that has been loaded in already.
		 * @param propsObj Property of object of the asset to be loaded in.
		 * @return The BaseLoaderItem that was created by this function.
		 */
		public function addLoadedAsset( content: * , propsObj : Object = null ) : BaseLoaderItem {
			
			var props : BaseLoaderProperties = new BaseLoaderProperties( );
			props.id = propsObj["id"];
			
			var item : LoadedItem = new LoadedItem( props.id , props );
			item.content = content;
				
			_assetsById[ props.id ] = item;
			return item;			
		}

		/**
		 * Begin the load sequence.
		 */
		public function load() : void {
			
			if ( _loadingItems.length < _numberOfConnections ) _numberOfConnections = _loadingItems.length;
			for ( var i : int = 0; i < _numberOfConnections ; i++ ) {
				
				BaseLoaderItem( _loadingItems[ i ] ).load( );
			}
		}

		/**
		 * Return the untyped asset that corresponds to the id.
		 * @param id Id of the asset. URL by default.
		 */
		public function getAsset( id : String ) : * {
			
			if ( _assetsById[ id ] == null ) return null;
			return BaseLoaderItem( _assetsById[ id ] ).content;
		}

		/**
		 * Return the loader item of the item class. Helpful if you want to add listeners
		 * to the class directly.
		 * @param id Id of the asset. URL by default.
		 */
		public function getLoaderItem( id : String ) : BaseLoaderItem {
			
			return BaseLoaderItem( _assetsById[ id ] );
		}

		
		/**
		 * Clean out the baseLoader and all of the items it contains.
		 */
		public function dispose() : void {
			
			for ( var i : int = 0 ; i < _loadingItems.length ; i++ ) {

				BaseLoaderItem( _loadingItems[ i ] ).removeEventListener( Event.COMPLETE , _handleItemComplete );
				BaseLoaderItem( _loadingItems[ i ] ).removeEventListener( ProgressEvent.PROGRESS , _handleItemProgress );
				BaseLoaderItem( _loadingItems[ i ] ).removeEventListener( IOErrorEvent.IO_ERROR , _handleItemError );
				BaseLoaderItem( _loadingItems[ i ] ).clean( );
				_loadingItems[ i ] = null;
			}
			
			_loadingItems = null;
			_assetsById = null;
			_baseLoaderProgressManager = null;
		}

		/**
		 * Load the next item in the queue.
		 */
		protected function _loadNextItemInQueue() : void {
			
			if ( _loadedItems + _numberOfConnections <= _loadingItems.length )
				BaseLoaderItem( _loadingItems[ _loadedItems + _numberOfConnections - 1 ] ).load( );
		}

		/**
		 * Handle when an item is complete.
		 */
		protected function _handleItemComplete( event : Event ) : void {
			_loadedItems++;
			_baseLoaderProgressManager.updateItemProgress( event.target , 1 );
			
			var ev : BaseLoaderItemEvent = new BaseLoaderItemEvent( BaseLoaderItemEvent.ITEM_COMPLETE , false , false );
			ev.loaderItem = BaseLoaderItem( event.target );
			dispatchEvent( ev );

			if ( _loadedItems == _loadingItems.length ) {
				dispatchEvent( new Event( Event.COMPLETE ) );
			} else {
				_loadNextItemInQueue( );
			}
		}

		/**
		 * Handle when an item broadcasts its progress.
		 */
		protected function _handleItemProgress( event : ProgressEvent ) : void {
			
			_baseLoaderProgressManager.updateItemProgress( event.target , event.bytesLoaded / event.bytesTotal );
			
			var ev : BaseLoaderProgressEvent = new BaseLoaderProgressEvent( BaseLoaderProgressEvent.PROGRESS );
			ev.percentLoaded = _baseLoaderProgressManager.getOveralProgress( );
			dispatchEvent( ev );
		}

		/**
		 * Handle item errors.
		 */
		protected function _handleItemError( event : IOErrorEvent ) : void {

			var ev : BaseLoaderItemEvent = new BaseLoaderItemEvent( BaseLoaderItemEvent.ITEM_ERROR , false , false );
			ev.loaderItem = BaseLoaderItem( event.target );
			dispatchEvent( ev );
		}		
	}
}
