import { Utils, Object3D, Thing, ImageTexture, VideoTexture } from '@uino/thing';
import { FisheyeType, FisheyeType_Reverse } from '../../const';

/**
 * @class Projector
 * @summary 视频投影对象,用于将图像或视频纹理投射到三维场景中的指定物体表面。
 * 支持透视投影和正交投影两种模式,并可配置鱼眼效果、桶形畸变校正、遮挡处理等高级功能。
 * 投射纹理支持动态视频或静态图片,适用于监控画面投射、投影映射、AR 辅助透视等场景。
 * @memberof THING
 * @extends THING.Object3D
 * @public
 * @example
 * // 创建视频投影,将监控画面投射到指定物体
	 const projector = new THING.Projector({
		debug:true,
		image: {
			type: 'VideoTexture',
			url: 'https://uinnova-pano.oss-cn-beijing.aliyuncs.com/projector/resource/50.mp4'
		},
		receiveObjects: [obj],
		fov: 60,
		near: 0.5,
		far: 50,
		color: [1, 1, 1],
		opacity: 0.5
	});
	projector.position = [0, 10, 0];
	projector.lookAt([0, 0, 0]);
 */
class Projector extends Object3D {

	static defaultTagArray = ['Dummy'];

	/**
	 * 构造函数,用于初始化 Projector 对象。
	 * @param {object} param - 初始化参数对象,包含 Projector 的配置选项。
	 * @param {object} param.camera - 相机对象,用于渲染。
	 * @param {Array} param.receiveObjects - 接收物体的数组。数组内元素支持uuid和Object对象。
	 * @param {string} param.color - Projector 的颜色。
	 * @param {number} param.opacity - Projector 的不透明度。
	 * @param {boolean} param.debug - 是否启用调试模式。
	 * @param {number} param.fov - 视场角。
	 * @param {number} param.far - 远裁剪面。
	 * @param {number} param.near - 近裁剪面。
	 * @param {number} param.aspect - 纵横比。
	 * @param {number} param.orthoSize - 正交投影大小。
	 * @param {boolean} param.occlusion - 是否启用遮挡效果。
	 * @param {number} param.occlusionBias - 遮挡偏差。
	 * @param {number} param.occlusionQuality - 遮挡质量。
	 * @param {string} param.fisheye - 鱼眼效果类型。
	 * @param {boolean} param.barrelCorrection - 是否启用桶形畸变校正。
	 * @param {number} param.barrelFx - 桶形畸变的 X 方向参数。
	 * @param {number} param.barrelFy - 桶形畸变的 Y 方向参数。
	 * @param {number} param.barrelS - 桶形畸变的缩放参数。
	 * @param {string} param.projectionType - 投影类型。
	 * @param {object} param.image - 图像参数,支持THING.ImageTexture和THING.VideoTexture两种THINGJS对象;同时支持结构为{type,url}的数据对象,数据对象参数如下。
	 * @param {string} param.image.type - 图像类型<string>,支持'ImageTexture' 和 'VideoTexture'。
	 * @param {string} param.image.url - 图像URL<string>。
	 */
	constructor(param = {}) {
		super(param);

		const camera = this.app.camera;
		param.camera = camera;
		const node = Utils.createObject('DecalNode', { external: { uScene: this.node._uScene, param: param }});
		this._node = node;
		this.body.setNode(node);
		this._receiveObjects = [];
		this._instanceState = [];
		this._debug = false;
		this._map = null;

		if ('color' in param) {
			this.color = param['color'];
		}
		if ('opacity' in param) {
			this.opacity = param['opacity'];
		}
		if ('debug' in param) {
			this.debug = param['debug'];
		}
		if ('fov' in param) {
			this.fov = param['fov'];
		}
		if ('far' in param) {
			this.far = param['far'];
		}
		if ('near' in param) {
			this.near = param['near'];
		}
		if ('aspect' in param) {
			this.aspect = param['aspect'];
		}
		if ('orthoSize' in param) {
			this.orthoSize = param['orthoSize'];
		}
		if ('occlusion' in param) {
			this.occlusion = param['occlusion'];
		}
		if ('occlusionBias' in param) {
			this.occlusionBias = param['occlusionBias'];
		}
		if ('occlusionQuality' in param) {
			this.occlusionQuality = param['occlusionQuality'];
		}
		if ('fisheye' in param) {
			this.fisheye = param['fisheye'];
		}
		if ('barrelCorrection' in param) {
			this.barrelCorrection = param['barrelCorrection'];
		}
		if ('barrelFx' in param) {
			this.barrelFx = param['barrelFx'];
		}
		if ('barrelFy' in param) {
			this.barrelFy = param['barrelFy'];
		}
		if ('barrelS' in param) {
			this.barrelS = param['barrelS'];
		}
		if ('projectionType' in param) {
			this.projectionType = param['projectionType'];
		}

		// set time out make sure the receive objects be created
		if ('receiveObjects' in param) {
			let array = param['receiveObjects'];
			if (typeof array === 'string') {
				array = array.split(',');
			}
			Utils.setTimeout(() => {
				let receiveObjects = [];
				array.forEach(item => {
					if (item.isObject3D) {
						receiveObjects.push(item);
					}
					else if (typeof item === 'string') {
						let obj = this.app.objectManager.getBaseObjectFromUUID(item);
						if (obj) {
							receiveObjects.push(obj);
						}
					}
				})
				this.setReceiveObjects(receiveObjects);
			});
		}
		if ('image' in param) {
			let image = param['image'];
			if (image.isBaseTexture) {
				this.image = image;
			}
			else if (typeof image.type === 'string') {
				if (image.type == 'VideoTexture') {
					this.image = new VideoTexture({ url: image.url });
				}
				else {
					this.image = new ImageTexture(image.url);
				}
			}
		}
	}

	_clearCurrentObject() {
		this._node.clearCurrentObject();		// clear

		for (let i = 0; i < this._receiveObjects.length; i++) {
			if (!this._receiveObjects[i].destroyed) {
				this._receiveObjects[i].render.callbacks.beforeMakeInstancedDrawing = null;

				if (this._instanceState[i]) {
					this._receiveObjects[i].makeInstancedDrawing(true);
				}
			}
		}


		this._receiveObjects.length = 0;
		this._instanceState.length = 0;
	}

	/**
	 * @public 
	 * 设置可以接收投影的物体列表。传入的物体必须是 Object3D 的实例,或者是它们的 UUID 字符串。
	 * @param {String[]|Object3D[]} value - 接收投影的物体列表,可以是 Object3D 实例或它们的 UUID 字符串。
	 */
	async setReceiveObjects(value) {
		this._clearCurrentObject();

		if (!value || value.length <= 0) {
			return;
		}

		this._receiveObjects = value;

		this._instanceState = [];

		const temp = [];

		for (let i = 0; i < value.length; i++) {
			await value[i].waitForComplete();

			if (value[i].destroyed) {
				continue;
			}

			this._instanceState[i] = value[i].isInstancedDrawing;

			value[i].makeInstancedDrawing(false);

			value[i].render.callbacks.beforeMakeInstancedDrawing = function () {
				console.warn('Cannot change instanced state when using projector!');
				return false;
			}

			temp.push(value[i].node);
		}

		this._node.setReceiveNodes(temp);
	}

	/**
	 * Get array of nodes which receive this projector.
	 * @returns {Array}
	 */
	getReceiveObjects() {
		return this._receiveObjects;
	}

	/**
	 * 设置/获取投影类型。默认值为 'Perspective'。
	 * 支持 'Perspective'(透视投影)和 'Orthographic'(正交投影)。
	 * @type {string}
	 * @example
	 * projector.projectionType = 'Perspective';
	 * @public
	 * @example
	 * projector.projectionType = 'Orthographic';
	 * projector.orthoSize = 10;
	 */
	set projectionType(value) {
		this._node.setProjectionType(value);
	}

	get projectionType() {
		return this._node.getProjectionType();
	}

	/**
	 * 设置/获取贴花相机近平面。
	 * @type {number}
	 * @example
	 * projector.near = 0.1;
	 * @public
	 */
	set near(value) {
		this._node.setNear(value);
	}

	get near() {
		return this._node.getNear();
	}

	/**
	 * 设置/获取贴花相机远平面。
	 * @type {number}
	 * @example
	 * projector.far = 1000;
	 * @public
	 */
	set far(value) {
		this._node.setFar(value);
	}

	get far() {
		return this._node.getFar();
	}

	/**
	 * 设置/获取贴花相机视角(度)。使用此选项时相机类型为 Perspective。
	 * @type {number}
	 * @example
	 * projector.fov = 60;
	 * @public
	 */
	set fov(value) {
		this._node.setFov(value);
	}

	get fov() {
		return this._node.getFov();
	}

	/**
	 * 设置/获取贴花相机纵横比。
	 * @type {number}
	 * @example
	 * projector.aspect = 1.5;
	 * @public
	 */
	set aspect(value) {
		this._node.setAspect(value);
	}

	get aspect() {
		return this._node.getAspect();
	}

	/**
	 * 设置/获取贴花相机正交尺寸。使用此选项时相机类型为 Orthographic。
	 * @type {number}
	 * @example
	 * projector.orthoSize = 10;
	 * @public
	 */
	set orthoSize(value) {
		this._node.setOrthoSize(value);
	}

	get orthoSize() {
		return this._node.getOrthoSize();
	}

	/**
	 * 设置/获取遮挡质量,默认值为 512。可选值包括 256、512、1024、2048 和 4096。
	 * @type {number}
	 * @example
	 * projector.occlusionQuality = 1024;
	 * @public
	 */
	set occlusionQuality(value) {
		this._node.setOcclusionQuality(value);
	}

	get occlusionQuality() {
		return this._node.getOcclusionQuality();
	}

	/**
	 * 设置/获取贴图。
	 * 贴图对象,包含图像的纹理资源。
	 * @type {THING.ImageTexture}
	 * @example
	 * projector.image = new THING.ImageTexture('./path/to/image.png');
	 * @public
	 */
	set image(value) {
		this._map = value;
		this._node.setMap(value.getTextureResource());
	}

	get image() {
		return this._map;
	}

	/**
	 * 设置/获取是否使用球面空间。默认值为 false。
	 * @type {boolean}
	 * @example
	 * projector.sphereSpace = true;
	 * @public
	 */
	set sphereSpace(value) {
		this._node.setSphereSpace(value);
		this.debug = this._debug;
	}

	get sphereSpace() {
		return this._node.getSphereSpace();
	}

	/**
	 * 设置/获取不透明度。
	 * @type {number}
	 * @example
	 * projector.opacity = 0.5;
	 * @public
	 */
	set opacity(value) {
		this._node.setOpacity(value);
	}

	get opacity() {
		return this._node.getOpacity();
	}

	/**
	 * 设置/获取贴花颜色。默认值为 [1, 1, 1](白色)。
	 * @type {Array<number>}
	 * @example
	 * projector.color = [1, 0, 0];
	 * @public
	 */
	set color(value) {
		this._node.setColor(Utils.parseColor(value));
	}

	get color() {
		return this._node.getColor();
	}

	/**
	 * 设置/获取是否使用遮挡。默认值为 true。
	 * @type {boolean}
	 * @example
	 * projector.occlusion = true;
	 * @public
	 */
	set occlusion(value) {
		this._node.setOcclusion(value);
	}

	get occlusion() {
		return this._node.getOcclusion();
	}

	/**
	 * 设置/获取遮挡偏移。默认值为 -0。
	 * @type {number}
	 * @example
	 * projector.occlusionBias = -0.001;
	 * @public
	 */
	set occlusionBias(value) {
		this._node.setOcclusionBias(value);
	}

	get occlusionBias() {
		return this._node.getOcclusionBias();
	}

	/**
	 * 设置鱼眼类型。
	 * 鱼眼类型值:
	 * 1 - 前
	 * 2 - 后
	 * 3 - 左
	 * 4 - 右
	 * 5 - 底部
	 * 默认值为 false。
	 * @type {FisheyeType}
	 * @example
	 * projector.fisheye = 'Front';
	 * @public
	 */
	set fisheye(value) {
		if (value == false) {
			this._node.setFisheye(value);
		}
		else {
			this._node.setFisheye(FisheyeType[value]);
		}
	}

	get fisheye() {
		const fisheye = this._node.getFisheye()

		if (fisheye == false) {
			return fisheye;
		}
		else {
			return FisheyeType_Reverse[fisheye];
		}
	}

	/**
	 * 设置/获取是否使用桶形畸变矫正。默认值为 false。
	 * @type {boolean}
	 * @example
	 * projector.barrelCorrection = true;
	 * @public
	 */
	set barrelCorrection(value) {
		this._node.setBarrelCorrection(value);
	}

	get barrelCorrection() {
		return this._node.getBarrelCorrection();
	}

	/**
	 * 设置/获取桶形畸变 X 方向值。
	 * @type {number}
	 * @example
	 * projector.barrelFx = 1.0;
	 * @public
	 */
	set barrelFx(value) {
		this._node.setBarrelFx(value);
	}

	get barrelFx() {
		return this._node.getBarrelFx();
	}

	/**
	 * 设置/获取桶形畸变 Y 方向值。
	 * @type {number}
	 * @example
	 * projector.barrelFy = 1.0;
	 * @public
	 */
	set barrelFy(value) {
		this._node.setBarrelFy(value);
	}

	get barrelFy() {
		return this._node.getBarrelFy();
	}

	/**
	 * 设置/获取桶形畸变缩放因子。
	 * @type {number}
	 * @example
	 * projector.barrelS = 1.0;
	 * @public
	 */
	set barrelS(value) {
		this._node.setBarrelS(value);
	}

	get barrelS() {
		return this._node.getBarrelS();
	}

	/**
	 * 设置/获取是否使用调试。
	 * @type {boolean}
	 * @example
	 * projector.debug = true;
	 * @public
	 */
	set debug(value) {
		// if (value === this._debug) {
		// 	return;
		// }

		this._debug = value;

		if (value) {
			this._node.setDebug();
		}
		else {
			this.node.closeDebug();
		}

		this.updateMatrixWorld();
	}

	get debug() {
		return this._debug;
	}

	/**
	 * onDestroy callback
	 */
	onDestroy() {
		this._receiveObjects = null;
		this._map = null;
		super.onDestroy();
	}

	onExportExternalData() {
		let external = Object.assign({}, super.onExportExternalData());

		// receiveObjects
		let receiveObjectsURL = '';
		this.getReceiveObjects().forEach(obj => {
			receiveObjectsURL = receiveObjectsURL + obj.uuid + ',';
		});
		if (receiveObjectsURL) {
			external.receiveObjects = receiveObjectsURL;
		}

		// image
		if (this.image) {
			let imageType = 'ImageTexture';
			if (this.image.isVideoTexture) {
				imageType = 'VideoTexture';
			}
			external.image = {
				type: imageType,
				url: this.image.url
			}
		}

		Utils.setAttributeIfExist(external, 'projectionType', this, (v) => { v != 'Perspective' });
		Utils.setAttributeIfExist(external, 'fisheye', this, (v) => { v != false });
		Utils.setAttributeIfExist(external, 'debug', this, (v) => { v != false });
		Utils.setAttributeIfExist(external, 'color', this);
		Utils.setAttributeIfExist(external, 'opacity', this);
		Utils.setAttributeIfExist(external, 'fov', this);
		Utils.setAttributeIfExist(external, 'far', this);
		Utils.setAttributeIfExist(external, 'near', this);
		Utils.setAttributeIfExist(external, 'aspect', this);
		Utils.setAttributeIfExist(external, 'orthoSize', this);
		Utils.setAttributeIfExist(external, 'occlusion', this, (v) => { v != false });
		Utils.setAttributeIfExist(external, 'occlusionBias', this, (v) => { v != -0.0004 });
		Utils.setAttributeIfExist(external, 'occlusionQuality', this, (v) => { v != 512 });
		Utils.setAttributeIfExist(external, 'barrelCorrection', this, (v) => { v != false });
		Utils.setAttributeIfExist(external, 'barrelFx', this);
		Utils.setAttributeIfExist(external, 'barrelFy', this);
		Utils.setAttributeIfExist(external, 'barrelS', this);

		return external;
	}

}

export { Projector };