/**
 * Created by rockyl on 2019-04-18.
 *
 * 资源管理
 */

import {FrameAnimation, getFrameAnimation, putFrameAnim,} from "../core/FrameAnimation";
import {createTexture, default as Texture} from '../core/Texture'
import {Sheet} from '../core/Sheet'
import {EngineConfig} from "../engine-config";

/**
 * 资源管理器
 */
export default class AssetsManager{
	private resCache = {};
	private resPath = '';
	private resCacheProxy;

	private resLoaderType;
	
	constructor(){
		this.resLoaderType = {
			'.json': this.loadJson,
			'.json5': this.loadJson5,
			'.txt': this.loadTxt,
			'.png': this.loadTexture,
			'.jpg': this.loadTexture,
			'.svg': this.loadTexture,
			'.bimg': this.loadTextureFromBlob,
			'.sht': this.loadSheet,
			'.sht-disperse': this.loadSheetDisperse,
			'.fnt': this.loadFont,
			'.anim': this.loadAnim,
			'.pfb': this.loadPrefab,
		};
	}

	/**
	 * 获取一个加载器
	 * @param ext
	 * @param url
	 */
	getLoader(ext: string, url: string): (url: string, uuid: string, cache:boolean, config:any)=>Promise<any> {
		ext = ext || url.substr(url.lastIndexOf('.'));
		return this.resLoaderType[ext] || this.loadAny;
	}

	/**
	 * 增加加载器
	 * @param ext 需加载的文件后缀
	 * @param loader 加载方法，返回携带最终资源的Promise
	 */
	addLoader(ext: string, loader: Function) {
		this.resLoaderType[ext] = loader;
	}

	/**
	 * 设置资源根路径
	 * @param path
	 */
	setResPath(path: string) {
		this.resPath = path;
	}

	/**
	 * 加载一批资源
	 * @param items 资源数组: ['aaa.png', {uuid: 'bbb', url: 'alias.png'}]
	 * @param replaceConfig 替换项配置
	 * @param progress 进度回调，参数为加载百分比
	 * @return Promise<Array<any>> 资源组
	 */
	loadResItems(items: Array<any | string>, replaceConfig?, progress?: (percentage: number) => void): Promise<Array<any>> {
		let total = items.length;
		let count = 0;

		return Promise.all(
			items.map(item => {
				let uuid, url, config, ext;
				if (typeof item === 'string') {
					url = item;
				} else {
					url = item.url;
				}
				if(replaceConfig){
					url = replaceConfig[url] || url;
				}
				if (!url) {
					return Promise.resolve();
				}
				if (typeof item === 'string') {
					uuid = this.getUUIDFromUrl(url);
				} else {
					uuid = item.uuid || this.getUUIDFromUrl(url);
					ext = item.ext;
					config = item.config;
				}
				let loader = this.getLoader(ext, url);
				return loader.call(this, url, uuid, true, config).then((res) => {
					count++;
					progress && progress(count / total);
					return res;
				})
			})
		);
	}
	
	/**
	 * 获取资源
	 * @param uuid
	 */
	getRes(uuid: string) {
		if (this.resCacheProxy) {
			return this.resCacheProxy(uuid);
		}
		return this.resCache[uuid];
	}

	setResCacheProxy(proxyFunc: Function) {
		this.resCacheProxy = proxyFunc;
	}

	/**
	 * 销毁资源
	 * @param uuidOrUuids
	 */
	destroyRes(uuidOrUuids: string | Array<string>) {
		if (Array.isArray(uuidOrUuids)) {
			while (uuidOrUuids.length > 0) {
				delete this.resCache[uuidOrUuids.pop()];
			}
		} else {
			delete this.resCache[uuidOrUuids];
		}
	}

	/**
	 * 销毁全部资源
	 */
	destroyAllRes(): any {
		for (let key in this.resCache) {
			this.destroyRes(key);
		}
	}

	/**
	 * 获取所有uuid值
	 */
	getAllResUuids(): string[] {
		return Object.keys(this.resCache);
	}

	/**
	 * 处理url
	 * @param url url
	 * @return string 处理后的url
	 */
	private resolveUrl(url: string): string {
		if (url.indexOf('//') === 0 || url.indexOf('http:') === 0 || url.indexOf('https:') === 0) {
			return url;
		}
		return this.resPath + url;
	}

	/**
	 * 根据url推断名称，只是获取短文件名
	 * @param url
	 */
	private getUUIDFromUrl(url): string {
		return url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.'))
	}

	/**
	 * 缓存资源
	 * @param res
	 * @param url
	 * @param uuid
	 */
	private cacheRes(res, url: string, uuid?: string) {
		uuid = uuid || this.getUUIDFromUrl(url);
		this.resCache[uuid] = res;
	}

	//------------loaders

	/**
	 * 加载任意网络资源
	 * @param url url
	 * @param uuid
	 * @param type 类型(json|text|arraybuffer|blob)
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @param options 请求配置
	 * @return Promise<any> 资源
	 */
	async loadAny(url: string, uuid?: string, cache: boolean = true, config?: any, options = {}, type = 'arraybuffer'): Promise<any> {
		let response = await fetch(this.resolveUrl(url), options);

		let result;
		switch (type) {
			case 'json':
				result = response.json();
				break;
			case 'text':
				result = response.text();
				break;
			case 'arraybuffer':
				result = response.arrayBuffer();
				break;
			case 'blob':
				result = response.blob();
				break;
		}

		return result;
	}

	/**
	 * 加载文本资源
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @return Promise<any> 资源
	 */
	loadTxt(url: string, uuid?: string, cache: boolean = true, config?: any): Promise<string> {
		let p = this.loadAny(url, uuid, cache, config, undefined, 'text');
		if (cache) {
			p.then(data => {
				this.cacheRes(data, url, uuid);
				return data;
			})
		}

		return p;
	}

	/**
	 * 加载json资源
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @return Promise<any> 资源
	 */
	loadJson(url: string, uuid?: string, cache: boolean = true, config?: any): Promise<any> {
		let p = this.loadAny(url, uuid, cache, config, undefined, 'json');
		if (cache) {
			p.then(data => {
				this.cacheRes(data, url, uuid);
				return data;
			})
		}

		return p;
	}

	/**
	 * 加载json5资源
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @return Promise<any> 资源
	 */
	async loadJson5(url: string, uuid?: string, cache: boolean = true, config?: any): Promise<any> {
		let txt = await this.loadTxt(url, uuid);
		const jsonData = window['eval'](`(${txt})`);
		if(cache){
			this.cacheRes(jsonData, url, uuid);
		}
		return jsonData;
	}

	/**
	 * 加载预制资源
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @return Promise<any> 资源
	 */
	async loadPrefab(url, uuid?, cache = true, config?): Promise<any> {
		let data = await this.loadJson5(url, uuid, false);
		if(cache){
			this.cacheRes(data, url, uuid);
		}
		return data;
	}

	/**
	 * 加载图集资源
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @return Promise<any> 资源
	 */
	loadSheet(url: string, uuid?: string, cache: boolean = true, config?: any): Promise<Sheet> {
		let pngFile = url.substring(0, url.lastIndexOf('.')) + '.png';
		return Promise.all([
			this.loadJson(url, null, false),
			this.loadImage(pngFile, null, false),
		]).then(
			(result) => {
				let data: any = result[0];
				let img: any = result[1];

				let sheet: Sheet = new Sheet(img, data.frames);
				sheet.generateAll();

				if (cache) {
					this.cacheRes(sheet, url, uuid);

					if (config) {
						for (let textureConfig of config.textures) {
							const {name, uuid} = textureConfig;
							const texture = sheet.getTexture(name);
							this.cacheRes(texture, name, uuid);
						}
					} else {
						let textures = sheet.getAllTextures();
						for (let key in textures) {
							this.cacheRes(textures[key], key, key);
						}
					}
				}

				return sheet;
			}
		)
	}

	/**
	 * 加载散列的图集
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @return Promise<any> 资源
	 */
	async loadSheetDisperse(url: string, uuid?: string, cache: boolean = true, config?: any): Promise<Sheet> {
		let sheet: Sheet = new Sheet();
		for (let {name, uuid} of config.textures) {
			let subUrl = url.replace('-disperse', '') + '/' + name;
			const texture = await this.loadTexture(subUrl, uuid);
			sheet.inputTexture(name, texture);
		}

		if(cache){
			this.cacheRes(sheet, url, uuid);
		}

		return sheet;
	}

	/**
	 * 加载文图字资源
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @return Promise<any> 资源
	 */
	loadFont(url: string, uuid?: string, cache: boolean = true, config?: any): Promise<Sheet> {
		return this.loadSheet(url, null, false).then(
			sheet => {
				if (cache) {
					this.cacheRes(sheet, url, uuid);
				}

				return sheet;
			}
		)
	}

	private findAnimConfig(animations, name) {
		let result;
		animations.some((item: any) => {
			if (item.name === name) {
				result = item;
				return true;
			}
		});
		return result;
	}

	/**
	 * 加载帧动画资源(多个)
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @return Promise<any> 资源
	 */
	loadAnim(url: string, uuid?: string, cache: boolean = true, config?: any): Promise<FrameAnimation[]> {
		let pngFile = url.substring(0, url.lastIndexOf('.')) + '.png';

		return <Promise<FrameAnimation[]>>Promise.all([
			this.loadJson(url, null, false),
			this.loadImage(pngFile, null, false),
		]).then(
			(result) => {
				let data: any = result[0];
				let img: any = result[1];

				putFrameAnim(img, data);

				const animations = {};
				for (let name in data.mc) {
					const animation = getFrameAnimation(name);
					if (cache) {
						let uuid = name;
						if (config) {
							const cfg = this.findAnimConfig(config.animations, name);
							uuid = cfg.uuid;
						}
						this.cacheRes(animation, name, uuid);
					}
					animations[name] = animation;
				}

				if (cache) {
					this.cacheRes(animations, url, uuid);
				}

				return animations;
			}
		)
	}

	/**
	 * 加载图片资源
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 */
	loadImage(url: string, uuid?: string, cache: boolean = true, config?: any): Promise<HTMLImageElement> {
		return new Promise<HTMLImageElement>((resolve, reject) => {
			let img = new Image();
			if (EngineConfig.imgCrossOrigin) {
				img.setAttribute('crossOrigin', 'anonymous');
			}
			img.onload = function (e) {
				resolve(img);
			};
			img.onerror = function (e) {
				reject(e);
			};
			img.src = this.resolveUrl(url);
		});
	}

	/**
	 * 加载blob图片资源
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @return Promise<any> 资源
	 */
	async loadImageFromBlob(url: string, uuid?: string, cache: boolean = true, config?: any): Promise<any> {
		try {
			const imgBlob = await this.loadAny(url, uuid, false, config, undefined, 'blob');
			return await this.blobToImage(imgBlob);
		} catch (e) {
			console.log(e);
		}
	}

	/**
	 * 加载纹理资源
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @return Promise<any> 资源
	 */
	async loadTexture(url: string, uuid?: string, cache: boolean = true, config?: any): Promise<Texture> {
		const img = await this.loadImage(url, uuid, false);
		const texture = createTexture(img);
		if (cache) {
			this.cacheRes(texture, url, uuid);
		}
		return texture;
	}

	/**
	 * 加载blob纹理资源
	 * @param url url
	 * @param uuid 唯一名
	 * @param cache 是否缓存
	 * @param config 资源配置
	 * @return Promise<any> 资源
	 */
	async loadTextureFromBlob(url: string, uuid?: string, cache: boolean = true, config?: any): Promise<Texture> {
		const img: any = await this.loadImageFromBlob(url, uuid, false);
		const texture = createTexture(img);
		if (cache) {
			this.cacheRes(texture, url, uuid);
		}
		return texture;
	}

	private fileOrBlobToDataURL(obj): Promise<string> {
		return new Promise<string>((resolve, reject) => {
			let a = new FileReader();
			a.readAsDataURL(obj);
			a.onload = function (e) {
				resolve(e.target['result']);
			};
			a.onerror = function (e) {
				reject(e);
			}
		});
	}

	private async blobToImage(blob) {
		const dataUrl = await this.fileOrBlobToDataURL(blob);

		return new Promise((resolve, reject) => {
			let img = new Image();
			if (EngineConfig.imgCrossOrigin) {
				img.setAttribute('crossOrigin', 'anonymous');
			}
			img.onload = function () {
				resolve(img);
			};
			img.onerror = function (e) {
				reject(e);
			};
			img.src = dataUrl;
		})
	}
}
