//////////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 2014-present, Egret Technology.
//  All rights reserved.
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the Egret nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY EGRET AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
//  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
//  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//  IN NO EVENT SHALL EGRET AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
//  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;LOSS OF USE, DATA,
//  OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
//  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////////////


namespace RES {

	/**
	 * @class RES.ResourceLoader
	 * @classdesc
	 * @extends egret.EventDispatcher
	 * @private
	 */
	export class ResourceLoader extends egret.EventDispatcher{
		/**
		 * 构造函数
		 * @method RES.ResourceLoader#constructor
		 */
		public constructor(){
			super();
		}
        /**
         * 最大并发加载数
         */
        public thread: number = 2;
        /**
         * 正在加载的线程计数
         */
        private loadingCount:number = 0;
        /**
         * 一项加载结束回调函数。无论加载成功或者出错都将执行回调函数。示例：callBack(resItem:ResourceItem):void;
		 * @member {Function} RES.ResourceLoader#callBack
         */
        public callBack:Function = null;
        /**
         * RES单例的引用
		 * @member {any} RES.ResourceLoader#resInstance
         */
        public resInstance:any = null;

		/**
		 * 当前组加载的项总个数,key为groupName
		 */
		private groupTotalDic:any = {};
		/**
		 * 已经加载的项个数,key为groupName
		 */
		private numLoadedDic:any = {};
		/**
		 * 正在加载的组列表,key为groupName
		 */
		private itemListDic:any = {};
		/**
		 * 加载失败的组,key为groupName
		 */
		private groupErrorDic:any = {};

        private retryTimesDic: any = {};
        public maxRetryTimes = 3;
        private failedList: Array<ResourceItem> = new Array<ResourceItem>();

		/**
		 * 优先级队列,key为priority，value为groupName列表
		 */
		private priorityQueue:any = {};
		/**
		 * 检查指定的组是否正在加载中
		 * @method RES.ResourceLoader#isGroupInLoading
		 * @param groupName {string}
		 * @returns {boolean}
		 */
		public isGroupInLoading(groupName:string):boolean{
			return this.itemListDic[groupName]!==undefined;
		}
		/**
		 * 开始加载一组文件
		 * @method RES.ResourceLoader#loadGroup
		 * @param list {egret.Array<ResourceItem>} 加载项列表
		 * @param groupName {string} 组名
		 * @param priority {number} 加载优先级
		 */
		public loadGroup(list:Array<ResourceItem>,groupName:string,priority:number=0):void{
			if(this.itemListDic[groupName]||!groupName)
				return;
			if(!list||list.length==0){
                egret.$warn(3201, groupName);
				let event:ResourceEvent = new ResourceEvent(ResourceEvent.GROUP_LOAD_ERROR);
				event.groupName = groupName;
				this.dispatchEvent(event);
				return;
			}
			if(this.priorityQueue[priority])
				this.priorityQueue[priority].push(groupName);
			else
				this.priorityQueue[priority] = [groupName];
			this.itemListDic[groupName] = list;
            let length:number = list.length;
            for(let i:number=0;i<length;i++){
                let resItem:ResourceItem = list[i];
				resItem.groupName = groupName;
			}
			this.groupTotalDic[groupName] = list.length;
			this.numLoadedDic[groupName] = 0;
			this.next();
		}
		/**
		 * 延迟加载队列
		 */
		private lazyLoadList:Array<ResourceItem> = new Array<ResourceItem>();
		/**
		 * 加载一个文件
		 * @method RES.ResourceLoader#loadItem
		 * @param resItem {egret.ResourceItem} 要加载的项
		 */
		public loadItem(resItem:ResourceItem):void{
			this.lazyLoadList.push(resItem);
			resItem.groupName = "";
			this.next();
		}
		/**
		 * 资源解析库字典类
		 */
		private analyzerDic:any = {};
		/**
		 * 加载下一项
		 */
		private next():void{
            while(this.loadingCount<this.thread) {
                let resItem:ResourceItem = this.getOneResourceItem();
                if(!resItem)
                    break;
                this.loadingCount++;
                if(resItem.loaded){
                    this.onItemComplete(resItem);
                }
                else{
					let analyzer:AnalyzerBase = this.resInstance.$getAnalyzerByType(resItem.type);
                    analyzer.loadFile(resItem,this.onItemComplete,this);
                }
            }
		}

		/**
		 * 当前应该加载同优先级队列的第几列
		 */
		private queueIndex:number = 0;
		/**
		 * 获取下一个待加载项
		 */
		private getOneResourceItem():ResourceItem{
            if (this.failedList.length > 0)
                return this.failedList.shift();
			let maxPriority:number = Number.NEGATIVE_INFINITY;
			for(let p in this.priorityQueue){
				maxPriority = Math.max(maxPriority,<number><any> p);
			}
			let queue:any[] = this.priorityQueue[maxPriority];
			if(!queue||queue.length==0){
				if(this.lazyLoadList.length==0)
					return null;
				//后请求的先加载，以便更快获取当前需要的资源
				return this.lazyLoadList.pop();
			}
			let length:number = queue.length;
			let list:Array<ResourceItem>;
			for(let i:number=0;i<length;i++){
				if(this.queueIndex>=length)
					this.queueIndex = 0;
				list = this.itemListDic[queue[this.queueIndex]];
				if(list.length>0)
					break;
				this.queueIndex++;
			}
			if(list.length==0)
				return null;
			return list.shift();
		}
		/**
		 * 加载结束
		 */
		private onItemComplete(resItem:ResourceItem):void{
            this.loadingCount--;
			let groupName:string = resItem.groupName;
			if(!resItem.loaded){//加载失败
                let times = this.retryTimesDic[resItem.name] || 1;
                if (times > this.maxRetryTimes) {
                    delete this.retryTimesDic[resItem.name];
                    ResourceEvent.dispatchResourceEvent(this.resInstance, ResourceEvent.ITEM_LOAD_ERROR, groupName, resItem);
                }
                else {
                    this.retryTimesDic[resItem.name] = times + 1;
                    this.failedList.push(resItem);
                    this.next();
                    return;
                }
			}

			if(groupName){
				this.numLoadedDic[groupName]++;
                let itemsLoaded:number = this.numLoadedDic[groupName];
                let itemsTotal:number = this.groupTotalDic[groupName];
				if(!resItem.loaded){
					this.groupErrorDic[groupName] = true;
				}
                ResourceEvent.dispatchResourceEvent(this.resInstance,ResourceEvent.GROUP_PROGRESS,groupName,resItem,itemsLoaded,itemsTotal);
				if(itemsLoaded==itemsTotal){
					let groupError:boolean = this.groupErrorDic[groupName];
					this.removeGroupName(groupName);
					delete this.groupTotalDic[groupName];
					delete this.numLoadedDic[groupName];
					delete this.itemListDic[groupName];
					delete this.groupErrorDic[groupName];
					if(groupError){
						ResourceEvent.dispatchResourceEvent(this,ResourceEvent.GROUP_LOAD_ERROR,groupName);
					}
					else{
						ResourceEvent.dispatchResourceEvent(this,ResourceEvent.GROUP_COMPLETE,groupName);
					}
				}
			}
            else{
                this.callBack.call(this.resInstance,resItem);
            }
			this.next();
		}
		/**
		 * 从优先级队列中移除指定的组名
		 */
		private removeGroupName(groupName:string):void{
			for(let p in this.priorityQueue){
				let queue:any[] = this.priorityQueue[p];
				let index:number = 0;
				let found:boolean = false;
                let length:number = queue.length;
                for(let i:number=0;i<length;i++){
                    let name:string = queue[i];
					if(name==groupName){
						queue.splice(index,1);
						found = true;
						break;
					}
					index++;
				}
				if(found){
					if(queue.length==0){
						delete this.priorityQueue[p];
					}
					break;
				}
			}
		}
	}
}
