import { Utils } from '../common/Utils';
import { BaseComponent } from './BaseComponent';
import { AmbientLight } from '../objects/AmbientLight';
import { DirectionalLight } from '../objects/DirectionalLight';
import { HemisphereLight } from '../objects/HemisphereLight';
import { ImageMappingType, EventType } from '../const'
import { CameraPostEffectComponent } from './CameraPostEffectComponent';
import { CameraEffectComponent } from './CameraEffectComponent';

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

/**
 * 全局渲染配置组件,用于管理应用的渲染设置。
 * @class
 * @public
 * @memberof THING
 * @extends THING.BaseComponent
 */
class AppRenderConfigComponent extends BaseComponent {

	/**
	 * @constructor
	 * @public
	 * 全局渲染配置组件,用于管理应用的渲染设置。
	 */
	constructor() {
		super();

		this[__.private] = {};

		this._initPrivateMembers();
	}

	onAdd(object) {
		super.onAdd(object);

		const _private = this[__.private];

		const LightManager = Utils.getRegisteredClass('LightManager') || defaultLightManager;

		_private.lightManager = new LightManager(this._object);
		this.lightManager.initMainLight();
	}

	onRemove() {
		super.onRemove();
		this.lightManager.dispose();
	}

	/**
	 * @public
	 * 应用渲染配置的相关信息。
	 * @typedef {object} RenderSettingsInfo
	 * @property {Lights} lights 灯光配置
	 * @property {THING.CameraFogComponent} fog 雾效配置
	 * @property {THING.ImageTexture| THING.CubeTexture | Array<number>} background 背景
	 * @property {Environment} environment 场景的全局环境
	 * @property {THING.CameraPostEffectComponent} postEffects 后处理效果
	 * @property {THING.CameraEffectComponent} cameraEffects 摄像机效果
	 */

	/**
	 * @public
	 * 灯光配置
	 * @typedef {object} Lights
	 * @property {AmbientLight[]} ambientLights 场景的环境光列表。
	 * @property {DirectionalLight[]} directionalLights 场景的方向光列表。
	 * @property {HemisphereLight[]} hemisphereLights 场景的半球光列表。
	 */

	/**
	 * @public
	 * 环境配置。
	 * @typedef {object} Environment
	 * @property {THING.ImageTexture | THING.CubeTexture} envMap 场景的环境贴图。
	 * @property {number} diffuseIntensity 环境贴图的漫反射光照强度。
	 */

	/**
	 * @public
	 * 获取/设置全局渲染配置。(涉及到灯光参数时,只能控制 app.root.children 下的灯光)
	 * @type {RenderSettingsInfo}
	 */
	get renderSettings() {
		const app = this._object;

		// 集群光照
		const { enable: clusteredLightingEnable, debug: clusteredLightingDebug, clipNear, clipFar, maxLightsPerCell, cells } = app.view.getAttribute('ClusteredLighting');

		// 遮挡
		const { enable: occlusionOptionsEnable, debug: occlusionOptionsDebug, interval } = app.view.getAttribute('OcclusionOptions');

		const config = {
			'background': app.background,
			'environment': {
				'envMap': app.envMap,
				'diffuseIntensity': app.scene.envDiffuseIntensity,
				'specularIntensity': app.scene.envSpecularIntensity
			},
			'clusteredLighting': {
				'enable': clusteredLightingEnable,
				'debug': clusteredLightingDebug,
				'clipNear': clipNear,
				'clipFar': clipFar,
				'maxLightsPerCell': maxLightsPerCell,
				'cells': cells,
			},
			'occlusionOptions': {
				'enable': occlusionOptionsEnable,
				'debug': occlusionOptionsDebug,
				'interval': interval
			}
		};

		// 灯光
		config.lights = this.lightManager.getConfig();

		// 雾效
		config.fog = Utils.cloneObject(app.camera.fog.config, false);
		config.postEffects = Utils.cloneObject(app.camera.postEffect.config, false);
		config.cameraEffects = Utils.cloneObject(app.camera.effect.config, false);

		return config;
	}

	set renderSettings(config) {
		const app = this._object;

		if (config.clusteredLighting) {
			app.view.setAttribute('ClusteredLighting', config.clusteredLighting)
		}

		if (config.occlusionOptions) {
			app.view.setAttribute('OcclusionOptions', config.occlusionOptions)
		}

		if (config.background !== undefined) {
			app.background = config.background;
		}

		this._setEnvironment(config.environment)

		// 雾效

		const fog = config.fog;

		if (fog) {
			app.camera.fog.config = fog;
		}

		// 灯光
		if (config.lights) {
			this.lightManager.setLights(config.lights);
		}

		app.camera.postEffect.config = config.postEffects;
		app.camera.effect.config = config.cameraEffects;
	}

	get config() {
		let _private = this[__.private];

		const config = this.renderSettings

		// 处理背景和环境贴图 URL

		const background = config.background

		const environment = config.environment

		config.background = _private.processBackground(background);
		config.environment = _private.processEnvironment(environment);

		return config
	}

	set config(config) {
		let _private = this[__.private];

		const background = config.background;
		const environment = config.environment;

		config.background = _private.loadBackground(background);
		config.environment = _private.loadEnvMap(environment);

		this.renderSettings = config;
	}

	get lightManager() {
		const _private = this[__.private];
		return _private.lightManager;
	}

	_setEnvironment(environment) {
		if (!environment) {
			return
		}

		const app = this._object;

		if (Utils.isValid(environment.diffuseIntensity)) {
			app.scene.envDiffuseIntensity = environment.diffuseIntensity;
		}

		if (Utils.isValid(environment.specularIntensity)) {	// 兼容旧配置
			app.scene.envSpecularIntensity = environment.specularIntensity;
		}

		if (environment.envMap === undefined) {
			return
		}

		app.envMap = environment.envMap;
	}

	_initPrivateMembers() {
		const _private = this[__.private];

		const app = this._object || Utils.getCurrentApp();

		_private.getWorkPath = () => {
			let workPath = '';
			if (app && app.workPath) {
				workPath = app.workPath;
			}
			else {
				workPath = Utils.workPath;
			}

			workPath = workPath.replaceAll('\\', '/');
			if (!workPath.endsWith('/')) {
				workPath = workPath + '/';
			}
			return workPath;
		}

		_private.resolveURL = (url) => {
			// let baseURL = _private.getBaseURL();
			// if (url.startsWith(baseURL)) {
			// 	url = url.replace(baseURL, '');
			// }

			let workPath = _private.getWorkPath();
			if (workPath && url.startsWith(workPath)) {
				url = url.replace(workPath, '');
				url = "/" + url;
			}
			return url;
		}
		_private.processURI = (oriURI) => {
			let retURI = null;
			if (Array.isArray(oriURI)) {
				retURI = oriURI.map(uri => {
					uri = _private.resolveURL(uri);
					return uri;
				});
			}
			else {
				retURI = _private.resolveURL(oriURI);
			}
			return retURI;
		}

		_private.collectImage = (value) => {
			if (Utils.isNull(value)) {
				return null;
			}
			else if (Array.isArray(value)) {
				return {
					type: 'color',
					value: value
				}
			}

			let data = {};
			// 值为图像
			if (Utils.isNull(value.url)) {
				return null;
			}
			else if (value.isImageTexture) {
				if (value.mappingType === ImageMappingType.EquirectangularReflection) {
					data.type = 'cube';
				}
				else {
					data.type = 'image';
				}
			}
			else if (value.isCubeTexture) {
				data.type = 'cube'
			}

			// 是纹理
			let url = _private.processURI(value.url);
			data.value = url;

			return data;
		}

		_private.processBackground = (value) => {
			let background = _private.collectImage(value);
			return background;
		}
		_private.processEnvironment = (value) => {
			const environment = {
				type: 'none',
				value: ''
			}

			if (Utils.isNull(value)) {
				return null;
			}

			if (value.envMap != null) {
				let envMap = _private.collectImage(value.envMap);
				if (Utils.isNull(envMap)) {
					environment.value = null;
				}
				else {
					environment.type = envMap.type;
					environment.value = envMap.value;
				}
			}

			if (Utils.isValid(value.diffuseIntensity)) {
				environment.diffuseIntensity = value.diffuseIntensity;
			}

			if (Utils.isValid(value.specularIntensity)) {
				environment.specularIntensity = value.specularIntensity;
			}

			return environment;
		}

		_private.resolveLoadURL = (url) => {
			if (url._startsWith('http://') || url._startsWith('https://') || url._startsWith('file')) {
				return url;
			}
			else if (url._startsWith('/') || url._startsWith('.')) {
				return Utils.workPath._appendURL(url);
			}
			// else if (url._startsWith('/')) {
			// 	if (_private.workPath) {
			// 		return _private.workPath._appendURL(url);
			// 	}
			// 	else {
			// 		return url;
			// 	}
			// }
			// else if (url._startsWith('.')) {
			// 	if (_private.basePath) {
			// 		return _private.basePath._appendURL(url);
			// 	}
			// 	else {
			// 		return url;
			// 	}
			// }
			else {
				return Utils.resolveURL(url);
			}
		}

		_private.loadImage = (image, pmrem) => {
			if (!image) {
				return null;
			}

			if (image.type == 'color') {
				return image.value;
			}
			else if (image.type == 'image' || (image.type == 'cube' && !Array.isArray(image.value))) {
				let imageTexture = null;

				// 需要 PMREM
				if (image.type == 'cube') {
					imageTexture = _private.loadImageTexture(image, true);
				}
				else {
					imageTexture = _private.loadImageTexture(image, pmrem);
				}

				return imageTexture;
			}
			else if (image.type == 'cube') {
				let cubeTexture = _private.loadCubeTexture(image);
				return cubeTexture;
			}

			return null;
		}

		_private.loadCubeTexture = (image) => {
			let urls = image.value;
			if (!Array.isArray(urls)) {
				urls = [image.value];
			}
			for (let i = 0; i < urls.length; i++) {
				const url = urls[i];
				urls[i] = _private.resolveLoadURL(url)
			}
			let cubeTexture = app.loadCubeTexture(urls);

			return cubeTexture;
		}

		_private.loadImageTexture = (image, pmrem = false) => {
			let url = _private.resolveLoadURL(image.value);
			let imageTexture;
			if (pmrem) {
				imageTexture = app.loadImageTexture(url, { mappingType: ImageMappingType.EquirectangularReflection });
			}
			else {
				imageTexture = app.loadImageTexture(url);
			}
			return imageTexture;
		}

		_private.loadBackground = (background) => {
			if (Utils.isNull(background)) {
				return;
			}

			return _private.loadImage(background);
		}

		_private.loadEnvMap = (environment) => {
			if (Utils.isNull(environment)) {
				return;
			}
			let envMap = environment.value;
			if (Utils.isNull(envMap)) {
				environment.type = 'none';
				environment.value = null;
			}
			environment.envMap = _private.loadImage(environment, true);

			return environment;
		}
	}

}

class LightManager {

	/**
	 * 灯光管理器,用于管理应用中的各种灯光。
	 * @param {THING.App} app 应用实例
	 */
	constructor(app) {
		this.app = app;
		this.lightMap = new Map([
			['ambientLights', []],
			['directionalLights', []],
			['hemisphereLights', []]
		]);
	}

	// 首次创建时初始化
	initMainLight() {
		const app = this.app;

		if (app.scene.mainLight) {
			this.lightMap.get('directionalLights').push(app.scene.mainLight);
		}

		if (app.scene.ambientLight) {
			this.lightMap.get('ambientLights').push(app.scene.ambientLight);
		}
	}

	setLights(config) {
		const app = this.app;

		this.clearLights();

		// 不直接销毁所有灯光,而是尽量复用并赋值:移除多余灯光,按需添加新灯光。

		const lightSettings = Utils.cloneObject(config, false);

		const { ambientLights, directionalLights, hemisphereLights } = lightSettings;

		// 处理主灯光

		const mainAmbientLight = app.scene.ambientLight;
		const mainDirectionalLight = app.scene.mainLight;

		if (ambientLights.length > 0 && mainAmbientLight) {
			const mainAmbientLightConfig = ambientLights.shift();
			_setAmbientLight(mainAmbientLight, mainAmbientLightConfig);
			this.lightMap.get('ambientLights').push(mainAmbientLight);
		}

		if (directionalLights.length > 0 && mainDirectionalLight) {
			const mainDirectionalLightConfig = directionalLights.shift();
			_setDirectionalLight(mainDirectionalLight, mainDirectionalLightConfig, app);
			this.lightMap.get('directionalLights').push(mainDirectionalLight);
		}

		// 处理其他灯光

		ambientLights.forEach(ambientLightConfig => {
			this.addLight('ambientLights', ambientLightConfig);
		});

		directionalLights.forEach(directionalLightConfig => {
			this.addLight('directionalLights', directionalLightConfig);
		});

		hemisphereLights.forEach(hemisphereLightConfig => {
			this.addLight('hemisphereLights', hemisphereLightConfig);
		});
	}

	addLight(type, lightConfig, options) {
		const light = this._createLight(type, lightConfig, options);

		this.lightMap.get(type).push(light);

		return light;
	}

	_createLight(type, lightConfig, options) {
		const app = this.app;

		switch (type) {
			case 'ambientLights':
				return _createAmbientLight(lightConfig, app, options);
			case 'directionalLights':
				return _createDirectionalLight(lightConfig, app, options);
			case 'hemisphereLights':
				return _createHemisphereLight(lightConfig, app, options);
			default:
				break;
		}
	}

	removeLight(light) {
		const uuid = light.uuid;

		const type = getLightType(light);

		const lights = this.lightMap.get(type);

		const index = lights.findIndex(light => light.uuid === uuid);

		if (index > -1) {
			light.destroy();
			lights.splice(index, 1);
		}
	}

	clearLights() {
		this.lightMap.forEach((lights, type) => {
			lights.forEach(light => {
				!light.destroyed && light.destroy();
			});

			lights.length = 0;
		});
	}

	dispose() {
		this.lightMap.forEach((lights, type) => {
			lights.forEach(light => {
				!light.destroyed && light.destroy();
			});
		});

		this.lightMap.clear();
	}

	getConfig() {
		const data = {
			ambientLights: [],
			directionalLights: [],
			hemisphereLights: []
		};

		this.lightMap.forEach((lights, type) => {
			lights.forEach(light => {
				data[type].push(_getLightConfig(type, light));
			});
		});

		return data;
	}

}

let defaultLightManager = LightManager;

const _getLightConfig = (type, light) => {
	switch (type) {
		case 'ambientLights':
			return _getAmbientLightConfig(light);
		case 'directionalLights':
			return _getDirectionalLightConfig(light);
		case 'hemisphereLights':
			return _getHemisphereLightConfig(light);
		default:
			break;
	}
}

const getLightType = (light) => {
	switch (light.type) {
		case 'AmbientLight':
			return 'ambientLights';
		case 'DirectionalLight':
			return 'directionalLights';
		case 'HemisphereLight':
			return 'hemisphereLights';
		default:
			break;
	}
}

const _createAmbientLight = (lightConfig, app, options) => {
	const light = new AmbientLight(Object.assign({
		app,
		internalData: {
			'renderSettingsLight': true
		}
	}, options));

	_setAmbientLight(light, lightConfig);

	return light;
}

const _getAmbientLightConfig = (light) => {
	return {
		'name': light.name,
		'color': light.color,
		'intensity': light.intensity,
		'groups': light.groups
	};
}

const _setAmbientLight = (light, config) => {
	if (config.name) {
		light.name = config.name;
	}
	light.color = config.color;
	light.intensity = config.intensity;
	if (Utils.isValid(config.groups)) {
		light.groups = config.groups;
	}
	_triggerLightChangeEvent(light);
}

const _createDirectionalLight = (lightConfig, app, options) => {
	const light = new DirectionalLight(Object.assign({
		app,
		internalData: {
			'renderSettingsLight': true
		}
	}, options));

	light.adapter.bind(app.root);

	_setDirectionalLight(light, lightConfig, app);

	return light;
}

const _getDirectionalLightConfig = (light) => {
	return {
		name: light.name,
		enableShadow: light.castShadow,
		intensity: light.intensity,
		color: light.color,
		shadowBias: light.shadowBias,
		shadowQuality: light.shadowQuality,
		horzAngle: light.adapter.horzAngle,
		vertAngle: light.adapter.vertAngle,
		visible: light.visible,
		bindCamera: !!light.adapter.bindingCamera,
		distance: light.adapter.distance,
		farFactor: light.adapter.farFactor,
		groups: light.groups
	};
}

const _setDirectionalLight = (light, config, app) => {
	if (config.name) {
		light.name = config.name;
	}
	light.intensity = config.intensity;
	light.color = config.color;
	light.shadowQuality = config.shadowQuality;
	light.adapter.horzAngle = config.horzAngle;
	light.adapter.vertAngle = config.vertAngle;

	if (Utils.isValid(config.enableShadow)) {
		light.castShadow = config.enableShadow
	}

	if (Utils.isValid(config.castShadow)) {
		light.castShadow = config.castShadow
	}

	if (Utils.isValid(config.shadowBias)) {
		light.shadowBias = config.shadowBias;
	}

	if (Utils.isValid(config.visible)) {
		light.visible = config.visible;
	}

	if (Utils.isValid(config.distance)) {
		light.adapter.distance = config.distance;
	}

	if (Utils.isValid(config.farFactor)) {
		light.adapter.farFactor = config.farFactor;
	}

	if (Utils.isValid(config.groups)) {
		light.groups = config.groups;
	}

	if (Utils.isValid(config.bindCamera) && config.bindCamera) {
		light.adapter.bindCamera(app.camera);
	}

	if (config.enableShadow) {
		light.adapter.refresh();
	}
	_triggerLightChangeEvent(light);
}

const _createHemisphereLight = (lightConfig, app, options) => {
	const light = new HemisphereLight(Object.assign({
		app,
		internalData: {
			'renderSettingsLight': true
		}
	}, options));

	_setHemisphereLight(light, lightConfig);

	return light;
}

const _getHemisphereLightConfig = (light) => {
	return {
		name: light.name,
		intensity: light.intensity,
		color: light.color,
		groundColor: light.groundColor,
		groups: light.groups
	};
}

const _setHemisphereLight = (light, config) => {
	if (config.name) {
		light.name = config.name;
	}
	light.intensity = config.intensity;
	light.color = config.color;
	light.groundColor = config.groundColor;
	if (Utils.isValid(config.groups)) {
		light.groups = config.groups;
	}
	_triggerLightChangeEvent(light);
}
const _triggerLightChangeEvent = (light) => {
	light.app.trigger(EventType.RenderSettingsLightChange, { object: light });
}

AppRenderConfigComponent.exportProperties = [
	'renderSettings'
];

export { AppRenderConfigComponent, LightManager }