import { Utils } from '../common/Utils';
import { CubeTexture } from '../resources/CubeTexture';
import { ImageFilterType, ImageWrapType, ImageMappingType } from '../const';

/**
 * @public
 * @typedef LoadTextureResourceSamplerInfo 纹理采样参数
 * @property {ImageFilterType} minFilter 放大时的过滤方式
 * @property {ImageFilterType} magFilter 缩小时的过滤方式
 * @property {ImageWrapType} wrapS 水平方向的循环方式
 * @property {ImageWrapType} wrapT 垂直方向的循环方式
 * @property {ImageMappingType} mappingType 纹理映射类型 例如普通的UV映射 立方体映射 单张图作为环境图的映射
 */

/**
 * When create texture.
 * @callback onCreateTextureCallback
 * @param {string} url The url.
 * @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
 * @returns {THING.BaseTexture}
 */

/**
 * When create texture in async mode.
 * @callback onCreateTextureAsyncCallback
 * @param {string} url The url.
 * @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
 * @returns {Promise<any>}
 */

const __ = {
	private: Symbol('private'),
}

const _defaultSampler = {
	minFilter: ImageFilterType.LinearMipmapLinearFilter,
	magFilter: ImageFilterType.LinearFilter,
	wrapS: ImageWrapType.Repeat,
	wrapT: ImageWrapType.Repeat,
	mappingType: ImageMappingType.UV
};

const _defaultCubeSampler = {
	minFilter: ImageFilterType.LinearMipmapLinearFilter,
	magFilter: ImageFilterType.LinearFilter,
	wrapS: ImageWrapType.ClampToEdge,
	wrapT: ImageWrapType.ClampToEdge,
	mappingType: ImageMappingType.CubeReflection
}

const _defaultOptions = {};

// #region Private Functions

// Build sampler key.
function _buildSamplerKey(sampler, defaultSampler) {
	if (!sampler) {
		return '';
	}

	let minFilter = sampler.minFilter ? sampler.minFilter : defaultSampler.minFilter;
	let magFilter = sampler.magFilter ? sampler.magFilter : defaultSampler.magFilter;
	let wrapS = sampler.wrapS ? sampler.wrapS : defaultSampler.wrapS;
	let wrapT = sampler.wrapT ? sampler.wrapT : defaultSampler.wrapT;
	let mappingType = sampler.mappingType ? sampler.mappingType : defaultSampler.mappingType;

	return `${minFilter};${magFilter};${wrapS};${wrapT};${mappingType}`;
}

// Build url key.
function _buildUrlKey(url, flipY) {
	if (Utils.isString(url)) {
		// gltf no url, so url is empty string
		if (url.length) {
			return url + ';' + flipY;
		}
		return '';
	}
	else if (Utils.isArray(url)) {
		return url.join(';') + flipY;
	}
	else {
		return '';
	}
}

// Update cache map.
function _updateTexture(urlKey, samplerKey, texture, map) {
	let urlMap = map.get(urlKey);
	if (!urlMap) {
		urlMap = new Map();

		map.set(urlKey, urlMap);
	}

	urlMap.set(samplerKey, texture);
}

// Clear resources.
function _clearResources(resourcesMap) {
	resourcesMap.forEach(resources => {
		resources.forEach(resource => {
			resource.release();
		});
	});

	resourcesMap.clear();
}

// #endregion

/**
 * @class BaseTextureManager
 * The base texture manager.
 * @memberof THING
 */
class BaseTextureManager {

	/**
	 * 基础纹理管理器类,用于管理纹理的加载和缓存
	 * @private
	 */
	constructor() {
		/**
		 * When create texture.
		 * @member {OnCreateTextureCallback} onCreateTexture
		 * @memberof THING.BaseTextureManager
		 * @instance
		 * @private
		 */

		/**
		 * When create texture in async mode.
		 * @member {onCreateTextureAsyncCallback} onCreateTextureAsync
		 * @memberof THING.BaseTextureManager
		 * @instance
		 * @private
		 */

		this[__.private] = {};
		let _private = this[__.private];

		_private.texturesMap = new Map();
		_private.cubeTexturesMap = new Map();
	}

	// #region Private

	_updateTexture(url, samplerKey, texture) {
		let _private = this[__.private];

		let textures = new Map();
		textures.set(samplerKey, texture);

		_private.texturesMap.set(url, textures);
	}

	_loadTexture(urlKey, samplerKey, texture) {
		const _private = this[__.private];

		this._hookTexture(texture);

		_updateTexture(urlKey, samplerKey, texture, _private.texturesMap);
	}

	_createTexture(url, urlKey, sampler, samplerKey, options) {
		// Create texture
		if (this.onCreateTexture) {
			let texture = this.onCreateTexture(url, sampler, options);
			if (texture) {
				this._loadTexture(urlKey, samplerKey, texture);
			}

			return texture;
		}
		// Create texture in async mode
		else if (this.onCreateTextureAsync) {
			return this.onCreateTextureAsync(url, sampler, options).then((texture) => {
				if (texture) {
					this._loadTexture(urlKey, samplerKey, texture);
				}

				return texture;
			});
		}
		// Do not how to create texture, we should impl create texture interface at least
		else {
			return null;
		}
	}

	_createCubeTexture(param, urlKey, sampler, samplerKey, options) {
		const _private = this[__.private];

		const cubeTexture = new CubeTexture({
			url: param.url,
			flipY: Utils.parseValue(options['flipY'], false),
			wrapTypeS: sampler.wrapS,
			wrapTypeT: sampler.wrapT,
			minFilterType: sampler.minFilter,
			magFilterType: sampler.magFilter,
			mappingType: sampler.mappingType
		})

		this._hookTexture(cubeTexture)

		_updateTexture(urlKey, samplerKey, cubeTexture, _private.cubeTexturesMap)

		return cubeTexture;
	}

	_getTexture(urlKey, samplerKey, map) {
		const texture = map.get(urlKey)?.get(samplerKey)

		if (!texture) {
			return null;
		}

		// Check whether it has been disposed
		if (texture.disposed) {
			map.get(urlKey).delete(samplerKey);
			return null;
		}

		return texture;
	}

	_hookTexture(texture) {
		let _private = this[__.private];

		// Change map key when update sampler
		texture.onUpdateSampler = (scope, sampler) => {
			const map = scope.isCubeTexture ? _private.cubeTexturesMap : _private.texturesMap;

			const urlKey = _buildUrlKey(scope.url);

			// Delete the old texture cache
			const urlMap = map.get(urlKey);
			if (!urlMap) {
				return;
			}

			for (let [samplerKey, texture] of urlMap) {
				if (texture == scope) {
					// Delete the old texture cache
					urlMap.delete(samplerKey);

					const defaultSampler = scope.isCubeTexture ? _defaultCubeSampler : _defaultSampler;

					// Add texture with new sampler values into cache
					const newSamplerKey = _buildSamplerKey(sampler, defaultSampler);
					urlMap.set(newSamplerKey, scope);

					return;
				}
			}
		};

		// Change map key when update url
		texture.onUpdateUrl = (scope) => {
			const map = scope.isCubeTexture ? _private.cubeTexturesMap : _private.texturesMap;

			for (let [urlKey, urlMap] of map) {
				for (let [samplerKey, texture] of urlMap) {
					if (texture == scope) {
						// Delete the old texture cache
						urlMap.delete(samplerKey);

						const urlKey = _buildUrlKey(scope.url);
						if (urlKey) {
							_updateTexture(urlKey, samplerKey, texture, map);
						}
						return;
					}
				}
			}
		};
	}

	// #endregion

	/**
	 * Dispose.
	 * @private
	 */
	dispose() {
		let _private = this[__.private];

		_clearResources(_private.texturesMap);
		_clearResources(_private.cubeTexturesMap);
	}

	/**
	 * Load texture from URL.
	 * @param {string} url The resource path.
	 * @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
	 * @param {object} options The options.
	 * @returns {THING.BaseTexture|Promise<any>}
	 * @private
	 */
	load(url, sampler = _defaultSampler, options = _defaultOptions) {
		let _private = this[__.private];

		// Build the url key
		const urlKey = _buildUrlKey(url, Utils.parseValue(options['flipY'], true));
		if (!urlKey) {
			return null;
		}

		const samplerKey = _buildSamplerKey(sampler, _defaultSampler);

		// Get cache map
		const cacheMap = _private.texturesMap
		let texture = this._getTexture(urlKey, samplerKey, cacheMap);
		if (!texture) {
			return this._createTexture(url, urlKey, sampler, samplerKey, options);
		}

		texture.addRef();

		return texture;
	}

	/**
	 * Load cube texture from URL.
	 * @param {string} url The resource path.
	 * @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
	 * @param {object} options The options.
	 * @returns {THING.CubeTexture|Promise<any>}
	 * @private
	 */
	loadCubeTexture(param, sampler = _defaultCubeSampler, options = _defaultOptions) {
		const _private = this[__.private];

		param = Utils.parseCubeTextureParams(param);

		// Build the url key
		const urlKey = _buildUrlKey(param.url, Utils.parseValue(options['flipY'], false));
		if (!urlKey) {
			return null;
		}

		const samplerKey = _buildSamplerKey(sampler, _defaultCubeSampler);

		// Get cache texture
		const cacheMap = _private.cubeTexturesMap
		const cubeTexture = this._getTexture(urlKey, samplerKey, cacheMap);
		if (!cubeTexture) {
			return this._createCubeTexture(param, urlKey, sampler, samplerKey, options);
		}

		cubeTexture.addRef();

		return cubeTexture;
	}

	/**
	 * Load texture from URL in async mode.
	 * @param {string} url The resource path.
	 * @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
	 * @param {object} options The options.
	 * @returns {Promise<any>}
	 * @private
	 */
	loadAsync(url, sampler = _defaultSampler, options = _defaultOptions) {
		let texture = this.load(url, sampler, options);
		if (texture) {
			if (texture.isBaseTexture) {
				return texture.waitForComplete();
			}
			else {
				return texture.then((result) => {
					return result.waitForComplete();
				});
			}
		}
		else {
			return Promise.reject(`Load '${url}' texture failed`);
		}
	}

	getTextures(){
		let _private = this[__.private];
		return _private.texturesMap;
	}

}

export { BaseTextureManager }