Source: resources/Style.js

import { Flags, ObjectAttributes } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { MathUtils } from '../math/MathUtils';
import { EventType, ImageSlotType, PlayStateType, Empty } from '../const';

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

// #region Internal Variables

const cAnimationTagName = '__Style_AnimationTagName__';

const Flag = {
	// #region Diff

	// Type
	BlendingType: 1 << 0,
	SideType: 1 << 1,
	// Number
	Opacity: 1 << 2,
	Metalness: 1 << 3,
	Roughness: 1 << 4,
	// Color
	Color: 1 << 5,
	Emissive: 1 << 6,
	OutlineColor: 1 << 7,
	Transparent: 1 << 8,
	// Object
	ClippingPlanes: 1 << 9,
	Edge: 1 << 10,
	Effect: 1 << 11,
	Images: 1 << 12,
	UVMatrix: 1 << 13,
	// Boolean
	Wireframe: 1 << 14,
	DepthTest: 1 << 15,
	DepthWrite: 1 << 16,
	EnvMapping: 1 << 17,
	// External
	Animation: 1 << 18,
	Attributes: 1 << 19,
	Uniforms: 1 << 20,
	Macros: 1 << 21,

	// #endregion

	// #region State

	DelayBegin: 1 << 22,
	HookAnimationEvent: 1 << 23,
	ChangingDefaultValues: 1 << 24

	// #endregion
};

const cBaseValueFlags =
	Flag.BlendingType |
	Flag.SideType |
	Flag.Opacity |
	Flag.Metalness |
	Flag.Roughness |
	Flag.Color |
	Flag.Emissive |
	Flag.OutlineColor |
	Flag.Transparent |
	Flag.ClippingPlanes |
	Flag.Wireframe |
	Flag.DepthTest |
	Flag.DepthWrite |
	Flag.EnvMapping;

const cDiffValueFlags =
	cBaseValueFlags |
	Flag.Edge |
	Flag.Effect |
	Flag.Images |
	Flag.UVMatrix |
	Flag.Animation |
	Flag.Attributes |
	Flag.Uniforms |
	Flag.Macros;

const cDefaultValues = {
	// Type
	blendingType: null,
	sideType: null,
	// Number
	opacity: null,
	metalness: null,
	roughness: null,
	// Color
	color: null,
	emissive: null,
	outlineColor: null,
	transparent: null,
	// Object
	clippingPlanes: null,
	// Boolean
	wireframe: null,
	depthTest: null,
	depthWrite: null,
	envMapping: null
};

const cDefaultValueKeys = Object.keys(cDefaultValues);

/**
 * @typedef {Object} StyleEdgeResult
 * @property {Boolean} enable True indicates enable it.
 * @property {Array<Number>} color The color.
 * @property {Number} opacity The opacity.
 * @property {Boolean} glow True indicates enable glow effect.
 */
const cEdgeDefaultValues = {
	enable: false,
	color: [1, 1, 1],
	opacity: 1,
	glow: false,
}

const cEdgeDefaultValueKeys = Object.keys(cEdgeDefaultValues);

/**
 * @typedef {Object} StyleEffectResult
 * @property {Number} glow The glow intensity (null indicates resume original effect).
 * @property {Boolean} innerGlow The inner glow effect (null indicates resume original effect).
 * @property {Boolean} lineBloom The line bloom effect (null indicates resume original effect).
 * @property {Boolean} tailing The tailing effect (null indicates resume original effect).
 * @property {Boolean} radial The radial effect (null indicates resume original effect).
 * @property {Boolean} ghosting The ghosting effect (null indicates resume original effect).
 * @public
 */
const cEffectDefaultValues = {
	glow: null,
	innerGlow: null,
	lineBloom: null,
	tailing: null,
	radial: null,
	ghosting: null
};

const cEffectDefaultValueKeys = Object.keys(cEffectDefaultValues);

/**
 * @typedef {Object} StyleImagesResult
 * @property {Object} map
 * @property {Object} envMap
 * @property {Object} alphaMap
 * @property {Object} emissiveMap
 * @property {Object} normalMap
 * @property {Object} colorMapping
 * @property {Object} aoMap
 * @public
 */
const cImagesDefaultValues = {
	map: null,
	envMap: null,
	alphaMap: null,
	emissiveMap: null,
	normalMap: null,
	colorMapping: null,
	aoMap: null,
};

const cImagesDefaultValueKeys = Object.keys(cImagesDefaultValues);

function _createUVMatrixDefaultValues() {
	return {
		offset: [0, 0],
		repeat: [1, 1],
		center: [0, 0],
		rotation: 0
	};
}

/**
 * @typedef {Object} StyleUVMatrixResult
 * @property {Array<Number>} offset The offset of UV matrix, default: [0, 0]
 * @property {Array<Number>} repeat The repeat of UV matrix, default: [1, 1]
 * @property {Array<Number>} center The center of UV matrix, default: [0, 0]
 * @property {Number} rotation The rotation in degree.
 * @public
 */
const cUVMatrixDefaultValues = _createUVMatrixDefaultValues();

const cValueKeys = [
	'attributes',
	'uniforms',
	'macros'
];

// #endregion

// #region Private Functions

// Fast to compare values.
function _equals(v1, v2) {
	if (v1 && v2 && Utils.isArray(v1) && Utils.isArray(v2)) {
		if (v1.length != v2.length) {
			return false;
		}

		switch (v1.length) {
			case 1: return v1[0] == v2[0];
			case 2: return v1[0] == v2[0] && v1[1] == v2[1];
			case 3: return v1[0] == v2[0] && v1[1] == v2[1] && v1[2] == v2[2];
			case 4: return v1[0] == v2[0] && v1[1] == v2[1] && v1[2] == v2[2] && v1[3] == v2[3];
			default:
				for (let i = 0; i < v1.length; i++) {
					if (v1[i] != v2[i]) {
						return false;
					}
				}
				break;
		}
	}
	else if (v1 !== v2) {
		return false;
	}

	return true;
}

function _assignArray(value) {
	return value;
}

function _toImageType(type) {
	switch (type) {
		case 'Map': return 'map';
		case 'EnvMap': return 'envMap';
		case 'AlphaMap': return 'alphaMap';
		case 'EmissiveMap': return 'emissiveMap';
		case 'NormalMap': return 'normalMap';
		case 'ColorMapping': return 'colorMapping';
		case 'AOMap': return 'aoMap';
		default:
			return type;
	}
}

function _toUImageType(type) {
	switch (type) {
		case 'map': return 'Map';
		case 'envMap': return 'EnvMap';
		case 'alphaMap': return 'AlphaMap';
		case 'emissiveMap': return 'EmissiveMap';
		case 'normalMap': return 'NormalMap';
		case 'colorMapping': return 'ColorMapping';
		case 'aoMap': return 'AOMap';
		default:
			return type;
	}
}

function _needProxy(prop, value) {
	if (value && value.isBaseObject) {
		return false;
	}

	return true;
}

function _assignObject(data, target) {
	target = target || {};

	return Object.assign(target, data);
}

function _cloneObject(data, target) {
	target = target || {};

	for (let key in data) {
		let value = data[key];

		if (value !== null && value !== undefined) {
			if (Utils.isArray(value)) {
				target[key] = value.slice(0);
			}
			else {
				target[key] = value;
			}
		}
		else {
			target[key] = value;
		}
	}

	return target;
}

// #endregion

/**
 * @class Style
 * The style resource of objects.
 * @memberof THING
 * @public
 */
class Style {

	static cDefaultValues = cDefaultValues;
	static cEdgeDefaultValues = cEdgeDefaultValues;
	static cEffectDefaultValues = cEffectDefaultValues;
	static cImagesDefaultValues = cImagesDefaultValues;
	static cUVMatrixDefaultValues = cUVMatrixDefaultValues;

	/**
	 * The object style, it can change object's color, outlineColor etc.
	 * @param {Object} modifier The modifier.
	 */
	constructor(modifier) {
		this[__.private] = {};
		let _private = this[__.private];

		_private.modifier = modifier;

		// The current image slot type
		_private.curImageSlotType = ImageSlotType.Map;

		// Attributes
		_private.edgeData = null;
		_private.effectData = null;
		_private.imagesData = null;
		_private.uvData = null; // Use image type as key
		_private.data = new ObjectAttributes({
			data: cDefaultValues,
			keys: cDefaultValueKeys,
			onNeedProxy: _needProxy,
			onCloneObject: _cloneObject,
			onEquals: _equals
		});

		// If reach to 0 then indicates use the default original style in Diff flags
		_private.state = new Flags();

		// The values for attributes/uniforms/macros
		_private.values = {
			attributes: null,
			uniforms: null,
			macros: null,
		};

		// Watch GC to check memory leak
		if (_DEBUG) {
			this._uuid = MathUtils.generateUUID();

			let app = Utils.getCurrentApp();

			if (!Style.gcObjects) {
				Style.gcObjects = new Set();

				app.global.gc.registerDestroyObjectCallback((key) => {
					Style.gcObjects.delete(key);
				});
			}

			let key = `type: 'Style', uuid: ${this._uuid}`;
			Style.gcObjects.add(key);

			app.global.gc.register(this, key);
		}
	}

	// #region Private

	// #region Private-Attributes

	/**
	 * Get proxy values or original values.
	 * @param {*} valueKey
	 * @returns {Object}
	 * @private
	 */
	_getValues(valueKey) {
		let _private = this[__.private];

		let values = {
			data: _private.data[valueKey]
		};

		if (_private.edgeData) {
			values['edge'] = _private.edgeData[valueKey];
		}

		if (_private.effectData) {
			values['effect'] = _private.effectData[valueKey];
		}

		if (_private.imagesData) {
			values['images'] = _private.imagesData[valueKey];
		}

		if (_private.uvData) {
			values['uvData'] = _private.uvData[valueKey];
		}

		return values;
	}

	_updateDiffSlots(flag, value) {
		let _private = this[__.private];

		let diffFlags = _private.state;

		// Update flags
		let oldValues = diffFlags.values & cDiffValueFlags;
		diffFlags.enable(flag, value);

		// Begin to change body node style
		if (oldValues === 0 && diffFlags.values) {
			_private.modifier.onBegin();

			if (_private.state.has(Flag.DelayBegin)) {
				_private.modifier.resource.begin();

				_private.state.enable(Flag.DelayBegin, false);
			}
		}
		// Restore the default original style
		else if (oldValues && ((diffFlags.values & cDiffValueFlags) === 0)) {
			_private.modifier.onEnd();
		}
	}

	_createColorOpCallback(callback) {
		if (!callback) {
			return null;
		}

		let opCallback = function (baseValue) {
			let color = callback(baseValue, this);

			return Utils.parseColor(color, baseValue);
		}

		if (callback.isConstantValue) {
			opCallback.isConstantValue = true;
		}

		return opCallback;
	}

	_createOpCallback(callback) {
		if (!callback) {
			return null;
		}

		let opCallback = function (baseValue) {
			return callback(baseValue, this);
		}

		if (callback.isConstantValue) {
			opCallback.isConstantValue = true;
		}

		return opCallback;
	}

	_createColorCallbackWrapper(key) {
		let _private = this[__.private];

		return this._createColorOpCallback(baseValue => {
			return _private.data.getValueByDefault(key);
		});
	}

	_createOpCallbackWrapper(key, callback) {
		let _private = this[__.private];

		if (callback) {
			return this._createOpCallback(baseValue => {
				return callback(baseValue, _private.data.getValueByDefault(key));
			});
		}
		else {
			return this._createOpCallback(baseValue => {
				return _private.data.getValueByDefault(key);
			});
		}
	}

	_updateAttributeValue(flag, key, value, onFormatValue, onChange) {
		let _private = this[__.private];

		// Clear value
		if (Utils.isNull(value)) {
			if (!_private.data.setValue(key, null, _equals)) {
				return;
			}
		}
		// Set value
		else {
			// Try to format/convert value
			if (onFormatValue) {
				value = onFormatValue(value);
			}

			if (!_private.data.setValue(key, value, _equals, null, _assignArray)) {
				return;
			}
		}

		// Update the diff slots of current and base values
		let isBaseValue = _private.data.isBaseValue(key, _equals);
		this._updateDiffSlots(flag, !isBaseValue);

		// Get the value again(the default value could be returned here)
		value = _private.data.getValue(key);

		// Update renderer style attribute value
		onChange(key, value);
	}

	_updateAttributeOpFunc(flag, key, value, onFormatValue, onCreateOpCallback, onCreateCallbackWrapper) {
		let _private = this[__.private];

		// Clear it
		if (Utils.isNull(value)) {
			if (!_private.data.setValue(key, null, _equals)) {
				return;
			}

			// Get the default value to set as latest attribute
			if (_private.data.hasValue(key)) {
				value = onCreateCallbackWrapper(key);
			}
		}
		// Set it by function
		else if (Utils.isFunction(value)) {
			value = onCreateOpCallback(value);
			if (!_private.data.setValue(key, value, _equals)) {
				return;
			}

			// If we are changing default values and have value already then do not change attribute of style
			if (_private.state.has(Flag.ChangingDefaultValues)) {
				if (_private.data.hasValue(key, true)) {
					return;
				}
			}
		}
		// Set it by value(need to wrap function)
		else {
			// Try to format/convert value
			if (onFormatValue) {
				value = onFormatValue(value);
				if (!value) {
					return;
				}
			}

			if (!_private.data.setValue(key, value, _equals, null, _assignArray)) {
				return;
			}

			// If we are changing default values and have value already then do not change attribute of style
			if (_private.state.has(Flag.ChangingDefaultValues)) {
				if (_private.data.hasValue(key, true)) {
					return;
				}
			}

			// Create callback function wrapper due to renderer use callback function as argument
			value = onCreateCallbackWrapper(key);
		}

		// Update the diff slots of current and base values
		let isBaseValue = _private.data.isBaseValue(key, _equals);
		this._updateDiffSlots(flag, !isBaseValue);

		// Update renderer callback function
		_private.modifier.onChangeOpFunc(key, value);
	}

	_updateColor(flag, key, value) {
		this._updateAttributeValue(flag, key, value,
			(value) => {
				return Utils.parseColor(value, null);
			},
			(key, value) => {
				let _private = this[__.private];

				_private.modifier.onChangeColor(key, value);
			}
		);
	}

	_updateValue(flag, key, value) {
		this._updateAttributeValue(flag, key, value, null,
			(key, value) => {
				let _private = this[__.private];

				_private.modifier.onChangeValue(key, value);
			}
		);
	}

	_updateColorOpFunc(flag, key, value) {
		this._updateAttributeOpFunc(flag, key, value,
			(v) => {
				return Utils.parseColor(v, null);
			},
			// By function
			(v) => {
				return this._createColorOpCallback(v);
			},
			// By value(we need to wrap function)
			(key) => {
				return this._createColorCallbackWrapper(key);
			}
		);
	}

	_updateValueOpFunc(flag, key, value, callback) {
		this._updateAttributeOpFunc(flag, key, value, null,
			// By function
			(v) => {
				return this._createOpCallback(v);
			},
			// By value(we need to wrap function)
			(key) => {
				return this._createOpCallbackWrapper(key, callback);
			}
		);
	}

	_updateValues(flag, key, value, values) {
		values.setValue(key, value, _equals, (key, value) => {
			let isBaseValue = _equals(values.getValue(key), value);
			this._updateDiffSlots(flag, !isBaseValue);
		});
	}

	_changeValue(flag, funcName, ev) {
		let _private = this[__.private];

		let key = ev.key;
		let value = Utils.parseValue(ev.value, null);

		// Update the diff slots of current and base values
		let isBaseValue = _private.data.isBaseValue(key, _equals);
		this._updateDiffSlots(flag, !isBaseValue);

		_private.modifier[funcName](key, value);
	}

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

		if (!_private.values['attributes']) {
			_private.values['attributes'] = new ObjectAttributes({
				onChange: (ev) => {
					this._changeValue(Flag.Attributes, 'onChangeAttribute', ev);
				}
			});
		}

		return _private.values['attributes'];
	}

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

		if (!_private.values['uniforms']) {
			_private.values['uniforms'] = new ObjectAttributes({
				onNeedProxy: function (prop, value) {
					if (value && value.isImageTexture) {
						return false;
					}

					return true;
				},
				onChange: (ev) => {
					this._changeValue(Flag.Uniforms, 'onChangeUniformValue', ev);
				}
			});
		}

		return _private.values['uniforms'];
	}

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

		if (!_private.values['macros']) {
			_private.values['macros'] = new ObjectAttributes({
				onChange: (ev) => {
					this._changeValue(Flag.Macros, 'onChangeMacroValue', ev);
				}
			});
		}


		return _private.values['macros'];
	}

	// #endregion

	// #region Private-Image

	_updateImage(type, image, isChangingDefaultValues) {
		let _private = this[__.private];

		// If we had destroyed object then modifier would be null
		let modifier = _private.modifier;
		if (!modifier) {
			return;
		}

		// If it's default values then need to check the current image state, if it's existing then do not update resource image now
		if (isChangingDefaultValues) {
			let values = _private.imagesData.getValues().data;
			if (values[type]) {
				return;
			}
		}
		// We must check the image here again to prevent the image loading order problem
		else {
			if (_private.imagesData.data[type] != image) {
				return;
			}
		}

		// Update diff slots
		this._updateDiffSlots(Flag.Images, true);

		// Get the U-Image type
		let uImageType = _toUImageType(type);

		// Update image resource
		if (image) {
			modifier.onChangeImage(uImageType, image.getTextureResource());
		}
		// Check whether need to use default image
		else {
			let defaultImage = _private.imagesData.defaultValues[type];
			if (defaultImage) {
				modifier.onChangeImage(uImageType, defaultImage.getTextureResource());
			}
			else {
				modifier.onChangeImage(uImageType, null);
			}
		}
	}

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

		if (!_private.imagesData) {
			_private.imagesData = new ObjectAttributes({
				data: cImagesDefaultValues,
				keys: cImagesDefaultValueKeys,
				onCloneObject: (data) => {
					return {
						map: data.map,
						envMap: data.envMap,
						alphaMap: data.alphaMap,
						emissiveMap: data.emissiveMap,
						normalMap: data.normalMap,
						colorMapping: data.colorMapping,
						aoMap: data.aoMap
					};
				},
				onFilterProperty: () => {
					// We skip to proxy images here
					return false;
				},
				onChange: (ev) => {
				},
				onNeedProxy: _needProxy,
				onCloneObject: _assignObject,
				onEquals: _equals
			});
		}

		return _private.imagesData;
	}

	// #endregion

	// #region Private-UV

	_updateImageUVMatrix(type, uv) {
		let _private = this[__.private];

		let offset = uv.offset;
		let repeat = uv.repeat;
		let center = uv.center;
		let rotation = MathUtils.degToRad(uv.rotation);

		const c = Math.cos(rotation);
		const s = Math.sin(rotation);

		let uvMatrix = [
			repeat[0] * c, -repeat[1] * s, 0,
			repeat[0] * s, repeat[1] * c, 0,
			-repeat[0] * (c * center[0] + s * center[1]) + center[0] + offset[0], -repeat[1] * (-s * center[0] + c * center[1]) + center[1] + offset[1], 1
		]

		this._updateDiffSlots(Flag.UVMatrix, true);

		_private.modifier.onChangeUVMatrix(type, uvMatrix);
	}

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

		if (!_private.uvData) {
			_private.uvData = new ObjectAttributes({
				data: {
					map: cUVMatrixDefaultValues,
					envMap: cUVMatrixDefaultValues,
					alphaMap: cUVMatrixDefaultValues,
					emissiveMap: cUVMatrixDefaultValues,
					normalMap: cUVMatrixDefaultValues,
					colorMapping: cUVMatrixDefaultValues,
					aoMap: cUVMatrixDefaultValues,
				},
				keys: cImagesDefaultValueKeys,
				onCloneObject: (data) => {
					return {
						map: _createUVMatrixDefaultValues(),
						envMap: _createUVMatrixDefaultValues(),
						alphaMap: _createUVMatrixDefaultValues(),
						emissiveMap: _createUVMatrixDefaultValues(),
						normalMap: _createUVMatrixDefaultValues(),
						colorMapping: _createUVMatrixDefaultValues(),
						aoMap: _createUVMatrixDefaultValues()
					};
				},
				onChange: (ev) => {
					let type = _private.curImageSlotType;
					let data = ev.data[_toImageType(type)];
					if (data) {
						this._updateImageUVMatrix(type, data);
					}
				},
				onEquals: _equals
			});
		}

		return _private.uvData;
	}

	_getImageUVMatrixByType(type) {
		let _private = this[__.private];

		type = _toImageType(type);

		this._createUVMatrixData();

		let uvData = _private.uvData.getValueByDefault(type);
		if (!uvData) {
			_private.uvData.setValue(type, {
				'offset': [0, 0],
				'repeat': [1, 1],
				'center': [0, 0],
				'rotation': 0
			}, _equals);

			uvData = _private.uvData.getValueByDefault(type);
		}

		return uvData;
	}

	// #endregion

	// #region Private-Edge

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

		if (!_private.edgeData) {
			_private.edgeData = new ObjectAttributes({
				data: cEdgeDefaultValues,
				keys: cEdgeDefaultValueKeys,
				onCloneObject: (data) => {
					return {
						enable: data.enable,
						color: data.color.slice(0),
						opacity: data.opacity,
						glow: data.glow
					};
				},
				onConvertValue: (type, value) => {
					if (type == 'color') {
						return Utils.parseColor(value, null);
					}
				},
				onChange: (ev) => {
					this._updateDiffSlots(Flag.Edge, true);

					_private.modifier.onChangeEdge(ev.data);
				},
				onEquals: _equals
			});
		}

		return _private.edgeData;
	}

	// #endregion

	// #region Private-Effect

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

		if (!_private.effectData) {
			_private.effectData = new ObjectAttributes({
				data: cEffectDefaultValues,
				keys: cEffectDefaultValueKeys,
				onCloneObject: (data) => {
					return {
						glow: data.glow,
						innerGlow: data.innerGlow,
						lineBloom: data.lineBloom,
						tailing: data.tailing,
						radial: data.radial,
						ghosting: data.ghosting
					};
				},
				onChange: (ev) => {
					this._updateDiffSlots(Flag.Effect, true);

					_private.modifier.onChangeEffect(ev.data);
				},
				onCloneObject: _assignObject,
				onEquals: _equals
			});
		}

		return _private.effectData;
	}

	// #endregion

	// #endregion

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

		if (_private.edgeData) {
			_private.edgeData.dispose();
			_private.edgeData = null;
		}

		if (_private.effectData) {
			_private.effectData.dispose();
			_private.effectData = null;
		}

		if (_private.imagesData) {
			_private.imagesData.dispose();
			_private.imagesData = null;
		}

		if (_private.uvData) {
			_private.uvData.dispose();
			_private.uvData = null;
		}

		// TODO ? Really need this ?!
		// Remember: we should not clear any values here, due to style use the callback functions what reply on it
		// if (_private.data) {
		// 	_private.data.dispose();
		// 	_private.data = null;
		// }

		if (_private.modifier) {
			let object = _private.modifier.object;
			object.off(EventType.PlayAnimation, cAnimationTagName);
			object.off(EventType.StopAnimation, cAnimationTagName);

			_private.modifier.dispose();
			_private.modifier = null;
		}

		cValueKeys.forEach(key => {
			if (_private.values[key]) {
				_private.values[key].dispose();
				_private.values[key] = null;
			}
		});
	}

	updateBaseValues(values) {
		let _private = this[__.private];

		let diffValues = _private.state.values & cDiffValueFlags;
		if (!diffValues) {
			return;
		}

		if (diffValues & cBaseValueFlags) {
			_private.data.updateBaseValues(values.data);
		}

		if (values.edge) {
			this._createEdgeData();

			_private.edgeData.updateBaseValues(values.edge);
		}

		if (values.effect) {
			this._createEffectData();

			_private.effectData.updateBaseValues(values.effect);
		}

		if (values.images) {
			this._createImagesData();

			_private.imagesData.updateBaseValues(values.images);
		}

		if (values.uv) {
			this._createUVMatrixData();

			_private.uvData.updateBaseValues(values.uv);
		}

		cValueKeys.forEach(key => {
			if (_private.values[key]) {
				_private.values[key].makeCurrentAsBaseValue();
			}
		});

		_private.state.clear(cDiffValueFlags);
	}

	initResource(resource) {
		let _private = this[__.private];

		_private.modifier.initResource(resource);
	}

	updateResource(resource) {
		let _private = this[__.private];

		_private.modifier.updateResource(resource);
	}

	getOringialValues() {
		return this._getValues('data');
	}

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

		let values = {
			data: _private.data.getValuesWithDefaults(_equals)
		};

		if (_private.edgeData) {
			values['edge'] = _private.edgeData.getValuesWithDefaults(_equals);
		}

		if (_private.effectData) {
			values['effect'] = _private.effectData.getValuesWithDefaults(_equals);
		}

		if (_private.imagesData) {
			values['images'] = _private.imagesData.getValuesWithDefaults(_equals);
		}

		if (_private.uvData) {
			values['uvData'] = _private.uvData.getValuesWithDefaults(_equals);
		}

		return values;
	}

	getData(key) {
		if (key == 'edge') {
			return this._createEdgeData().data;
		}
		else if (key == 'effect') {
			return this._createEffectData().data;
		}
		else if (key == 'images') {
			return this._createImagesData().data;
		}
		else if (key == 'uv') {
			return this._createUVMatrixData().data;
		}
		else if (key == 'attributes') {
			return this._createAttributes().data;
		}
		else if (key == 'macros') {
			return this._createMacros().data;
		}
		else if (key == 'uniforms') {
			return this._createUniforms().data;
		}

		return null;
	}

	copyFromValues(values) {
		let data = values['data'];
		for (let key in data) {
			this[key] = data[key];
		}

		let edge = values['edge'];
		if (edge) {
			this.edge = edge;
		}

		let effect = values['effect'];
		if (effect) {
			this.effect = effect;
		}

		let images = values['images'];
		if (images) {
			for (let key in images) {
				let image = images[key];
				if (image) {
					this.setImage(key, image);
				}
			}
		}

		let uvData = values['uvData'];
		if (uvData) {
			let prevImageSlotType = this.imageSlotType;

			for (let key in uvData) {
				this.imageSlotType = key;

				let uv = uvData[key];
				this.uv.offset = uv.offset;
				this.uv.repeat = uv.repeat;
				this.uv.center = uv.center;
				this.uv.rotation = uv.rotation;
			}

			this.imageSlotType = prevImageSlotType;
		}
	}

	copyFromProperties(values) {
		for (let key in values) {
			if (key === 'edge') {
				let edge = values['edge'];
				if (edge) {
					this.edge = edge;
				}
			}
			else if (key === 'effect') {
				let effect = values['effect'];
				if (effect) {
					this.effect = effect;
				}
			}
			else if (key === 'images') {
				let images = values['images'];
				if (images) {
					for (let key in images) {
						let image = images[key];
						if (image) {
							this.setImage(key, image);
						}
					}
				}
			}
			else if (key === 'uvData') {
				let uvData = values['uvData'];
				if (uvData) {
					let prevImageSlotType = this.imageSlotType;

					for (let key in uvData) {
						this.imageSlotType = key;

						let uv = uvData[key];
						this.uv.offset = uv.offset;
						this.uv.repeat = uv.repeat;
						this.uv.center = uv.center;
						this.uv.rotation = uv.rotation;
					}

					this.imageSlotType = prevImageSlotType;
				}
			}
			else {
				this[key] = values[key];
			}
		}
	}

	/**
	 * Copy from another style or object.
	 * @param {Object|THING.Style} style The style or object.
	 * @returns {THING.Style}
	 * @private
	 */
	copy(style) {
		if (!style) {
			return this;
		}

		if (style.isStyle) {
			this.copyFromValues(style.getOringialValues());
		}
		else {
			if (Utils.isObject(style)) {
				for (let key in style) {
					this[key] = style[key];
				}
			}
		}

		return this;
	}

	/**
	 * Get image.
	 * @param {ImageSlotType} type The image slot type.
	 * @returns {THING.ImageTexture}
	 * @private
	 */
	getImage(type) {
		let _private = this[__.private];

		if (!_private.imagesData) {
			return null;
		}

		return _private.imagesData.data[_toImageType(type)];
	}

	/**
	 * Set image.
	 * @param {ImageSlotType} type The image slot type.
	 * @param {THING.ImageTexture} value The image url or resource.
	 * @private
	 */
	setImage(type, value) {
		// Check value
		if (value) {
			// Check whether need to clear all textures.
			if (value == Empty.ImageTexture) {
				value = Utils.getCurrentApp().global.cache.images['empty_image_texture'];
				console.warn('THING.Empty.ImageTexture will not be supported soon. Use THING.Empty.Texture instead.')
			}

			if (value == Empty.Texture) {
				value = Utils.getCurrentApp().global.cache.images['empty_image_texture'];
			}

			if (!Utils.isValidTexture(value)) {
				Utils.error(`Set style image failed, due to image is unknown`, value);
				return;
			}
		}

		// Convert image type
		type = _toImageType(type);

		// Create images data
		let imagesData = this._createImagesData();

		// Use the right image resource to release
		let isChangingDefaultValues = imagesData.isChangingDefaultValues();

		// Check whether it's same image resource
		let imageResource = isChangingDefaultValues ? imagesData.defaultValues[type] : imagesData.data[type];
		if (imageResource == value) {
			return;
		}

		// Release previous image
		if (imageResource) {
			imageResource.release();
		}

		// Update image data
		imagesData.updateValue(type, value);

		if (value) {
			this._updateImage(type, value, isChangingDefaultValues);

			// Add image resource referenced count to keep it alive
			value.addRef();
		}
		else {
			this._updateImage(type, null);
		}
	}

	/**
	 * Get image UV.
	 * @param {ImageSlotType} type The image slot type.
	 * @returns {StyleUVMatrixResult}
	 * @private
	 */
	getUV(type) {
		let uvData = this._getImageUVMatrixByType(type);
		if (!uvData) {
			return null;
		}

		return uvData;
	}

	/**
	 * Set image UV.
	 * @param {ImageSlotType} type The image slot type.
	 * @param {String} key The UV key.
	 * @param {StyleUVMatrixResult} value The UV value.
	 * @private
	 */
	setUV(type, key, value) {
		let uvData = this._getImageUVMatrixByType(type);
		if (!uvData) {
			return;
		}

		uvData[key] = value;
	}

	/**
	 * Begin to set default values.
	 */
	beginDefaultValues() {
		let _private = this[__.private];

		_private.state.enable(Flag.ChangingDefaultValues, true);

		this._createEdgeData();
		this._createEffectData();
		this._createImagesData();
		this._createUVMatrixData();

		_private.edgeData.beginDefaultValues();
		_private.effectData.beginDefaultValues();
		_private.imagesData.beginDefaultValues();
		_private.uvData.beginDefaultValues();
		_private.data.beginDefaultValues();

		cValueKeys.forEach(key => {
			if (_private.values[key]) {
				_private.values[key].beginDefaultValues();
			}
		});
	}

	/**
	 * End to set default values.
	 */
	endDefaultValues() {
		let _private = this[__.private];

		_private.edgeData.endDefaultValues();
		_private.effectData.endDefaultValues();
		_private.imagesData.endDefaultValues();
		_private.uvData.endDefaultValues();
		_private.data.endDefaultValues();

		cValueKeys.forEach(key => {
			if (_private.values[key]) {
				_private.values[key].endDefaultValues();
			}
		});

		_private.state.enable(Flag.ChangingDefaultValues, false);
	}

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

		let values = _private.data.getDiffValues();

		if (_private.edgeData) {
			values['edge'] = _private.edgeData.getDiffValues();
		}

		if (_private.effectData) {
			values['effect'] = _private.effectData.getDiffValues();
		}

		if (_private.imagesData) {
			values['images'] = _private.imagesData.getDiffValues();
		}

		if (_private.uvData) {
			values['uvData'] = _private.uvData.getDiffValues();
		}

		return values;
	}

	bindAnimationEvents(object) {
		let _private = this[__.private];

		if (_private.state.has(Flag.HookAnimationEvent)) {
			return;
		}

		_private.state.enable(Flag.HookAnimationEvent, true);

		// Hook animation events
		let animationNames = object.animationNames;
		if (animationNames && animationNames.length) {
			object.on(EventType.PlayAnimation, (ev) => {
				this._updateDiffSlots(Flag.Animation, true);
			}, cAnimationTagName);

			object.on(EventType.StopAnimation, (ev) => {
				// let animations = object.animations;
				// for (let i = 0; i < animations.length; i++) {
				// 	let animation = animations[i];
				// 	if (animation.state != PlayStateType.Ready || animation.state != PlayStateType.Stopped) {
				// 		return;
				// 	}
				// }

				this._updateDiffSlots(Flag.Animation, false);
			}, cAnimationTagName);
		}
	}

	begin() {
		if (this.resource) {
			this.resource.begin();
		}
		else {
			let _private = this[__.private];

			_private.state.enable(Flag.DelayBegin, true);
		}
	}

	end() {
		if (this.resource) {
			this.resource.end();
		}
		else {
			let _private = this[__.private];

			_private.state.enable(Flag.DelayBegin, false);
		}
	}

	/**
	 * Set attribute.
	 * @param {String} type The type.
	 * @param {*} value The value.
	 * @private
	 */
	setAttribute(type, value) {
		let attributes = this._createAttributes();

		// Try to sync base value with resource default attribute value
		let currentValue = attributes.getValue(type);
		if (currentValue === null && this.resource) {
			currentValue = this.resource.getAttribute(type);
			if (Utils.isValid(currentValue)) {
				attributes.updateBaseValue(type, currentValue);
			}
		}

		this._updateValues(Flag.Attributes, type, value, attributes);
	}

	/**
	 * Get attribute.
	 * @param {String} type The type.
	 * @returns {*}
	 * @private
	 */
	getAttribute(type) {
		let attributes = this._createAttributes();

		return attributes.getValue(type);
	}

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

		let values = _private.data['data'];

		if (_private.edgeData) {
			values['edge'] = _private.edgeData['data'];
		}

		if (_private.effectData) {
			values['effect'] = _private.effectData['data'];
		}

		if (_private.imagesData) {
			values['images'] = _private.imagesData['data'];
		}

		if (_private.uvData) {
			values['uvData'] = _private.uvData['data'];
		}

		return values;
	}

	/**
	 * Set uniform value.
	 * @param {String} name The name.
	 * @param {*} value The value.
	 * @private
	 */
	setUniform(name, value) {
		let uniforms = this._createUniforms();

		this._updateValues(Flag.Uniforms, name, value, uniforms);
	}

	/**
	 * Get uniform value.
	 * @param {String} name The name.
	 * @returns {*}
	 * @private
	 */
	getUniform(name) {
		let uniforms = this._createUniforms();

		return uniforms.getValue(name);
	}

	/**
	 * Set macro value.
	 * @param {String} name The name.
	 * @param {*} value The value.
	 * @private
	 */
	setMacro(name, value) {
		let macros = this._createMacros();

		this._updateValues(Flag.Macros, name, value, macros);
	}

	/**
	 * Get macro value.
	 * @param {String} name The name.
	 * @returns {*}
	 * @private
	 */
	getMacro(name) {
		let macros = this._createMacros();

		return macros.getValue(name);
	}

	toJSON() {
		let style = {};

		function _export(values, keys, callback) {
			keys.forEach(key => {
				let value = values[key];

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

				if (Utils.isFunction(value)) {
					return;
				}

				callback(key, value)
			});
		}

		_export(this, cDefaultValueKeys, (key, value) => {
			style[key] = value;
		});

		_export(this.edge, cEdgeDefaultValueKeys, (key, value) => {
			style.edge = style.edge || {};
			style.edge[key] = value;
		});

		_export(this.effect, cEffectDefaultValueKeys, (key, value) => {
			style.effect = style.effect || {};
			style.effect[key] = value;
		});

		return style;
	}

	// #region Accessor

	get originalResource() {
		return this[__.private].modifier.originalResource;
	}

	get resource() {
		return this[__.private].modifier.resource;
	}

	get modifier() {
		return this[__.private].modifier;
	}
	set modifier(value) {
		this[__.private].modifier = value;
	}

	get values() {
		return this._getValues('values');
	}

	get data() {
		return this._getValues('data');
	}

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

		let style = _private.modifier.object.bodyNode.getStyle();

		return style.isEnabled('InstancedDrawing');
	}

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

		if (_private.data.isChanged()) {
			return true;
		}

		if (_private.edgeData && _private.edgeData.isChanged()) {
			return true;
		}

		if (_private.effectData && _private.effectData.isChanged()) {
			return true;
		}

		if (_private.imagesData && _private.imagesData.isChanged()) {
			return true;
		}

		if (_private.uvData && _private.uvData.isChanged()) {
			return true;
		}

		return false;
	}

	// #region Common

	/**
	 * Get/Set the blending type.
	 * @type {BlendingType}
	 * @public
	 */
	get blendingType() {
		return this.values.data.blendingType;
	}
	set blendingType(value) {
		this._updateValue(Flag.BlendingType, 'blendingType', value);
	}

	/**
	 * Get/Set the side type.
	 * @type {SideType}
	 * @public
	 */
	get sideType() {
		return this.values.data.sideType;
	}
	set sideType(value) {
		this._updateValue(Flag.SideType, 'sideType', value);
	}

	/**
	 * Enable/Disable transparent mode.
	 * @type {Boolean}
	 * @public
	 */
	get transparent() {
		return this.values.data.transparent;
	}
	set transparent(value) {
		this._updateValue(Flag.Transparent, 'transparent', value);
	}

	/**
	 * Enable/Disable wireframe.
	 * @type {Boolean}
	 * @public
	 */
	get wireframe() {
		return this.values.data.wireframe;
	}
	set wireframe(value) {
		this._updateValue(Flag.Wireframe, 'wireframe', value);
	}

	/**
	 * Enable/Disable depth test.
	 * @type {Boolean}
	 * @public
	 */
	get depthTest() {
		return this.values.data.depthTest;
	}
	set depthTest(value) {
		this._updateValue(Flag.DepthTest, 'depthTest', value);
	}

	/**
	 * Enable/Disable depth write.
	 * @type {Boolean}
	 * @public
	 */
	get depthWrite() {
		return this.values.data.depthWrite;
	}
	set depthWrite(value) {
		this._updateValue(Flag.DepthWrite, 'depthWrite', value);
	}

	/**
	 * Enable/Disable the env mapping.
	 * @type {Boolean}
	 * @public
	 */
	get envMapping() {
		return this.values.data.envMapping;
	}
	set envMapping(value) {
		this._updateValue(Flag.EnvMapping, 'envMapping', value);
	}

	// #endregion

	// #region Image

	/**
	 * Get/Set the current image slot type.
	 * @type {ImageSlotType}
	 * @public
	 */
	get imageSlotType() {
		let _private = this[__.private];

		return _private.curImageSlotType;
	}
	set imageSlotType(value) {
		let _private = this[__.private];

		_private.curImageSlotType = _toUImageType(value);
	}

	/**
	 * Get/Set the (canvas/image/url) image resource.
	 * @type {*}
	 * @public
	 */
	get image() {
		let _private = this[__.private];

		return this.getImage(_private.curImageSlotType);
	}
	set image(value) {
		let _private = this[__.private];

		this.setImage(_private.curImageSlotType, value);
	}

	/**
	 * Get/Set the image resource of map.
	 * @type {THING.ImageTexture}
	 * @public
	 */
	get map() {
		return this.getImage(ImageSlotType.Map);
	}
	set map(value) {
		this.setImage(ImageSlotType.Map, value);
	}

	/**
	 * Get/Set the image resource of env map.
	 * @type {THING.ImageTexture}
	 * @public
	 */
	get envMap() {
		return this.getImage(ImageSlotType.EnvMap);
	}
	set envMap(value) {
		this.setImage(ImageSlotType.EnvMap, value);
	}

	/**
	 * Get/Set the image resource of alpha map.
	 * @type {THING.ImageTexture}
	 * @public
	 */
	get alphaMap() {
		return this.getImage(ImageSlotType.AlphaMap);
	}
	set alphaMap(value) {
		this.setImage(ImageSlotType.AlphaMap, value);
	}

	/**
	 * Get/Set the image resource of emissive map.
	 * @type {THING.ImageTexture}
	 * @public
	 */
	get emissiveMap() {
		return this.getImage(ImageSlotType.EmissiveMap);
	}
	set emissiveMap(value) {
		this.setImage(ImageSlotType.EmissiveMap, value);
	}

	/**
	 * Get/Set the image resource of normal map.
	 * @type {THING.ImageTexture}
	 * @public
	 */
	get normalMap() {
		return this.getImage(ImageSlotType.NormalMap);
	}
	set normalMap(value) {
		this.setImage(ImageSlotType.NormalMap, value);
	}

	/**
	 * Get/Set the image resource of color mapping.
	 * @type {THING.ImageTexture}
	 * @public
	 */
	get colorMapping() {
		return this.getImage(ImageSlotType.ColorMapping);
	}
	set colorMapping(value) {
		this.setImage(ImageSlotType.ColorMapping, value);
	}

	/**
	 * Get/Set the image resource of ao map.
	 * @type {THING.ImageTexture}
	 * @public
	 */
	get aoMap() {
		return this.getImage(ImageSlotType.AOMap);
	}
	set aoMap(value) {
		this.setImage(ImageSlotType.AOMap, value);
	}

	// #endregion

	// #region UV

	/**
	 * Get/Set the uv(matrix).
	 * @type {StyleUVMatrixResult}
	 * @public
	 */
	get uv() {
		let _private = this[__.private];

		return this.getUV(_private.curImageSlotType);
	}
	set uv(value) {
		let _private = this[__.private];

		let uvData = this._getImageUVMatrixByType(_private.curImageSlotType);
		if (!uvData) {
			return;
		}

		for (let key in value) {
			uvData[key] = value[key];
		}
	}

	// #endregion

	// #region Color

	/**
	 * The function to call when update value.
	 * @callback StyleValueOperationCallback
	 * @param {Number} baseValue The base value.
	 * @param {THING.Style} style The style.
	 */

	/**
	 * The function to call when update color.
	 * @callback StyleColorOperationCallback
	 * @param {Array<Number>} baseValue The base color.
	 * @param {THING.Style} style The style.
	 */

	/**
	 * Get/Set the opacity.
	 * @type {Number|StyleValueOperationCallback}
	 * @public
	 */
	get opacity() {
		return this.values.data.opacity;
	}
	set opacity(value) {
		this._updateValueOpFunc(Flag.Opacity, 'opacity', value, (baseValue, v) => {
			return baseValue * v;
		});
	}

	/**
	 * Get/Set the color.
	 * @type {Number|String|Array<Number>|StyleColorOperationCallback}
	 * @public
	 */
	get color() {
		let _private = this[__.private];

		return _private.data.values['color'];
	}
	set color(value) {
		this._updateColorOpFunc(Flag.Color, 'color', value);
	}

	/**
	 * Get/Set the metalness.
	 * @type {Number|StyleValueOperationCallback}
	 * @public
	 */
	get metalness() {
		return this.values.data.metalness;
	}
	set metalness(value) {
		this._updateValueOpFunc(Flag.Metalness, 'metalness', value);
	}

	/**
	 * Get/Set the roughness.
	 * @type {Number|StyleValueOperationCallback}
	 * @public
	 */
	get roughness() {
		return this.values.data.roughness;
	}
	set roughness(value) {
		this._updateValueOpFunc(Flag.Roughness, 'roughness', value);
	}

	/**
	 * Get/Set the emissive.
	 * @type {Number|String|Array<Number>|StyleColorOperationCallback}
	 * @public
	 */
	get emissive() {
		let _private = this[__.private];

		return _private.data.values['emissive'];
	}
	set emissive(value) {
		this._updateColorOpFunc(Flag.Emissive, 'emissive', value);
	}

	/**
	 * Get/Set the outline color.
	 * @type {Number|String|Array<Number>}
	 * @public
	 */
	get outlineColor() {
		let _private = this[__.private];

		return _private.data.values['outlineColor'];
	}
	set outlineColor(value) {
		this._updateColor(Flag.OutlineColor, 'outlineColor', value);
	}

	// #endregion

	// #region Object

	/**
	 * Get/Set the clipping planes.
	 * @type {Array<THING.ClippingPlanes>}
	 * @example
	 * let clippingPlanes = [
	 * 	{ direction: [0, -1, 0], height: 10 }, // top
	 * 	{ direction: [0, 1, 0], height: 10 }, // bottom
	 * 	{ direction: [-1, 0, 0], height: 10 }, // right
	 * 	{ direction: [1, 0, 0], height: 10 }, // left
	 * 	{ direction: [0, 0, -1], height: 10 }, // front
	 * 	{ direction: [0, 0, 1], height: 10 } // back
	 * ];
	 *
	 * let clippingPlanesObject = new THING.ClippingPlanes({
	 * 	parent: object,
	 * 	planes: clippingPlanes,
	 * });
	 *
	 * object.query('*', { includeSelf: true }).style.clippingPlanes = clippingPlanesObject;
	 * @public
	 *
	 */
	get clippingPlanes() {
		return this.values.data.clippingPlanes;
	}
	set clippingPlanes(value) {
		this._updateValue(Flag.ClippingPlanes, 'clippingPlanes', value);
	}

	/**
	 * Get/Set the edge.
	 * @type {StyleEdgeResult}
	 * @public
	 */
	get edge() {
		let _private = this[__.private];

		this._createEdgeData();

		return _private.edgeData.values;
	}
	set edge(value) {
		if (!value) {
			return;
		}

		let _private = this[__.private];

		this._createEdgeData();

		_private.edgeData.mergeValues(value);
	}

	/**
	 * Get/Set the effect.
	 * @type {StyleEffectResult}
	 * @public
	 */
	get effect() {
		let _private = this[__.private];

		this._createEffectData();

		return _private.effectData.values;
	}
	set effect(value) {
		if (!value) {
			return;
		}

		let _private = this[__.private];

		this._createEffectData();

		_private.effectData.mergeValues(value);
	}

	// #endregion

	// #region Attributes

	/**
	 * Get/Set attributes.
	 * @type {Object}
	 * @private
	 */
	get attributes() {
		let attributes = this._createAttributes();

		return attributes.values;
	}
	set attributes(value) {
		for (let key in value) {
			this.setAttribute(key, value);
		}
	}

	/**
	 * Get/Set uniforms.
	 * @type {Object}
	 * @private
	 */
	get uniforms() {
		let uniforms = this._createUniforms();

		return uniforms.values;
	}
	set uniforms(value) {
		for (let key in value) {
			this.setUniform(key, value);
		}
	}

	/**
	 * Get/Set macros.
	 * @type {Object}
	 * @private
	 */
	get macros() {
		let macros = this._createMacros();

		return macros.values;
	}
	set macros(value) {
		for (let key in value) {
			this.setMacro(key, value);
		}
	}

	// #endregion

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

	// #endregion

}

export { Style }