import { StringEncoder } from '@uino/base-thing';
import { Utils } from '../common/Utils'
import { MathUtils } from '../math/MathUtils';
import { Object3D } from './Object3D';
import { RenderType, PivotMode, SideType, ScaleMode } from '../const';

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

const _spriteTypeName = StringEncoder.toText("<@secret Sprite>");

const _defaultPlaneOptions = {
	renderLayer: 1,
	envMap: false,
	lights: false,
};

// #endregion

/**
 * Marker - 标记
 *
 * 在场景中显示图像标记对象,支持精灵模式和平面模式,可自定义缩放和旋转。
 * @class Marker
 * @memberof THING
 * @extends THING.Object3D
 * @public
 * @example
 const marker = new THING.Marker({
   image: './images/marker.png',
   size: [1, 1],
   position: [0, 1, 0],
 });
 */
class Marker extends Object3D {

	/**
	 * 构造方法
	 * </br>在场景中显示图像的标记对象。
	 * @param {object} param 初始化参数,除Object3D基础的初始化参数以外的参数
	 * @param {boolean} [param.autoFitBodyScale=false] 是否自适应body的缩放
	 * @param {number} [param.scaleFactor=0.01] 缩放系数
	 * @param {THING.RenderType} [param.renderType=THING.RenderType.Sprite] 渲染类型 平面或精灵
	 * @deprecated @param {THING.PivotMode} [param.pivotMode=THING.PivotMode.Auto] 轴心点类型
	 * @param {number} [param.spriteRotation=0] 精灵模式下的旋转角度
	 * @constructor
	 * @public
	 */
	constructor(param = {}) {
		super(Utils.cloneObject(param));

		this._autoFitBodyScale = Utils.parseValue(param['autoFitBodyScale'], false);
		this._scaleFactor = Utils.parseValue(param['scaleFactor'], 0.01);
		this._renderType = Utils.parseValue(param['renderType'], RenderType.Sprite);
		this._pivotMode = Utils.parseValue(param['pivotMode'] || param['pivotModeType'], PivotMode.Auto);
		this._spriteRotation = Utils.parseValue(param['spriteRotation'], 0);
		this._scaleMode = Utils.parseValue(param['scaleMode'], ScaleMode.ScaleFactor);

		this._onBeforeFitBodyScale = null;
		this._onAfterFitBodyScale = null;

		let pivot = param['pivot'];
		if (pivot) {
			this._updatePivot(pivot);
		}
	}

	// #region Private Functions

	_useSpriteCenterAsPivot() {
		if (this.renderType != RenderType.Sprite) {
			return false;
		}

		if (this.pivotMode != PivotMode.Auto) {
			return false;
		}

		return true;
	}

	_fitBodyScale() {
		let image = this.style.image;
		if (!image) {
			return;
		}

		image.waitForComplete().then(() => {
			if (this.destroyed) {
				return;
			}

			// Get the image size
			let width = image.width;
			let height = image.height;
			if (!width || !height) {
				return;
			}

			// Notify we are going to set body scale
			if (this._onBeforeFitBodyScale) {
				this._onBeforeFitBodyScale({ object: this, image });
			}

			let _imgSize = this._calcSize(width, height);
			width = _imgSize[0];
			height = _imgSize[1];

			// Ignore parent scale
			let parent = this.parent;
			if (parent) {
				let parentScale = parent.scale;

				width /= parentScale[0];
				height /= parentScale[1];
			}

			// Update body scale to fit ratio
			this.body.localScale = [width, height, 1];

			// Notify we finished to set body scale
			if (this._onAfterFitBodyScale) {
				this._onAfterFitBodyScale({ object: this, image });
			}

			// Refresh keep size
			let keepSizeInfo = this.transform.keepSizeInfo;
			if (keepSizeInfo) {
				keepSizeInfo.refresh();
			}
		});
	}

	_updatePivot(pivot) {
		this.clearPivot();

		// Get body node
		let bodyNode = this.bodyNode;

		// It's sprite render type
		if (this._useSpriteCenterAsPivot()) {
			if (pivot) {
				bodyNode.setAttribute('Center', [pivot[0], pivot[1]]);
			}

			bodyNode.setAttribute('Rotation', MathUtils.degToRad(this._spriteRotation));

			bodyNode.setWorldPosition(this.position);

			if (this._autoFitBodyScale) {
				this._fitBodyScale();
			}
		}
		else {
			if (this._autoFitBodyScale) {
				this._fitBodyScale();
			}

			if (pivot) {
				this.pivot = pivot;
			}
		}
	}

	_refreshRenderableNode(options) {
		let renderType = this.renderType;

		// Create render node
		if (renderType == RenderType.Sprite) {
			// If user provide renderable node then skip to create it
			let renderableNode = options['renderableNode'];
			if (!renderableNode) {
				if (this.bodyNode.getType() != _spriteTypeName) {
					let node = Utils.createObject(_spriteTypeName, { ...this.external, app: this.app });
					this.body.setNode(node);
				}
			}

			var spriteRotation = options['spriteRotation'];
			if (spriteRotation) {
				this.bodyNode.setAttribute('Rotation', MathUtils.degToRad(spriteRotation));
			}
		}
		else if (renderType == RenderType.Plane) {
			// If user provide renderable node then skip to create it
			let renderableNode = options['renderableNode'];
			if (!renderableNode) {
				if (this.bodyNode.getType() != 'Node') {
					let node = this.app.resourceManager.parseModel('Plane', _defaultPlaneOptions);
					this.body.setNode(node);
				}
			}
		}

		// Get the pivot and prepare to set it (in delay mode)
		let pivot = options['pivot'];
		if (pivot) {
			this._updatePivot(pivot);
		}
	}

	_calcSize(width, height) {
		switch (this._scaleMode) {
			case ScaleMode.ScaleRatio:
				if (width >= height) {
					const _ratio = width / height;
					width = _ratio * this._scaleFactor;
					height = width / _ratio;
				}
				else {
					const _ratio = height / width;
					height = _ratio * this._scaleFactor;
					width = height / _ratio;
				}
				break;
			case ScaleMode.ScaleFixed:
				if (width >= height) {
					height = height / width * this._scaleFactor;
					width = this._scaleFactor;
				}
				else {
					width = width / height * this._scaleFactor;
					height = this._scaleFactor;
				}
				break;

			default:
				width *= this._scaleFactor;
				height *= this._scaleFactor;
				break;
		}
		return [width, height]
	}

	// #endregion

	// #region Overrides

	onBeforeSetup(param) {
		super.onBeforeSetup(param);
		// If user provide renderable node then skip to create it
		let renderableNode = param['renderableNode'];
		if (renderableNode) {
			return;
		}

		// Create render node
		let renderType = Utils.parseValue(param['renderType'], RenderType.Sprite);
		switch (renderType) {
			case RenderType.Sprite:
				let extras = param['extras'] || param['external'];
				extras = extras ? Utils.cloneObject(extras) : {};
				renderableNode = Utils.createObject(_spriteTypeName, extras);
				break;

			case RenderType.Plane:
				renderableNode = this.app.resourceManager.parseModel('Plane', _defaultPlaneOptions);
				break;

			default:
				break;
		}

		param['renderableNode'] = renderableNode;
	}

	onSetupStyle(param) {
		let style = param['style'];
		if (style) {
			// Enable transparent mode as default
			if (style.transparent === undefined) {
				style.transparent = true;
			}

			// Enable double side mode as default
			if (style.sideType === undefined) {
				style.sideType = SideType.Double;
			}

			// Disable ligths mode as default
			if (style.lights === undefined) {
				style.lights = false;
			}
		}
		else {
			param['style'] = {
				transparent: true,
				sideType: SideType.Double
			}
		}

		super.onSetupStyle(param);
	}

	onCopy(object) {
		super.onCopy(object);

		this._autoFitBodyScale = object.autoFitBodyScale;
		this._scaleFactor = object.scaleFactor;
		this._renderType = object.renderType;
		this._pivotMode = object.pivotMode;
		this._spriteRotation = object.spriteRotation;

		this.onRefresh();
	}

	superOnRefresh() {
		super.onRefresh();
	}

	onRefresh() {
		if (this._autoFitBodyScale) {
			this._fitBodyScale();
		}

		super.onRefresh();
	}

	/**
	 * When load reosurce.
	 * @param {object} options The options to load.
	 * @param {Function} resolve The promise resolve callback function.
	 * @param {Function} reject The promise reject callback function.
	 * @private
	 */
	onLoadResource(options, resolve, reject) {
		if (!this.isDynamicLoad) {
			Utils.setTimeout(() => {
				if (this.destroyed) {
					return;
				}

				this._refreshRenderableNode(options);

				resolve();
			});
		}
		else {
			Utils.setTimeout(() => {
				resolve();
			});
		}
	}

	onCreateBodyNode(type) {
		let node;

		switch (this.renderType) {
			case RenderType.Sprite:
				node = Utils.createObject(_spriteTypeName, { ...this.external, app: this.app });
				break;

			case RenderType.Plane:
				node = this.app.resourceManager.parseModel('Plane', _defaultPlaneOptions);
				break;

			default:
				node = super.onCreateBodyNode(type);
				break;
		}

		return node;
	}

	onGetPivot() {
		if (this._useSpriteCenterAsPivot()) {
			let pivot = this.bodyNode.getAttribute('Center');
			return [pivot[0], pivot[1], 0.5];
		}
		else {
			return super.onGetPivot();
		}
	}

	onSetPivot(value) {
		if (this._useSpriteCenterAsPivot()) {
			value = value || [0.5, 0.5, 0.5];
			this.bodyNode.setAttribute('Center', [value[0], value[1]]);
		}
		else {
			super.onSetPivot(value);
		}
	}

	onChangeImage() {
		this.waitForComplete().then(() => {
			if (this.destroyed) {
				return;
			}

			if (this._autoFitBodyScale) {
				this._fitBodyScale();
			}
		});
	}

	onSetupComplete(param) {
		super.onSetupComplete(param);

		this.level.config.ignoreStyle = true; // skip level outline
	}

	onExportExternalData(options) {
		let data = super.onExportExternalData(options) || {};

		data['autoFitBodyScale'] = this._autoFitBodyScale;
		data['scaleFactor'] = this._scaleFactor;
		data['renderType'] = this._renderType;
		data['pivotMode'] = this._pivotMode;
		data['spriteRotation'] = this._spriteRotation;

		return data;
	}

	/**
	 * 修正 keepSize 计算得到的缩放值。
	 * 当 Marker 开启 autoFitBodyScale 时,可以在此对缩放做额外处理。
	 * 当前默认实现保持缩放不变,仅作为子类扩展点。
	 * @param {Array<number>} scale 由 keepSize 计算得到的缩放值。
	 * @returns {Array<number>} 修正后的缩放值。
	 * @protected
	 */
	onFixKeepSizeScale(scale) {
		// 未开启自适应图片尺寸,直接返回原始缩放
		if (!this._autoFitBodyScale) {
			return scale;
		}

		// 根据图片的原始宽高来确定宽高比,确保 Marker 在 keepSize 模式下仍保持
		// 与图片一致的宽高比例,不会被变形。
		const style = this.style;
		const image = style && style.image;
		if (!image) {
			return scale;
		}

		const imgWidth = image.width;
		const imgHeight = image.height;
		if (!imgWidth || !imgHeight) {
			return scale;
		}

		// 使用 keepSize 计算出的 scale 作为“最大包围盒”,在该范围内按图片宽高比自适应缩放,
		// 保证宽高比正确,且不会超过 keepSize 计算得到的缩放值。
		const maxWidth = scale[0];
		const maxHeight = scale[1];
		const ratio = imgWidth / imgHeight; // 图片宽高比(宽/高)

		let fixedWidth;
		let fixedHeight;

		// 先假设高度占满,按宽高比算出宽度
		fixedHeight = maxHeight;
		fixedWidth = fixedHeight * ratio;

		// 如果宽度超过最大可用宽度,则改为宽度占满,再反推高度
		if (fixedWidth > maxWidth) {
			fixedWidth = maxWidth;
			fixedHeight = fixedWidth / ratio;
		}

		return [
			fixedWidth,
			fixedHeight,
			scale[2],
		];
	}

	// #endregion

	// #region Accessor

	/**
	 * 创建Marker是否自适应图片的宽高比 如果为false,图片会变形
	 * @default false
	 * @type {boolean}
	 * @example
	 * 	marker.autoFitBodyScale = true;
	 * @public
	 */
	get autoFitBodyScale() {
		return this._autoFitBodyScale;
	}
	set autoFitBodyScale(value) {
		this._autoFitBodyScale = value;

		if (this._autoFitBodyScale) {
			this._fitBodyScale();
		}
	}

	/**
	 * 设置/获取 自适应图片宽高比时的缩放倍数 仅autoFitBodyScale=true时生效
	 * @default 0.01
	 * @type {number|Array<number>}
	 * @example
	 * 	marker.scaleFactor = 0.1;
	 * @public
	 */
	get scaleFactor() {
		if (Utils.isArray(this._scaleFactor)) {
			return this._scaleFactor.concat();
		}

		return this._scaleFactor;
	}
	set scaleFactor(value) {
		if (Utils.isArray(value)) {
			this._scaleFactor = value.concat();
		}
		else {
			this._scaleFactor = value;
		}

		if (this._autoFitBodyScale) {
			this._fitBodyScale();
		}
	}

	/**
	 * 设置/获取 渲染类型 支持精灵模式和平面模式
	 * @type {RenderType}
	 * @default THING.RenderType.Sprite
	 * @public
	 */
	get renderType() {
		if (this._renderType !== undefined) {
			return this._renderType;
		}

		let type = this.bodyNode.getType();
		switch (type) {
			case 'Sprite': return RenderType.Sprite;
			case 'Plane': return RenderType.Plane;
			default:
				return RenderType.Plane;
		}
	}
	set renderType(value) {
		let pivot = this.pivot;

		this._renderType = value;

		this.reloadResource(false, { pivot });
	}

	/**
	 * 设置/获取 轴心点模式
	 * @type {PivotMode}
	 * @default THING.PivotMode.Auto
	 * @private
	 */
	get pivotMode() {
		return this._pivotMode;
	}
	set pivotMode(value) {
		if (this._pivotMode != value) {
			this._pivotMode = value;

			this.onSetPivot(this.pivot);
		}
	}

	/**
	 * Get/Set the pivot mode type.
	 * @type {PivotModeType}
	 * @deprecated 2.7
	 * @private
	 */
	get pivotModeType() {
		return this.pivotMode;
	}
	set pivotModeType(value) {
		this.pivotMode = value;
	}

	/**
	 * 精灵模式下的旋转角度
	 * @type {number}
	 * @public
	 */
	get spriteRotation() {
		return this._spriteRotation;
	}
	set spriteRotation(value) {
		this._spriteRotation = value;

		if (this.loaded) {
			if (this.renderType == RenderType.Sprite) {
				this.bodyNode.setAttribute('Rotation', MathUtils.degToRad(value));
			}
		}
	}


	/**
	 * 当适应图片尺寸时调用的函数。
	 * @callback OnFitBodyScaleCallback
	 * @param {object} info 参数
	 * @param  {THING.Marker} info.object 对象。Marker对象
	 * @param {object} info.image 图片。图片对象
	 * @public
	 */

	/**
	 * 获取/设置在适应图片尺寸之前调用的回调函数。
	 * @type {OnFitBodyScaleCallback}
	 * @public
	 */
	get onBeforeFitBodyScale() {
		return this._onBeforeFitBodyScale;
	}
	set onBeforeFitBodyScale(value) {
		this._onBeforeFitBodyScale = value;
	}

	/**
	 * 设置/获取 自适应图片尺寸结束后的回调
	 * @type {OnFitBodyScaleCallback}
	 * @public
	 */
	get onAfterFitBodyScale() {
		return this._onAfterFitBodyScale;
	}
	set onAfterFitBodyScale(value) {
		this._onAfterFitBodyScale = value;
	}

	/**
	 * 设置/获取 缩放模式  (仅 autoFitBodyScale=true 时生效)
	 * @type {THING.ScaleMode}
	 * @public
	 */
	get scaleMode() {
		return this._scaleMode;
	}
	set scaleMode(value) {
		this._scaleMode = value;

		if (this._autoFitBodyScale) {
			this._fitBodyScale();
		}
	}

	// #endregion

	get isMarker() {
		return true;
	}

}

export { Marker }