Source: resources/StyleModifier.js

import { Utils } from '../common/Utils';

/**
 * @class StyleModifier
 * The style modifier of object.
 * @memberof THING
 */
class StyleModifier {

	static dummyStyle;

	/**
	 * The style modifier to clone or resume style when change its attributes.
	 */
	constructor(object) {
		this._object = object;
		this._resource = null;
		this._needDisposeResource = false;

		this._originalResource = null;

		this._clippingPlanes = null;

		StyleModifier.dummyStyle = StyleModifier.dummyStyle || Utils.createObject('Style');
	}

	// #region Private

	_releaseStyle() {
		if (!this._resource) {
			return;
		}

		if (this._needDisposeResource) {
			this._needDisposeResource = false;

			let styleGroupPool = this._object.app.resourceManager.getStyleGroupPool();

			styleGroupPool.releaseStyle(this._resource);
		}

		this._resource = null;
	}

	// #endregion

	// #region Overrides

	onBegin() {
		let bodyNode = this._object.bodyNode;
		let bodyResource = bodyNode.getStyle();

		if (!this._resource) {
			this._needDisposeResource = true;

			this._resource = Utils.createObject('Style');
		}

		// Change to normal style without instanced drawing, due to any style attributes had changed
		if (bodyResource != this._resource) {
			this._originalResource = bodyResource;

			this._resource.copy(bodyResource);
			this._resource.enable('InstancedDrawing', false);

			bodyNode.setStyle(this._resource);
		}
	}

	onEnd() {
		if (!this._originalResource) {
			return;
		}

		let bodyNode = this._object.bodyNode;
		bodyNode.setStyle(this._originalResource);
	}

	onChangeColor(key, value) {
		let resource = this._resource;

		switch (key) {
			case 'outlineColor': resource.setOutlineColor(value); break;
			default:
				break;
		}
	}

	onChangeValue(key, value) {
		let resource = this._resource;

		switch (key) {
			case 'blendingType': resource.setBlendingType(value); break;
			case 'sideType': resource.setSideType(value); break;
			case 'transparent': resource.setTransparent(value); break;
			// Boolean
			case 'wireframe': resource.enable('Wireframe', value); break;
			case 'depthTest': resource.enable('DepthTest', value); break;
			case 'depthWrite': resource.enable('DepthWrite', value); break;
			case 'envMapping': resource.enable('EnvMap', value); break;
			// Object
			case 'clippingPlanes':
				if (value) {
					this._clippingPlanes = value;
					this._clippingPlanes.addStyleResource(resource);
				}
				else {
					if (this._clippingPlanes) {
						this._clippingPlanes.removeStyleResource(this._resource);
						this._clippingPlanes = null;
					}
				}
				break;

			default:
				break;
		}
	}

	onChangeOpFunc(key, value) {
		let resource = this._resource;

		switch (key) {
			case 'opacity': resource.setOpacityOp(value); break;
			case 'color': resource.setColorOp(value); break;
			case 'emissive': resource.setEmissiveOp(value); break;
			case 'roughness': resource.setRoughnessOp(value); break;
			case 'metalness': resource.setMetalnessOp(value); break;
			default:
				break;
		}
	}

	onChangeImage(type, value) {
		this._resource.setImage(type, value);

		// Notify object image of style has changed
		if (Utils.isFunction(this._object.onChangeImage)) {
			this._object.onChangeImage();
		}
	}

	onChangeUVMatrix(type, value) {
		this._resource.setUVMatrix(type, value);
	}

	onChangeEdge(value) {
		this._resource.setEdge(value);
	}

	onChangeEffect(value) {
		let resource = this._resource;

		resource.setEffect('glow', value.glow);
		resource.setEffect('innerGlow', value.innerGlow);
		resource.setEffect('lineBloom', value.lineBloom);
		resource.setEffect('tailing', value.tailing);
		resource.setEffect('radial', value.radial);
		resource.setEffect('ghosting', value.ghosting);
	}

	onChangeAttribute(key, value) {
		this._resource.setAttribute(key, value);
	}

	onChangeUniformValue(name, value) {
		if (value && Utils.isFunction(value.toUniform)) {
			// We must wait for the resource finish to loaded
			if (Utils.isFunction(value.waitForComplete)) {
				value.waitForComplete().then(() => {
					// The style would be released after resource is ready
					if (!this._resource) {
						return;
					}

					let uniform = value.toUniform()['value'];

					this._resource.setUniform(name, uniform);
				});
			}
			else {
				let uniform = value.toUniform()['value'];

				this._resource.setUniform(name, uniform);
			}
		}
		else {
			this._resource.setUniform(name, value);
		}
	}

	onChangeMacroValue(name, value) {
		this._resource.defineMacro(name, value);
	}

	// #endregion

	dispose() {
		if (this._clippingPlanes) {
			this._clippingPlanes.removeStyleResource(this._resource);
			this._clippingPlanes = null;
		}

		if (this._originalResource) {
			if (!this._originalResource.isDisposed()) {
				let bodyNode = this._object.bodyNode;

				let styleGroupPool = this._object.app.resourceManager.getStyleGroupPool();

				// If we have this style in pool then let the pool to release it
				if (styleGroupPool.hasStyle(this._originalResource)) {
					// Because the original resource would be release later, so we set the dummy style to body node
					bodyNode.setStyle(StyleModifier.dummyStyle);

					styleGroupPool.releaseStyle(this._originalResource);
				}
				else {
					bodyNode.setStyle(this._originalResource);
				}
			}

			this._originalResource = null;
		}

		this._releaseStyle();
	}

	initResource(resource) {
		this.dispose();

		this._originalResource = resource;

		let bodyNode = this._object.bodyNode;
		bodyNode.setStyle(resource);
	}

	updateResource(resource) {
		if (this._resource != resource) {
			this._releaseStyle();

			this._resource = resource;
		}

		let bodyNode = this._object.bodyNode;
		bodyNode.setStyle(resource);
	}

	get object() {
		return this._object;
	}

	get originalResource() {
		return this._originalResource;
	}

	get resource() {
		return this._resource;
	}

	/**
	 * Check class type.
	 * @type {Boolean}
	 * @private
	 */
	get isStyleModifier() {
		return true;
	}

}

export { StyleModifier }