Source: selector/Selector.js

import { Box3 } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { ObjectExpression } from '../common/ObjectExpression';
import { AsyncSelector } from './AsyncSelector';
import { StyleSelector } from './StyleSelector';
import { InheritSelector } from './InheritSelector';

const _styleSelector = new StyleSelector();
const _inheritSelector = new InheritSelector();

// #region ObjectExpression-Cache

let _expressions = new Map();

function _getExpression(condition, mode) {
	let key = mode ? mode + ':' + condition : condition;

	let expression = _expressions.get(key);
	if (!expression) {
		expression = new ObjectExpression(condition, mode);

		_expressions.set(key, expression);
	}

	return expression;
}

// #endregion

// #region Private

function _selectObjectsByExpression(expression, objects, result) {
	objects.forEach(object => {
		if (expression.evaluate(object)) {
			result.push(object);
		}
	});
}

function _selectObjectsByRegExpName(condition, objects, result) {
	objects.forEach(object => {
		if (!condition.test(object.name)) {
			return;
		}

		result.push(object);
	});
}

function _selectObjectsByFunction(condition, objects, result) {
	objects.forEach(object => {
		if (!condition(object)) {
			return;
		}

		result.push(object);
	});
}

function _findObjectsByExpression(expression, objects) {
	for (let i = 0, l = objects.length; i < l; i++) {
		let object = objects[i];

		if (expression.evaluate(object)) {
			return object;
		}
	}

	return null;
}

function _findObjectsByRegExpName(condition, objects) {
	for (let i = 0, l = objects.length; i < l; i++) {
		let object = objects[i];

		if (condition.test(object.name)) {
			return object;
		}
	}

	return null;
}

function _findObjectsByFunction(condition, objects) {
	for (let i = 0, l = objects.length; i < l; i++) {
		let object = objects[i];

		if (condition(object)) {
			return object;
		}
	}

	return null;
}

// Get objects by condition.
function _getObjectsByCondition(condition, objects) {
	if (condition) {
		if (Utils.isString(condition)) {
			return Selector.select(condition, objects);
		}
		else if (Utils.isArray(condition)) {
			return condition;
		}
		else if (condition.isSelector) {
			return condition.objects;
		}
		else {
			return [condition];
		}
	}

	return null;
}

function _parseObjects(param) {
	if (Utils.isArray(param)) {
		return param;
	}

	if (Utils.isObject(param)) {
		return Utils.parseValue(param['objects'], []);
	}

	return [];
}

// #endregion

/**
 * @class Selector
 * The selector to find objects with some conditions.
 * @memberof THING
 * @public
 */
class Selector {

	static asyncSelector = null;

	constructor(param) {
		this._app = Utils.getCurrentApp();
		this._objects = _parseObjects(param);

		// Hook [index] to implement accessor by index
		let handler = {
			get: (target, prop) => {
				let object = this._objects[prop];
				if (object && object.isBaseObject) {
					return object;
				}

				return target[prop];
			},
		};

		let proxy = new Proxy(this, handler);
		Object.defineProperty(proxy, 'customFormatters', {
			enumerable: false,
			configurable: false,
			get: () => {
				return ['object', { object: this._objects }];
			}
		});

		return proxy;
	}

	static update(deltaTime) {
		ObjectExpression.update(deltaTime);
	}

	// #region Private

	_queryInWorker(condition, root, recursive) {
		return new Promise((resolve, reject) => {
			// Query in normal async mode if it's just simple expression
			if (condition == '*') {
				let result;

				if (root) {
					result = root.query(condition, { recursive });
				}
				else {
					result = this.onCreateInstance({ objects: this.objects });
				}

				resolve(result);
			}
			else {
				let promise;

				if (root) {
					promise = Selector.asyncSelector.queryAsync(root, recursive, condition);
				}
				else {
					promise = Selector.asyncSelector.selectAsync(this.objects, condition);
				}

				promise.then(result => {
					let objects = this._app.objectManager.objects;

					let filteredObjects = result.orderIDs.map(id => {
						return objects.get(id);
					});

					let selector = this.onCreateInstance({ objects: filteredObjects });
					resolve(selector);
				});
			}
		});
	}

	// #endregion

	/**
	 * Initialize with objects.
	 * @param {Array<THING.BaseObject>} objects The objects.
	 * @private
	 */
	init(objects) {
		// Clear previous objects
		this.clear();

		// Set members as Array
		this._objects = objects.slice(0);

		return this;
	}

	applyObjects(funcName, args) {
		this._objects.forEach(object => {
			object[funcName].apply(object, args);
		});
	}

	onCreateInstance() {
		return new Selector(arguments[0]);
	}

	// #region Objects Query Operations

	/**
	 * Select objects by condition with object's queryable state.
	 * @param {String} condition The conditions.
	 * @param {Array<Object>} objects The objects what to be queried.
	 * @returns {Array<Object>}
	 * @example
	 * // Select entities from objects
	 * let objects = app.query('.BaseObject');
	 * let selector = new THING.Selector(objects);
	 * let entities = selector.select('.Entity', objects);
	 * let entity = entities[0];
	 * let ret = entity.isEntity;
	 * // @expect(ret == true)
	 */
	select(condition, objects) {
		let result = [];

		objects = objects || this.objects;

		if (objects.length) {
			if (Utils.isString(condition)) {
				// All
				if (condition == '*') {
					result = objects.slice(0);
				}
				else {
					let expression = _getExpression(condition);

					_selectObjectsByExpression(expression, objects, result);
				}
			}
			// Reg Expression
			else if (Utils.isRegExp(condition)) {
				_selectObjectsByRegExpName(condition, objects, result);
			}
			// Function
			else if (Utils.isFunction(condition)) {
				_selectObjectsByFunction(condition, objects, result);
			}
		}

		return result;
	}

	_selectByCondition(condition, objects, mode) {
		let result = [];

		objects = objects || this.objects;

		if (objects.length) {
			if (Utils.isString(condition)) {
				let expression = _getExpression(condition, mode);

				_selectObjectsByExpression(expression, objects, result);
			}
		}

		return result;
	}

	_outputLogger(funcName, condition) {
		let msg = { "condition": condition };
		Utils.logger.api("Selector_" + funcName, { msg: JSON.stringify(msg) });
	}

	/**
	 * Test the single object with some conditions (without object's queryable state).
	 * @param {String} condition The conditions.
	 * @param {THING.BaseObject} object The object what to be queried.
	 * @returns {Boolean}
	 * @example
	 * // Test whether object fit specified condition
	 * let objects = app.query('.BaseObject');
	 * let selector = new THING.Selector(objects);
	 * let object = new THING.Entity();
	 * let ret = selector.test('.Entity', object);
	 * // @expect(ret == true);
	 * let box = new THING.Box();
	 * ret = selector.test('.Entity', box);
	 * // @expect(ret == false);
	 */
	test(condition, object) {
		if (Utils.isString(condition)) {
			// All
			if (condition == '*') {
				return true;
			}
			else {
				let expression = _getExpression(condition);

				return expression.evaluate(object);
			}
		}
		// Reg Expression
		else if (Utils.isRegExp(condition)) {
			return condition.test(object.name);
		}
		// Function
		else if (Utils.isFunction(condition)) {
			return condition(object);
		}

		return false;
	}

	/**
	 * Find objects with some conditions and store it in result.
	 * @param {String} condition The conditions.
	 * @returns {THING.Selector} The reference of target or new selector.
	 * @example
	 * // Query/select objects by specified condition
	 * let objects = app.query('.BaseObject');
	 * let selector = new THING.Selector(objects);
	 * let entitys = selector.query('.Entity');
	 * let entity = entitys[0];
	 * // @expect(entity.isEntity == true);
	 */
	query(condition, objects, target) {
		let objs = objects || this._objects;
		let result = this.select(condition, objs).filter(object => {
			return object.queryable;
		});

		target = target || this.onCreateInstance();

		if (_NEED_LOGGER) {
			this._outputLogger('query', condition.toString());
		}

		return target.init(result);
	}

	_queryByAttribute(objects, cb, target) {
		var objs = (objects || this._objects).filter((c) => {
			return cb(c) && c.queryable;
		});
		target = target || this.onCreateInstance();
		return target.init(objs);
	}

	_queryByCondition(condition, objects, target, mode) {
		let objs = objects || this._objects;

		let result = this._selectByCondition(condition, objs, mode).filter(object => {
			return object.queryable;
		});
		target = target || this.onCreateInstance();

		return target.init(result);
	}

	/**
	 * Query children by reg exp.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let objects = app.query('.BaseObject');
	 * let selector = new THING.Selector(objects);
	 * let car = selector.queryByRegExp(/car/);
	 * let ret = car.length != 0;
	 * // @expect(ret == true)
	 * @public
	 */
	queryByRegExp(condition, objects, target) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByRegExp', condition.toString());
		}

		return this.query(condition, objects, target);
	}

	/**
	 * Query children by reg.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let car = selector.queryByReg(/car/);
	 * 	console.log(car);
	 * @deprecated 2.7
	 * @private
	 */
	queryByReg(condition, objects, target) {
		return this.queryByRegExp(condition, objects, target);
	}

	/**
	 * Query children by tag.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let objects = app.query('.BaseObject');
	 * let selector = new THING.Selector(objects);
	 * let car = selector.queryByTags('Entity')
	 * let ret = car.length != 0;
	 * // @expect(ret == true)
	 * @public
	 */
	queryByTags(condition, objects, target) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByTags', condition);
		}

		return this._queryByCondition('tags(' + condition + ')', objects, target, 'tags');
	}

	/**
	 * Query children by name.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let objects = app.query('.BaseObject');
	 * let selector = new THING.Selector(objects);
	 * let car = selector.queryByName('car1');
	 * let ret = car.length != 0 && car[0].name == 'car1';
	 * // @expect(ret == true)
	 * @public
	 */
	queryByName(condition, objects, target) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByName', condition);
		}
		return this._queryByAttribute(objects, (c) => { return c.name == condition }, target);
	}

	/**
	 * Query children by uuid.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let objects = app.query('.BaseObject');
	 * let selector = new THING.Selector(objects);
	 * let car = selector.queryByUUID('1605');
	 * let ret = car.length != 0 && car[0].uuid == '1605';
	 * // @expect(ret == true)
	 * @public
	 */
	queryByUUID(condition, objects, target) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByUUID', condition);
		}
		return this._queryByAttribute(objects, (c) => { return c.uuid == condition }, target);
	}

	/**
	 * Query children by id.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let objects = app.query('.BaseObject');
	 * let selector = new THING.Selector(objects);
	 * let car = selector.queryById('car01');
	 * let ret = car.length != 0 && car[0].id == 'car01';
	 * // @expect(ret == true)
	 * @public
	 */
	queryById(condition, objects, target) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryById', condition);
		}
		return this._queryByAttribute(objects, (c) => { return c.id == condition }, target);
	}

	/**
	 * Query children by type.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let objects = app.query('.BaseObject');
	 * let selector = new THING.Selector(objects);
	 * let entitys = selector.queryByType('Entity');
	 * let ret = entitys.length != 0 && entitys[0].type == 'Entity';
	 * // @expect(ret == true)
	 * @public
	 */
	queryByType(condition, objects, target) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByType', condition);
		}
		var key = "is" + condition;
		return this._queryByAttribute(objects, (c) => { return c[key] == true}, target);
	}

	/**
	 * Query children by userData.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let objects = app.query('.BaseObject');
	 * let selector = new THING.Selector(objects);
	 * let car = selector.queryByUserData('id="666"')
	 * let ret = car.length != 0 && car[0].userData.id == '666';
	 * // @expect(ret == true)
	 * @public
	 */
	queryByUserData(condition, objects, target) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByUserData', condition);
		}
		return this._queryByCondition(condition, objects, target, 'userData');
	}

	/**
	 * Query children by userData.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let car = selector.queryByData('test=1')
	 * 	console.log(car);
	 * @deprecated 2.7
	 * @private
	 */
	queryByData(condition, objects, target) {
		return this.queryByUserData(condition, objects, target);
	}

	/**
	 * Find objects with some conditions and store it in result in async mode.
	 * @param {String} condition The conditions.
	 * @param {THING.BaseObject} root The root object.
	 * @param {Boolean} recursive True indicates query in recursive mode.
	 * @returns {Promise<THING.Selector>}
	 * @example
	 * // Query/select objects by specified condition from root object in async mode
	 * selector.queryAsync('.Entity', rootObject, true).then((result) => {
	 * 	console.log(result);
	 * });
	 * @private
	 */
	queryAsync(condition, root, recursive) {
		// Try to query in worker by async selector
		if (Selector.asyncSelector) {
			return this._queryInWorker(condition, root, recursive);
		}
		else {
			return new Promise((resolve, reject) => {
				let result;

				if (root) {
					result = root.query(condition, { recursive });
				}
				else {
					result = this.query(condition);
				}

				resolve(result);
			});
		}
	}

	/**
	 * Find objects with some conditions and returns the first one.
	 * @param {String} condition The conditions.
	 * @param {Array<Object>} objects The objects what to be queried.
	 * @returns {BaseObject}
	 * @example
	 * let objects = app.query('.BaseObject');
	 * let selector = new THING.Selector(objects);
	 * let entity = selector.find('.Entity')
	 * let ret = entity instanceof THING.Entity;;
	 * // @expect(ret == true)
	 * @public
	 */
	find(condition, objects) {
		let objs = objects || this._objects;
		if (!objs.length) {
			return null;
		}

		if (Utils.isString(condition)) {
			// All
			if (condition == '*') {
				return objs[0];
			}
			else {
				let expression = _getExpression(condition);

				return _findObjectsByExpression(expression, objs);
			}
		}
		// Reg Expression
		else if (Utils.isRegExp(condition)) {
			return _findObjectsByRegExpName(condition, objs);
		}
		// Function
		else if (Utils.isFunction(condition)) {
			return _findObjectsByFunction(condition, objs);
		}

		return null;
	}

	// #endregion

	// #region Objects Array Operations

	/**
	 * Push object into it.
	 * @param {THING.BaseObject|Array<THING.BaseObject>|THING.Selector} object The object what you want to push.
	 * @returns {Number} The length of objects after push.
	 * @example
	 * let objects = app.query('.Entity');
	 * let selector = new THING.Selector(objects);
	 * let count1 = selector.length;
	 * let object = new THING.BaseObject();
	 * let count2 = selector.push(object);
	 * let ret = count2 - count1 == 1;
	 * // @expect(ret == true)
	 * @public
	 */
	push(object) {
		if (object) {
			if (object.isSelector) {
				this._objects = this._objects.concat(object.objects);
			}
			else if (Utils.isArray(object)) {
				this._objects = this._objects.concat(object);
			}
			else {
				this._objects.push(object);
			}
		}

		return this._objects.length;
	}

	/**
	 * Add objects by selector and return new one with results.
	 * @param {THING.Selector|Array<THING.BaseObject>|THING.BaseObject} selector The selector or object(s) to add.
	 * @returns {THING.Selector}
 	 * @example
	 * let objects = app.query('.Entity');
	 * let selector = new THING.Selector(objects);
	 * let count1 = selector.length;
	 * let object1 = new THING.BaseObject();
	 * let object2 = new THING.BaseObject();
	 * selector.add([object1,object2]);
	 * let count2 = selector.length;
	 * let ret = count2 - count1 == 2;
	 * // @expect(ret == true)
	 * @public
	 */
	add(selector) {
		// Get source objects
		let srcObjects = [];
		if (selector.isSelector) {
			srcObjects = selector.objects;
		}
		else if (Utils.isArray(selector)) {
			srcObjects = selector;
		}
		else if (selector.isBaseObject) {
			srcObjects = [selector];
		}

		// Add objects
		let objects = this._objects.slice(0);
		for (let i = 0; i < srcObjects.length; i++) {
			let index = objects.indexOf(srcObjects[i]);
			if (index === -1) {
				objects.push(srcObjects[i]);
			}
		}

		return this.onCreateInstance({ objects });
	}

	/**
	 * Remove objects with some conditions and return new selector with results.
	 * @param {String} condition The condition.
	 * @returns {THING.Selector}
	 * @example
	 * let objects = app.query('.Entity');
	 * let selector = new THING.Selector(objects);
	 * selector.remove('.Entity');
	 * let count = selector.length;
	 * let ret = count == 0;
	 * // @expect(ret == true)
	 * @public
	 */
	remove(condition) {
		let candidates = _getObjectsByCondition(condition, this._objects);
		if (!candidates) {
			return this.onCreateInstance({ objects: this.objects });
		}

		// Remove objects
		let objects = this._objects.slice(0);
		for (let i = 0; i < candidates.length; i++) {
			let index = objects.indexOf(candidates[i]);
			if (index !== -1) {
				objects._removeAt(index);
			}
		}

		return this.onCreateInstance({ objects });
	}

	/**
	 * Remove objects with some conditions and return new selector with results.
	 * @param {String} condition The condition.
	 * @returns {THING.Selector}
	 * @example
	 * // Return new selector by removing selector/objects
	 * let objects = selector.not([obj1, obj2, obj3]);
	 * console.log(objects);
	 * @public
	 */
	not(condition) {
		return this.remove(condition);
	}

	/**
	 * Clear objects.
	 * @example
	 * let objects = app.query('.Entity');
	 * let selector = new THING.Selector(objects);
	 * selector.clear();
	 * let ret = count == 0;
	 * // @expect(ret == true)
	 * @public
	 */
	clear() {
		this._objects.length = 0;
	}

	/**
	 * The function to call when to get objects by forEach.
	 * @callback OnTraverseObjectsInSelector
	 * @param {THING.BaseObject} object The object.
	 * @param {Number} index The index of objects.
	 * @param {Array<THING.BaseObject>} objects The objects.
	 */

	/**
	 * Traverse objects.
	 * @param {OnTraverseObjectsInSelector} callback The callback function(returns false indicates break it, otherwise continue to process it).
	 * @example
	 * // Traverse all objects in selector
	 * selector.forEach((object) => {
	 * 	console.log(object);
	 * });
	 * @public
	 */
	forEach(callback) {
		let objects = this._objects;

		for (let i = 0, l = objects.length; i < l; i++) {
			callback(objects[i], i, objects);
		}
	}

	/**
	 * Traverse objects in async mode.
	 * @param {OnTraverseObjectsInSelector} callback The callback function(returns false indicates break it, otherwise continue to process it).
	 * @example
	 * // Objects fly one by one(it would wait for previous object fly to complete before start)
	 * selector.forEachAsync(object => {
	 * 	return THING.App.current.camera.flyToAsync({
	 * 		target: object,
	 * 		duration: 1 * 1000,
	 * 		distance: 10,
	 * 		delayTime: THING.Math.randomFloat(0, 1000),
	 * 		complete: function (ev) {
	 * 		}
	 * 	});
	 * });
	 * @public
	 */
	forEachAsync(callback) {
		this._objects.forEachAsync(callback);
	}

	/**
	 * Traverse objects in async mode.
	 * @param {OnTraverseObjectsInSelector} callback The callback function(returns false indicates break it, otherwise continue to process it).
	 * @deprecated 2.7
	 * @private
	 */
	forEachSync(callback) {
		this.forEachAsync(callback);
	}

	/**
	 * Traverse self and all children.
	 * @param {Function} callback The callback function.
	 * @example
	 * selector.traverse(object =>{
	 *  console.log(object)
	 * });
	 * @private
	 */
	traverse(callback) {
		var objects = this._objects;
		for (let i = 0, l = objects.length; i < l; i++) {
			objects[i].traverse(callback);
		}
	}

	/**
	 * Traverse self and all children. (Support for exit at traverse runtime)
	 * @param {Function} callback The callback function. (Return false to exit)
	 * @example
	 * selector.traverseBranch(object =>{
	 *  if(object.children.length){
	 *    return false;
	 *  }
	 *  return true;
	 * })
	 * @private
	 */
	traverseBranch(callback) {
		for (let i = 0, l = this._objects.length; i < l; i++) {
			const isBranch = this._objects[i].traverseBranch(callback);
			if (!isBranch) {
				return;
			}
		}
	}

	/**
	 * unique the selector. process the repeat and inherit object
	 * @private
	 * @example
	 *	selector.uniqueObjects();
	 */
	unique() {
		this._objects = Utils.uniqueObjects(this._objects);
	}

	/**
	 * The function to call when to get objects by map.
	 * @callback OnMapObjectsInSelector
	 * @param {THING.BaseObject} object The object.
	 * @param {Number} index The index of objects.
	 * @param {Array<THING.BaseObject>} objects The objects.
	 * @returns {THING.BaseObject}
	 */

	/**
	 * Traverse objects and return an new selector.
	 * @param {OnMapObjectsInSelector} callback The callback function.
	 * @returns {THING.Selector}
	 * @example
	 * // Returns all first child object of each objects
	 * let objects = selector.map((object) => {
	 * 	return object.children[0];
	 * });
	 * @public
	 */
	map(callback) {
		if (!callback) {
			return null;
		}

		let objects = this._objects.map((object, i) => {
			return callback(object, i, this._objects);
		}).filter(object => {
			return !!object;
		});

		return this.onCreateInstance({ objects });
	}

	/**
	 * The function to call when to reduce in selector.
	 * @callback OnReduceObjectsInSelector
	 * @param {Number} accumulator The accumulate value.
	 * @param {Number} currentValue The current value.
	 * @param {Number} currentIndex The current index.
	 * @param {Array<THING.BaseObject>} objects The objects.
	 * @returns {Number}
	 * @public
	 */

	/**
	 * Executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element.
	 * The final result of running the reducer across all elements of the array is a single value.
	 * @param {OnReduceObjectsInSelector} callback The callback function.
	 * @param {Number} initialValue The initial number.
	 * @returns {Number}
	 * @example
	 * // Get avg height of entities
	 * let entities = app.query('.Entity');
	 * let height = entities.reduce((value, entity) => {
	 * 	return value + entity.position[1];
	 * }, 0) / entities.length;
	 * @public
	 */
	reduce(callback, initialValue = 0) {
		if (!callback) {
			return 0;
		}

		return this._objects.reduce((accumulator, object, currentIndex) => {
			return callback(accumulator, object, currentIndex, this._objects);
		}, initialValue);
	}

	/**
	 * Return elements in range.
	 * @param {Number} start The start index.
	 * @param {Number} end? The end index.
	 * @returns {THING.Selector}
	 * @example
	 * // Returns [1, 4] range of objects
	 * let objects1 = selector.slice(1, 4);
	 *
	 * // Returns [1, ...] range of objects
	 * let objects2 = selector.slice(1);
	 * @public
	 */
	slice(start, end) {
		return this.onCreateInstance({ objects: this._objects.slice(start, end) });
	}

	/**
	 * The function to call when to get objects by filter.
	 * @callback OnFilterObjectsInSelector
	 * @param {THING.BaseObject} object The object.
	 * @param {Number} index The index of objects.
	 * @param {Array<THING.BaseObject>} objects The objects.
	 * @returns {Boolean}
	 * @public
	 */

	/**
	 * Filter.
	 * @param {OnFilterObjectsInSelector} callback The callback function.
	 * @returns {THING.Selector}
	 * @example
	 * // Filter objects with name
	 * let objects = selector.filter((object) => {
	 * 	return !!object.name;
	 * });
	 * @public
	 */
	filter(callback) {
		if (!callback) {
			return null;
		}

		let objects = this._objects.filter((object, i) => {
			return callback(object, i, this._objects);
		});

		return this.onCreateInstance({ objects });
	}

	/**
	 * The function to call when to sort objects.
	 * @callback OnSortObjectsInSelector
	 * @param {THING.BaseObject} obj1 The first object.
	 * @param {THING.BaseObject} obj2 The second object.
	 * @returns {Number}
	 * < 0: obj1 < obj2
	 * == 0: obj1 == obj2
	 * > 0: obj1 > obj2
	 * @public
	 */

	/**
	 * Sort objects from low to high by the result from callback function.
	 * @param {OnSortObjectsInSelector} callback The callback function to sort.
	 * @returns {THING.Selector}
	 * @example
	 * // Sort objects by name
	 * selector.sort((obj1, obj2) => {
	 * 	return obj1.name.localeCompare(obj2.name);
	 * })
	 * @public
	 */
	sort(callback) {
		let objects = this._objects;

		objects.sort(callback);

		return this;
	}

	/**
	 * Convert to array (Return a new array)
	 * @returns {Array<THING.BaseObject>}
	 * @example
	 * // Get/Convert objects in array mode
	 * let objects = selector.objects;
	 * console.log(objects);
	 * @public
	 */
	toArray() {
		return this._objects.slice(0);
	}

	/**
	 * Check whether has/includes element.
	 * @param {Object} object The object.
	 * @returns {Boolean}
	 * @example
	 * // Check whether includes specified object
	 * let exists = selector.includes(object);
	 * @public
	 */
	includes(object) {
		return this._objects.includes(object);
	}

	/**
	 * Get the index of element in objects.
	 * @param {Object} object The object.
	 * @returns {Number} -1 indicates that specified object does not exist.
	 * @example
	 * // Get the index of object in selector
	 * let index = selector.indexOf(object);
	 * @public
	 */
	indexOf(object) {
		return this._objects.indexOf(object);
	}

	/**
	 * Remove objects.
	 * @param {Number} index The start index.
	 * @param {Number} number The number of objects what to remove.
	 * @returns {THING.Selector} The removed objects.
	 * @example
	 * // Remove [1, 4] range of objects
	 * let removeObjects = selector.splice(1, 4);
	 * @public
	 */
	splice(index, number) {
		return this.onCreateInstance({ objects: this._objects.splice(index, number) });
	}

	/**
	 * Insert object by index.
	 * @param {Number} index The index to insert.
	 * @param {THING.BaseObject} object The object to insert.
	 * @returns {THING.Selector}
	 * @example
	 * // Insert objects at the front of selector
	 * selector.insert(0, [obj1, obj2]);
	 * @public
	 */
	insert(index, object) {
		this._objects.splice(index, 0, object);

		return this;
	}

	/**
	 * Remove object at index.
	 * @param {Number} index The index of object to remove.
	 * @returns {THING.Selector}
	 * @example
	 * // Remove the first object in selector.
	 * selector.remoteAt(0);
	 * @public
	 */
	removeAt(index) {
		this._objects.splice(index, 1);

		return this;
	}

	/**
	 * Swap objects by index.
	 * @param {Number} index0 The first index of objects.
	 * @param {Number} index1 The second index of objects.
	 * @returns {THING.Selector}
	 * @example
	 * // Swap (index: 0) and (index:3) objects in selector
	 * selector.swap(0, 3);
	 * @public
	 */
	swap(index0, index1) {
		this._objects._swap(index0, index1);

		return this;
	}

	/**
	 * Combine array.
	 * @param {THING.Selector|Array<THING.BaseObject>} selector The objects array or selector.
	 * @returns {THING.Selector}
	 * @example
	 * // Concat other objects/selector and create new selector
	 * let newSelector = selector.concat([obj1, obj2]);
	 * @public
	 */
	concat(selector) {
		// Concat objects and itself to new selector
		if (selector) {
			let candidates = this._objects.concat(selector.isSelector ? selector.objects : selector);

			return this.onCreateInstance({ objects: Array.from(new Set(candidates)) });
		}
		// Return itself as new one
		else {
			return this.onCreateInstance({ objects: this._objects.slice() });
		}
	}

	/**
	 * Reverse the objects.
	 * @returns {THING.Selector}
	 * @example
	 * // Reverse objects in selector
	 * selector.reverse();
	 * @public
	 */
	reverse() {
		this._objects.reverse();

		return this;
	}

	/**
	 * Check whether has object or not.
	 * @param {Object} object The object.
	 * @returns {Boolean}
	 * @example
	 * // Check whether has specified object
	 * let exists = selector.has(object);
	 * @public
	 */
	has(object) {
		return this._objects.indexOf(object) !== -1;
	}

	/**
	 * Check whether it's the same selector.
	 * @param {Array<Object>|THING.Selector} objects The objects array or selector.
	 * @returns {Boolean}
	 * @example
	 * let isSame = selector.equals([obj1, obj2]);
	 * @public
	 */
	equals(objects) {
		if (this.length != objects.length) {
			return false;
		}

		for (let i = 0; i < this.length; i++) {
			if (this[i] != objects[i]) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Get/Set the length of objects.
	 * @type {Number}
	 * @example
	 * let length = selector.length;
	 * @public
	 */
	get length() {
		return this._objects.length;
	}
	set length(value) {
		this._objects.length = value;
	}

	/**
	 * Get the objects.
	 * @type {Array<THING.BaseObject>}
	 * @example
	 * let objects = selector.objects;
	 * @private
	 */
	get objects() {
		return this._objects;
	}

	// #endregion

	// #region Objects State Operations

	/**
	 * Get the loaded objects.
	 * @returns {Array<THING.BaseObject>}
	 * @private
	 */
	getLoadedObjects() {
		return this._objects.filter(object => {
			return object.loaded;
		});
	}

	/**
	 * Get the unloaded objects.
	 * @returns {Array<THING.BaseObject>}
	 * @private
	 */
	getUnloadedObjects() {
		return this._objects.filter(object => {
			return !object.loaded;
		});
	}

	/**
	 * Get the visible objects.
	 * @returns {Array<THING.BaseObject>}
	 * @private
	 */
	getVisibleObjects() {
		return this._objects.filter(object => {
			return object.visible;
		});
	}

	/**
	 * Get the invisible objects.
	 * @returns {Array<THING.BaseObject>}
	 * @private
	 */
	getInvisibleObjects() {
		return this._objects.filter(object => {
			return !object.visible;
		});
	}

	/**
	 * Make objects in instanced drawing mode.
	 * @param {Boolean} value True indicates enable instanced drawing mode.
	 * @param {Object} options The options.
	 * @example
	 * // Enable objects instanced drawing
	 * selector.makeInstancedDrawing(true);
	 *
	 * // Disable objects instanced drawing
	 * selector.makeInstancedDrawing(false);
	 * @public
	 */
	makeInstancedDrawing(value, options) {
		this._objects.forEach(object => {
			if (!object.isObject3D) {
				return;
			}

			object.makeInstancedDrawing(value, options);
		});
	}

	/**
	 * Get the instanced drawing objects.
	 * @returns {Array<THING.BaseObject>}
	 * @example
	 * // Get the objects what has enabled instanced drawing
	 * let instancedDrawingObjects = selector.getInstancedDrawingObjects();
	 * @public
	 */
	getInstancedDrawingObjects() {
		return this._objects.filter(object => {
			return object.isInstancedDrawing;
		});
	}

	/**
	 * Set visible state.
	 * @param {Boolean} value True indicates show it, otherwise hide it.
	 * @param {Boolean} [recursive=false] True indicates process it with all children.
	 * @example
	 * // Set visible attribute of all objects in selector to true
	 * selector.setVisible(true);
	 *
	 * // Set visible attribute of all objects and its children in selector to true
	 * selector.setVisible(true, true);
	 * @public
	 */
	setVisible(value, recursive = false) {
		this._objects.forEach(object => {
			object.setVisible(value, recursive);
		});
	}

	/**
	 * Set all objects and its children visible state.
	 * @type {Boolean}
	 * @example
	 * // Set visible attribute of all objects and its children in selector to true
	 * selector.visible = true;
	 * @public
	 */
	set visible(value) {
		this._objects.forEach(object => {
			object.visible = value;
		});
	}

	/**
	 * Set all objects and its children pickable state.
	 * @type {Boolean}
	 * @example
	 * // Set pickable attribute of all objects and its children in selector to true
	 * selector.pickable = true;
	 * @public
	 */
	set pickable(value) {
		this._objects.forEach(object => {
			object.pickable = value;
		});
	}

	// #endregion

	// #region Objects Resource Operations

	/**
	 * Wait for all objects load completed.
	 * @returns {Promise<any>}
	 * @example
	 * // Wait all objects load finished then print their name
	 * selector.waitForComplete().then((objects) => {
	 * 	objects.forEach(object => {
	 * 		console.log(object.name);
	 * 	});
	 * });
	 * @public
	 */
	waitForComplete() {
		return this._objects.waitForComplete();
	}

	/**
	 * Wait for all objects sync action finished(would process objects one by one).
	 * @param {Function} callback The callback function.
	 * @returns {Promise<any>}
	 * @example
	 * // Load objects one by one
	 * selector.waitForEachSync(async (object) => {
	 * 	return object.loadResource();
	 * });
	 * @private
	 */
	waitForEachAsync(callback) {
		return this._objects.waitForEachAsync(callback);
	}

	/**
	 * Wait for all objects sync action finished(would process objects one by one).
	 * @param {Function} callback The callback function.
	 * @returns {Promise<any>}
	 * @deprecated 2.7
	 * @private
	 */
	waitForEachSync(callback) {
		return this.waitForEachAsync(callback);
	}

	/**
	 * Destroy all.
	 * @example
	 * selector.destroy();
	 * @public
	 */
	destroy() {
		let objects = this._objects.slice(0);

		objects.forEach(object => {
			object.destroy();
		});
	}

	/**
	 * Load resource.
	 * @param {Object} options The load options.
	 * @example
	 * selector.loadResource();
	 * @public
	 */
	loadResource(options) {
		this._objects.forEach(object => {
			object.loadResource(false, options);
		});
	}

	/**
	 * Unload resource.
	 * @example
	 * selector.unloadResource();
	 * @public
	 */
	unloadResource() {
		this._objects.forEach(object => {
			object.unloadResource();
		});
	}

	// #endregion

	// #region Objects Event Operations

	/**
	 * Register all objects event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {Function} callback The callback function.
	 * @param {String} tag The event tag.
	 * @param {Number} priority The priority value(default is 0, higher value will be processed first)
	 * @param {ObjectEventOptions} options The options.
	 * @example
	 * // Register all entities with 'click' event listener
     * let mark = 0;
	 * let entity = new THING.Entity();
	 * entity.on('test', ()=>{
	 *    mark = 1;
	 * })
	 * let selector = new THING.Selector([entity]);
	 * let markOn = 0;
	 * selector.on('testOn', function(ev) {
	 * 		markOn = 1;
	 * });
	 * selector.trigger('testOn');
	 * // @expect(markOn == 1 && mark == 1);
	 * @public
	 */
	on() {
		this.applyObjects('on', arguments);
	}

	/**
	 * Register all objects event what just trigger once time.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {Function} callback The callback function.
	 * @param {String} tag The event tag.
	 * @param {Number} priority The priority value(default is 0, higher value will be processed first)
	 * @param {ObjectEventOptions} options The options.
	 * @example
	 * // Register all entities with 'click' event listener in one time
	 * let mark = 0;
	 * let entity = new THING.Entity();
	 * entity.on('test', ()=>{
	 *    mark = 1;
	 * })
	 * let selector = new THING.Selector([entity]);
	 * let markOnce = 0;
	 * selector.once('testOnce', function(ev) {
	 * 		markOnce = 1;
	 * });
	 * selector.trigger('testOnce');
	 * // @expect(markOnce == 1 && mark == 1);
	 * markOnce = 0;
	 * mark = 0;
	 * selector.trigger('testOnce');
	 * // @expect(markOnce == 0 && mark == 0);
	 * @public
	 */
	once() {
		this.applyObjects('once', arguments);
	}

	/**
	 * Unregister all objects event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {String} tag The event tag.
	 * @example
	 * // Unregister all entities with 'click' event listener by tag name
	 * let mark = 0;
	 * let entity = new THING.Entity();
	 * entity.on('test', ()=>{
	 *    mark = 1;
	 * })
	 * let selector = new THING.Selector([entity]);
	 * let markOff = 0;
	 * selector.on('testOff', function(ev) {
	 * 		markOff = 1;
	 * });
	 * selector.trigger('testOff');
	 * // @expect(markOff == 1 && mark == 1);
	 * markOff = 0;
	 * mark = 0;
	 * selector.off('testOff');
	 * selector.trigger('testOff');
	 * // @expect(markOff == 0 && mak == 0);
	 * @public
	 */
	off() {
		this.applyObjects('off', arguments);
	}

	/**
	 * Pause all objects event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {String} tag The event tag.
	 * @example
	 * // Pause all entities with 'click' event listener by tag name
	 * let mark = 0;
	 * let entity = new THING.Entity();
	 * entity.on('test', ()=>{
	 *    mark = 1;
	 * })
	 * let selector = new THING.Selector([entity]);
	 * let markPause = 0;
	 * selector.on('testPause', function(ev) {
	 * 		markPause = 1;
	 * });
	 * selector.trigger('testPause');
	 * // @expect(markPause == 1 && mark == 1);
	 * markPause = 0;
	 * mark = 0;
	 * selector.PauseEvent('testPause');
	 * selector.trigger('testPause');
	 * // @expect(markPause == 0 && mark == 0);
	 * @public
	 */
	pauseEvent() {
		this.applyObjects('pauseEvent', arguments);
	}

	/**
	 * Resume all objects event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {String} tag The event tag.
	 * @example
	 * // Resume all entities with 'click' event listener by tag name
	 * let mark = 0;
	 * let entity = new THING.Entity();
	 * entity.on('test', ()=>{
	 *    mark = 1;
	 * })
	 * let selector = new THING.Selector([entity]);
	 * let markResume = 0;
	 * selector.on('testPause', function(ev) {
	 * 		markResume = 1;
	 * });
	 * selector.trigger('testPause');
	 * // @expect(markResume == 1 && mark == 1);
	 * markResume = 0;
	 * mark = 0;
	 * selector.PauseEvent('testPause');
	 * selector.trigger('testPause');
	 * // @expect(markResume == 0 && mark == 0);
	 * selector.resumeEvent('testPause');
	 * selector.trigger('testPause');
	 * // @expect(markResume == 1 && mark == 1);
	 * @public
	 */
	resumeEvent() {
		this.applyObjects('resumeEvent', arguments);
	}

	/**
	 * Trigger all objects event.
	 * @param {String} type The event type.
	 * @param {Object} ev The event info.
	 * @param {Object} options The options.
	 * @param {String} options.tag The tag name.
	 * @example
	 * // Trigger all entities with 'click' event listener by tag name
	 * let mark = 0;
	 * let entity = new THING.Entity();
	 * entity.on('test', ()=>{
	 *   mark = 1;
	 * })
	 * let selector = new THING.Selector([entity]);
	 * selector.trigger('test');
	 * // @expect(mark == 1)
	 * @public
	 */
	trigger(type, ev) {
		this._objects.forEach(object => {
			object.trigger(type, Object.assign({}, ev));
		});
	}

	// #endregion

	// #region Objects Lerp Operations

	stopMoving() {
		this.applyObjects('stopMoving', arguments);
	}

	moveTo() {
		this.applyObjects('moveTo', arguments);
	}

	movePath() {
		this.applyObjects('movePath', arguments);
	}

	stopRotating() {
		this.applyObjects('stopRotating', arguments);
	}

	rotateTo() {
		this.applyObjects('rotateTo', arguments);
	}

	stopScaling() {
		this.applyObjects('stopScaling', arguments);
	}

	scaleTo() {
		this.applyObjects('scaleTo', arguments);
	}

	stopFading() {
		this.applyObjects('stopFading', arguments);
	}

	fadeIn() {
		this.applyObjects('fadeIn', arguments);
	}

	fadeOut() {
		this.applyObjects('fadeOut', arguments);
	}

	stopAnimation() {
		this.applyObjects('stopAnimation', arguments);
	}

	stopAllAnimations() {
		this.applyObjects('stopAllAnimations', arguments);
	}

	playAnimation() {
		this.applyObjects('playAnimation', arguments);
	}

	// #endregion

	// #region Objects Component Operations

	addComponent() {
		this.applyObjects('addComponent', arguments);
	}

	removeComponent() {
		this.applyObjects('removeComponent', arguments);
	}

	// #endregion

	// #region Objects Wrapper Operations

	get style() {
		return _styleSelector.init(this._objects);
	}

	get inherit() {
		return _inheritSelector.init(this._objects);
	}

	// #endregion

	// #region Objects Bounding Operations

	/**
	 * Get the axis-aligned bounding box(AABB).
	 * @returns {THING.Box3}
	 * @example
	 * let selector = new THING.Selector();
	 * let box = selector.getBoundingBox();
	 * let ret = box.size[0] == 0;
	 * // @expect(ret == true);
	 * @public
	 */
	getBoundingBox() {
		let aabb = new Box3();

		this._objects.forEach(object => {
			let boundingBox = object.boundingBox;

			aabb.expandByPoint(boundingBox.min);
			aabb.expandByPoint(boundingBox.max);
		});

		return aabb;
	}

	// #endregion

	/**
	 * Check class type.
	 * @type {Boolean}
	 * @example
	 * let selector = new THING.Selector();
	 * // @expect(selector.isSelector == true);
	 */
	get isSelector() {
		return true;
	}

}

// #region Static Interface

let _selector = new Selector();

Selector.init = function (param = {}) {
	let workerUrl = param['workerUrl'];
	if (workerUrl) {
		Selector.asyncSelector = new AsyncSelector({ workerUrl });
	}
}

Selector.buildExpression = function (condition, mode) {
	return new ObjectExpression(condition, mode);
}

Selector.testByExpression = function (expression, object) {
	return expression.evaluate(object);
}

Selector.testByRegExpName = function (condition, object) {
	return condition.test(object.name);
}

Selector.testByFunction = function (condition, object) {
	return condition(object);
}

Selector.test = function (condition, object) {
	return _selector.test(condition, object);
}

Selector.select = function (condition, objects) {
	return _selector.select(condition, objects);
}

Selector.query = function (condition, objects) {
	return _selector.query(condition, objects);
}

Selector.queryAsync = function (condition, root, recursive) {
	return _selector.queryAsync(condition, root, recursive);
}

Selector.updateObjectAttribute = function (object, key, value) {
	if (Selector.asyncSelector) {
		Selector.asyncSelector.updateObjectAttribute(object, key, value);
	}
}

Selector.addObject = function (object) {
	if (Selector.asyncSelector) {
		Selector.asyncSelector.addObject(object);
	}
}

Selector.removeObject = function (object) {
	if (Selector.asyncSelector) {
		Selector.asyncSelector.removeObject(object);
	}
}

Selector.updateObjectAttribute = function (object, key, value) {
	if (Selector.asyncSelector) {
		Selector.asyncSelector.updateObjectAttribute(object, key, value);
	}
}

// #endregion

export { Selector };