Source: objects/BaseObject.js

import { Flags, ObjectProxy } from '@uino/base-thing';
import { MathUtils } from '../math/MathUtils';
import { Utils } from '../common/Utils'
import { Selector } from '../selector/Selector';
import { DynamicSelector } from '../selector/DynamicSelector';
import { BaseComponentGroup } from '../components/BaseComponentGroup';
import { EventType } from '../const';

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

const Flag = {
	Destroyed: 1 << 0,
	Dirty: 1 << 1,
	Queryable: 1 << 2,
	Destroyable: 1 << 3,
}

let _orderId = 1;
let _dummySelector = new Selector();
let _eventListeners = [];
let _defaultParams = {};

// #region Private Functions



// #endregion

/**
 * @class BaseObject
 * The base object.
 * @memberof THING
 * @extends THING.BaseComponentGroup
 * @public
 */
class BaseObject extends BaseComponentGroup {

	/**
	 * @typedef BaseObjectInitialOptions
	 * @property {String} [name=''] The name.
	 * @property {String} [id=''] The id.
	 */

	/**
	 * The base object.
	 * @param {BaseObjectInitialOptions} param The initial parameters.
	 */
	constructor(param = {}) {
		super();

		this[__.private] = {};
		let _private = this[__.private];
		let app = Utils.getCurrentApp();

		// Common
		_private.app = app;
		_private.userData = null;
		_private.type = '';

		// Temporary Data
		_private.tempData = null;

		// Update internal order ID for fast searching
		_private.orderId = _orderId++;

		// Relation
		_private.parent = null;
		_private.children = null;

		// The proxy object
		_private.proxyObject = null;

		// Show important info what we can not wrap into private space, so bind to object
		this._name = Utils.parseValue(param['name'], '');
		this._id = Utils.parseValue(param['id'], '');
		this._uuid = param['uuid'] || MathUtils.generateUUID();

		// Setup flags
		this._flags = new Flags();
		this._flags.enable(Flag.Queryable, Utils.parseValue(param['queryable'], true));
		this._flags.enable(Flag.Destroyable, Utils.parseValue(param['destroyable'], true));

		// Setup type first, because we need type attribute
		this.onSetupType(param);

		// Setup
		this.onBeforeSetup(param);
		this.onSetupParent(param); // Set parent first to make sure parent order ID is valid
		this.onSetupAsyncSelector(param); // Add object first to make sure object order ID is valid
		this.onSetupUserData(param);
		this.onAfterSetup(param);

		// Add object into manager
		app.objectManager.addObject(this);
	}

	// #region Private

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

		// Remove it from previous parent
		if (_private.parent) {
			let children = _private.parent.children;
			let index = children.indexOf(this);
			if (index !== -1) {
				children.removeAt(index);
			}

			_private.parent = null;
		}
	}

	_addEventListener(type, condition, callback, tag, priority, options, once = false) {
		let info = Utils.parseEvent(type, condition, callback, tag, priority, options, this.onInitEventArgs);
		if (this.onParseEvent(info)) {
			return this.app.eventManager.addEventListener(info.type.toLowerCase(), this, info.condition, info.callback, info.tag, {
				once,
				priority: info.priority,
				useCapture: info.useCapture,
				enumerable: info.enumerable,
				pausable: info.pausable,
				includeSelf: info.includeSelf
			});
		}

		return null;
	}

	_getEventListener(type, condition, tag) {
		return this.app.eventManager.getEventListener(type.toLowerCase(), this, condition, tag);
	}

	_removeEventListener(type, condition, tag) {
		this.app.eventManager.removeEventListener(type.toLowerCase(), this, condition, tag);
	}

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

		let userData = _private.userData;
		if (userData) {
			return userData;
		}

		if (Selector.asyncSelector) {
			_private.userData = new ObjectProxy({
				onChange: (ev) => {
					Selector.updateObjectAttribute(this, 'userData', ev.data);
				}
			});
		}
		else {
			_private.userData = {};
		}

		return _private.userData;
	}

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

		if (!_private.tempData) {
			_private.tempData = {
				tickableCounter: 0,
				resizableCounter: 0,
			};

			if (callback) {
				callback(_private.tempData);
			}
		}

		return _private.tempData;
	}

	_getParentObject(param) {
		let parent = param['parent'];
		if (parent === null) {
			return null;
		}

		return parent || this.app.root;
	}

	_isExpression(condition) {
		// It's string format
		if (condition._startsWith('"') || condition._startsWith('\'')) {
			return true;
		}

		// It's id format
		if (condition._startsWith('#')) {
			return true;
		}

		// It's type format
		if (condition._startsWith('.')) {
			return true;
		}

		// It's attribute name
		if (condition._startsWith('[') && condition._endsWith(']')) {
			return true;
		}

		// Has OR operator
		if (condition.indexOf('||') !== -1 || condition.indexOf('|') !== -1) {
			return true;
		}

		// Has AND operator
		if (condition.indexOf('&&') !== -1 || condition.indexOf('&') !== -1) {
			return true;
		}

		// It's tags format
		if (condition._startsWith('tags')) {
			if (condition[4] === ':' || condition[4] === '(') {
				return true;
			}
		}

		return false;
	}

	// #endregion

	// #region Overrides

	onAddTickableObject() {
		let tempData = this._getTempData();
		if (tempData['tickableCounter'] === 0) {
			this.app.objectManager.addTickableObject(this);
		}

		tempData['tickableCounter']++;
	}

	onAddResizableObject() {
		let tempData = this._getTempData();
		if (tempData['resizableCounter'] === 0) {
			this.app.objectManager.addResizableObject(this);
		}

		tempData['resizableCounter']++;
	}

	onRemoveTickableObject() {
		let tempData = this._getTempData();

		tempData['tickableCounter']--;
		if (tempData['tickableCounter'] === 0) {
			this.app.objectManager.removeTickableObject(this);
		}
	}

	onRemoveResizableObject() {
		let tempData = this._getTempData();

		tempData['resizableCounter']--;
		if (tempData['resizableCounter'] === 0) {
			this.app.objectManager.removeResizableObject(this);
		}
	}

	// #endregion

	// #region Setup

	onBeforeSetup(param) {
	}

	onAfterSetup(param) {
	}

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

		let prototype = this.__proto__;

		let className = Utils.getClassNameFromProto(prototype);
		_private.type = className;

		// Check whether we had processed yet
		prototype.__inherits = prototype.__inherits || {};
		if (prototype.__inherits[className]) {
			return;
		}

		prototype.__inherits[className] = true;

		// The types of each class to get inherition
		let types = {};

		Object.defineProperty(prototype, '__types', {
			enumerable: false,
			configurable: false,
			get() {
				return types;
			},
		});

		// Get the class names
		let classNames = this.app.global.getClassNamesFromObject(this);
		classNames.forEach(name => {
			types[name] = true;

			let key = 'is' + name;

			if (!this[key]) {
				Object.defineProperty(prototype, key, {
					enumerable: false,
					configurable: false,
					get() {
						return types[name];
					},
				});
			}
		});
	}

	onSetupParent(param) {
		let parent = this._getParentObject(param);
		if (!parent) {
			return;
		}

		parent.add(this, { attachMode: false });
	}

	onSetupAsyncSelector(param) {
		// Add into async selector
		Selector.addObject(this);
	}

	onSetupUserData(param) {
		let fromUserData = param['userData'];
		if (!fromUserData) {
			return;
		}

		let toUserData = this._getUserData();
		if (toUserData.isObjectProxy) {
			toUserData.copy(fromUserData);
		}
		else {
			Object.assign(toUserData, fromUserData);
		}
	}

	// #endregion

	// #region Overrides

	/**
	 * When update.
	 * @param {Number} deltaTime The delta time in seconds.
	 * @private
	 */
	onUpdate(deltaTime) {
		let tickableComponents = this.tickableComponents;
		if (tickableComponents) {
			for (let i = 0, l = tickableComponents.length; i < l; i++) {
				let component = tickableComponents[i];
				if (component.active === false) {
					continue;
				}

				component.onUpdate(deltaTime);
			}
		}
	}

	/**
	 * When resize.
	 * @param {Number} width The width in pixel.
	 * @param {Number} height The height in pixel.
	 * @private
	 */
	onResize(width, height) {
		let resizableComponents = this.resizableComponents;
		if (resizableComponents) {
			for (let i = 0, l = resizableComponents.length; i < l; i++) {
				let component = resizableComponents[i];

				component.onResize(width, height);
			}
		}
	}

	/**
	 * When refresh.
	 * @private
	 */
	onRefresh() {
		let refreshableComponents = this.refreshableComponents;
		if (refreshableComponents) {
			for (let i = 0, l = refreshableComponents.length; i < l; i++) {
				let component = refreshableComponents[i];

				component.onRefresh();
			}
		}

		this._flags.enable(Flag.Dirty, false);
	}

	/**
	 * When before destroy.
	 * @returns {Boolean} false indicates break the destroy operation.
	 * @private
	 */
	onBeforeDestroy() {
		let flags = this._flags;

		// Check whether can be destroyed
		if (!flags.has(Flag.Destroyable)) {
			return false;
		}

		// Check whether had destroyed
		if (flags.has(Flag.Destroyed)) {
			return false;
		}

		return true;
	}

	/**
	 * When destroy.
	 * @private
	 */
	onDestroy() {
		let _private = this[__.private];

		// Notify it's going to destroy it
		this.trigger(EventType.Destroy);

		// Notify parent to remove child object
		let parent = _private.parent;
		if (parent) {
			parent.onBeforeRemoveChild(this);

			this.onSetParent(null);

			parent.onAfterRemoveChild(this);
		}

		// Remove all children
		if (_private.children) {
			let children = _private.children.slice(0);
			children.forEach(child => {
				child.destroy();
			});

			_private.children.clear();
		}

		// Remove it from object manager
		_private.app.objectManager.removeObject(this);

		// Remove it from async selector
		Selector.removeObject(this);

		// Remove all components
		this.removeAllComponents(true);

		// Clear all accessor info
		this.clearAccessorInfo();

		// Clear user data
		let userData = _private.userData;
		if (userData) {
			if (userData.isObjectProxy) {
				userData.dispose();
			}

			_private.userData = null;
		}

		// We must clear parent here, due to 'AfterDestroy' event need to check parent object
		_private.parent = null;

		// Remove all events of it
		_private.app.eventManager.removeAllEventListeners(this);

		// Make it destroyed, be careful:
		// We must set the destroy flag after object manager remove it to prevent 'Destroy' event trigger failed
		this._flags.enable(Flag.Destroyed, true);
	}

	/**
	 * When after destroy.
	 * @private
	 */
	onAfterDestroy() {
		let _private = this[__.private];

		// Remove it from previous parent
		this._removeFromParent();

		// Resume all paused events
		_private.app.eventManager.resumeAllEvents(this);

		_private.app = null;
	}

	onParseEvent(info) {
		return true;
	}

	onBeforeAddChild(object) {
		// Notify all components
		let components = this.components;
		components.forEach((component) => {
			if (component.onBeforeAddChild) {
				component.onBeforeAddChild(object);
			}
		})

		this.trigger(EventType.BeforeAddChild, { child: object });
	}

	onAddChild(object) {
		object.onSetParent(this);
	}

	onAfterAddChild(object) {
		// Notify all components
		let components = this.components;
		components.forEach((component) => {
			if (component.onAfterAddChild) {
				component.onAfterAddChild(object);
			}
		})

		this.trigger(EventType.AfterAddChild, { child: object });
	}

	onBeforeRemoveChild(object) {
		// Notify all components
		let components = this.components;
		components.forEach((component) => {
			if (component.onBeforeRemoveChild) {
				component.onBeforeRemoveChild(object);
			}
		})

		this.trigger(EventType.BeforeRemoveChild, { child: object });
	}

	onAfterRemoveChild(object) {
		// Notify all components
		let components = this.components;
		components.forEach((component) => {
			if (component.onAfterRemoveChild) {
				component.onAfterRemoveChild(object);
			}
		})

		this.trigger(EventType.AfterRemoveChild, { child: object });
	}

	// #endregion

	// #region BaseComponentGroup Interfaces

	onAddComponent(component, args) {
		super.onAddComponent(component, args);

		if (component.onUpdate || component.onRender) {
			this.onAddTickableObject();
		}

		if (component.onResize) {
			this.onAddResizableObject();
		}
	}

	onRemoveComponent(component) {
		super.onRemoveComponent(component);

		if (component.onUpdate || component.onRender) {
			this.onRemoveTickableObject();
		}

		if (component.onResize) {
			this.onRemoveResizableObject();
		}
	}

	onBeforeSetParent() {

	}

	// #endregion

	// #region Common

	/**
	 * Check whether has attribute.
	 * @param {String} name The attribute name, it can use like 'a/b/c' to access attribute.
	 * @returns {Boolean}
	 * @example
	 * let object = new THING.BaseObject();
	 * object.userData['power'] = 100;
	 * let ret = object.hasAttribute('userData/power');
	 * // @expect(ret == true)
	 * @public
	 */
	hasAttribute(name, separator = '/') {
		return Utils.hasAttribute(this, name, separator);
	}

	/**
	 * Get attribute value.
	 * @param {String} name The attribute name, it can use like 'a/b/c' to access attribute.
	 * @returns {*}
	 * @example
	 * let object = new THING.BaseObject();
	 * object.userData['power'] = 100;
	 * let power = object.getAttribute('userData/power');
	 * // @expect(power == 100)
	 * @public
	 */
	getAttribute(key, separator = '/') {
		return Utils.getAttribute(this, key, separator);
	}

	/**
	 * Set attribute value.
	 * @param {String} name The attribute name, it can use like 'a/b/c' to access attribute.
	 * @param {*} value The attribute value.
	 * @example
	 * let object = new THING.BaseObject();
	 * object.setAttribute('userData/power', 200);
	 * let power = object.getAttribute('userData/power');
	 * // @expect(power == 200)
	 * @public
	 */
	setAttribute(key, value, separator = '/') {
		Utils.setAttribute(this, key, value, separator);
	}

	/**
	 * Set attribute values.
	 * @param {Object} attributes The attribute values.
	 * @param {Boolean} [overwrite=true] True indicates overwrite attribute.
	 * @example
	 * let object = new THING.BaseObject();
	 * object.setAttributes({
	 *   "userData/name": 'Mr.Door',
	 *   "userData/age": 18
	 * })
	 * let name = object.getAttribute('userData/name');
	 * // @expect(name == 'Mr.Door')
	 * let age = object.getAttribute('userData/age');
	 * // @expect(age == 18)
	 * @public
	 */
	setAttributes(attributes = {}, overwrite = true, separator = '/') {
		for (let key in attributes) {
			if (!overwrite && !Utils.isUndefined(this.getAttribute(key))) {
				continue;
			}

			this.setAttribute(key, attributes[key], separator);
		}

		this.trigger(EventType.ChangeAttributes, { attributes });
	}

	/**
	 * Get application.
	 * @type {THING.App}
	 * @example
	 * let object = new THING.BaseObject();
	 * let app = object.app;
	 * let ret = app == THING.App.current;
	 * // @expect(ret == true)
	 * @public
	 */
	get app() {
		return this[__.private].app;
	}

	/**
	 * Get type.
	 * @type {String}
	 * @example
	 * let object = new THING.BaseObject();
	 * let type = object.type;
	 * // @expect(type == 'BaseObject')
	 * @public
	 */
	get type() {
		return this[__.private].type;
	}

	/**
	 * Get order ID.
	 * @type {Number}
	 * @private
	 */
	get orderId() {
		return this[__.private].orderId;
	}

	/**
	 * Get/Set name.
	 * @type {String}
	 * @example
	 * let object = new THING.BaseObject();
	 * // @expect(object.name == '');
	 * object.name = 'car';
	 * // @expect(object.name == 'car');
	 * @public
	 */
	get name() {
		return this._name;
	}
	set name(value) {
		this._name = value;

		Selector.updateObjectAttribute(this, 'name', value);
	}

	/**
	 * Get/Set id.
	 * @type {String}
	 * @example
	 * let object = new THING.BaseObject();
	 * object.id = 'DEVICE_007';
	 * // @expect(object.id == 'DEVICE_007')
	 * @public
	 */
	get id() {
		return this._id;
	}
	set id(value) {
		this._id = value;

		Selector.updateObjectAttribute(this, 'id', value);
	}

	/**
	 * Get/Set uuid.
	 * @type {String}
	 * @example
	 * let object = new THING.BaseObject({uuid: 10000});
	 * // @expect(object.uuid == 10000)
	 * object.uuid = THING.Math.generateUUID();
	 * // @expect(object.id != 10000)
	 * @public
	 */
	get uuid() {
		return this._uuid;
	}
	set uuid(value) {
		this._uuid = value;

		Selector.updateObjectAttribute(this, 'uuid', value);
	}

	/**
	 * Get/Set user data.
	 * @type {Object}
	 * @example
	 * let object = new THING.BaseObject();
	 * object.userData['Notebook'] = {
	 * 	name: 'FlyingCar',
	 * 	price: 100
	 * };
	 * let name = object.userData['Notebook'].name;
	 * // @expect(name == 'FlyingCar')
	 * let price = object.userData['Notebook'].price
	 * // @expect(price == 100)
	 * @public
	 */
	get userData() {
		let userData = this._getUserData();

		if (userData.isObjectProxy) {
			return userData.dataProxy;
		}
		else {
			return userData;
		}
	}
	set userData(value) {
		let userData = this._getUserData();
		if (userData.isObjectProxy) {
			userData.copy(value);
		}
		else {
			Utils.copyObject(userData, value);
		}
	}

	// #endregion

	// #region State

	/**
	 * Get queryable state.
	 * @returns {Boolean}
	 * @private
	 */
	getQueryable() {
		return this._flags.has(Flag.Queryable);
	}

	/**
	 * Set queryable state.
	 * @param {Boolean} value The state value.
	 * @param {Boolean} recursive True indicates process it with all children.
	 * @private
	 */
	setQueryable(value, recursive) {
		// Update queryable flag
		this._flags.enable(Flag.Queryable, value);

		// Sync background selector's flag
		Selector.updateObjectAttribute(this, 'queryable', value);

		// Change all children if needed
		if (recursive) {
			let _private = this[__.private];
			if (_private.children) {
				let children = _private.children;
				for (let i = 0, l = children.length; i < l; i++) {
					children[i].setQueryable(value, true);
				}
			}
		}
	}

	/**
	 * Make object to call onRefresh() interface later.
	 * @private
	 */
	setDirty(force) {
		let flags = this._flags;
		if (flags.has(Flag.Dirty)) {
			return;
		}

		flags.enable(Flag.Dirty, true);

		if (force) {
			this.onRefresh();
		}
		else {
			this.app.objectManager.addDirtyObject(this);
		}
	}


	/**
	 * Destroy.
	 * @returns {Boolean}
	 * @example
	 * let object = new THING.BaseObject();
	 * // @expect(object.destroyed == false);
	 * object.destroy();
	 * // @expect(object.destroyed == true)
	 * @public
	 */
	destroy() {
		if (this.onBeforeDestroy() === false) {
			return false;
		}

		this.onDestroy();

		this.onAfterDestroy();

		return true;
	}

	/**
	 * Get flags.
	 * @type {Object}
	 * @private
	 */
	get flags() {
		return this._flags;
	}

	/**
	 * Get destroyed state.
	 * @type {Boolean}
	 * @private
	 */
	get destroyed() {
		return this._flags.has(Flag.Destroyed);
	}

	/**
	 * Enable/Disable queryable.
	 * @type {Boolean}
	 * @example
	 * let object = new THING.BaseObject();
	 * object.name = 'Hidden';
	 * let ret = app.query('Hidden');
	 * // @expect(ret[0].name = 'Hidden')
	 * object.queryable = false;
	 * ret = app.query('Hidden');
	 * // @expect(ret.length = 0)
	 * @public
	 */
	get queryable() {
		return this._flags.has(Flag.Queryable);
	}
	set queryable(value) {
		this.setQueryable(value, true);
	}

	// #endregion

	// #region Event

	/**
	 * Check whether has specified event listener.
	 * @property {String} type The event type.
	 * @property {String} tag The event tag.
	 * @returns {Boolean}
	 * @private
	 */
	hasEvent(type, tag) {
		_eventListeners.length = 0;

		let events = this.app.eventManager.getEventListeners(type.toLowerCase(), this, _eventListeners);
		for (let i = 0; i < events.length; i++) {
			let event = events[i];

			if (event.removed) {
				continue;
			}

			if (tag !== event.tag) {
				continue;
			}

			return true;
		}

		return false;
	}

	/**
	 * @typedef {Object} ObjectListenerInfo
	 * @property {String} type The event type.
	 * @property {String} condition The condition to select objects.
	 * @property {Function} callback The event callback.
	 * @property {String} tag The event tag.
	 * @property {Number} priority The event priority.
	 * @property {Boolean} once True indicates it's trigger only once event.
	 * @property {Boolean} paused True indicates it had paused.
	 */

	/**
	 * Get events with type.
	 * @param {String} type The event type, null indicates get all events.
	 * @returns {Array<ObjectListenerInfo>}
	 * @private
	 */
	getEvents(type) {
		let eventManager = this.app.eventManager;

		let events;
		if (type) {
			events = eventManager.getEventListeners(type.toLowerCase(), this);
		}
		else {
			events = eventManager.getAllEventListeners(this);
		}

		let aliveEvents = [];
		events.forEach(event => {
			if (event.removed) {
				return;
			}

			if (!event.enumerable) {
				return;
			}

			aliveEvents.push(event);
		});

		return aliveEvents;
	}

	/**
	 * Get event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {String} tag The event tag.
	 * @returns {ObjectListenerInfo}
	 * @private
	 */
	getEvent(type, condition, tag) {
		type = type.toLowerCase();

		let events = this.getEvents();
		for (let i = 0; i < events.length; i++) {
			let event = events[i];

			if (event.type != type) {
				continue;
			}

			if (event.condition != condition) {
				continue;
			}

			if (event.tag != tag) {
				continue;
			}

			return event;
		}

		return null;
	}

	/**
	 * @typedef {Object} ObjectEventOptions
	 * @property {Boolean} useCapture True indicates capture all same events from children.
	 */

	/**
	 * Register 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
	 * let object = new THING.BaseObject();
	 * let mark = 0;
	 * object.on('click', function(ev){
	 * 	mark = 1;
	 * }, 'MyClick');
	 * object.trigger('click');
	 * // @expect(mark == 1)
	 * let mark2 = 0;
	 * object.on('click', '.Box', function(ev){
	 * 	mark2 = 1;
	 * }, 'MyClick');
	 * // @expect(mark2 == 0)
	 * @public
	 */
	on(type, condition, callback, tag, priority, options) {
		this._addEventListener(type, condition, callback, tag, priority, options, false);
	}

	/**
	 * Register 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
	 * let object = new THING.BaseObject();
	 * let markOnce = 0;
	 * object.once('testOnce', function(ev) {
	 * 		markOnce = 1;
	 * });
	 * object.trigger('testOnce');
	 * // @expect(markOnce == 1);
	 * markOnce = 0;
	 * object.trigger('testOnce');
	 * // @expect(markOnce == 0);
	 * @public
	 */
	once(type, condition, callback, tag, priority, options) {
		this._addEventListener(type, condition, callback, tag, priority, options, true);
	}

	/**
	 * Unregister event.
	 * @param {String} type The event type.
	 * @param {String} condition? The condition to select objects.
	 * @param {String} tag The event tag.
	 * @example
	 * let object = new THING.BaseObject();
	 * let markOff = 0;
	 * object.on('testOff', function(ev) {
	 * 		markOff = 1;
	 * });
	 * object.trigger('testOff');
	 * // @expect(markOff == 1);
	 * markOff = 0;
	 * object.off('testOff');
	 * object.trigger('testOff');
	 * // @expect(markOff == 0);
	 * @public
	 */
	off(type, condition, tag) {
		// Use condition as tag
		if (Utils.isString(condition) && Utils.isNull(tag)) {
			this._removeEventListener(type, null, condition);
		}
		// Use both condition and tag
		else {
			this._removeEventListener(type, condition, tag);
		}
	}

	/**
	 * Pause event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {String} tag The event tag.
	 * @example
	 * let object = new THING.BaseObject();
	 * let markPause = 0;
	 * object.on('testPause', function(ev) {
	 * 		markPause = 1;
	 * });
	 * object.trigger('testPause');
	 * // @expect(markPause == 1);
	 * markPause = 0;
	 * object.pauseEvent('testPause');
	 * object.trigger('testPause');
	 * // @expect(markPause == 0);
	 * @public
	 */
	pauseEvent(type, condition, tag) {
		this.app.eventManager.pauseEvent(this, type, condition, tag);
	}

	/**
	 * Resume event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {String} tag The event tag.
	 * @example
	 * let object = new THING.BaseObject();
	 * let markResume = 0;
	 * object.on('testResume', function(ev) {
	 * 		markResume = 1;
	 * });
	 * object.trigger('testResume');
	 * // @expect(markResume == 1);
	 * markResume = 0;
	 * object.pauseEvent('testResume');
	 * object.trigger('testResume');
	 * // @expect(markResume == 0);
	 * object.resumeEvent('testResume');
	 * object.trigger('testResume');
	 * // @expect(markResume == 1);
	 * @public
	 */
	resumeEvent(type, condition, tag) {
		this.app.eventManager.resumeEvent(this, type, condition, tag);
	}

	/**
	 * Trigger 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.
	 * @returns {*}
	 * @example
	 * let object = new THING.BaseObject();
	 *  let markTrigger = {};
	 * 	object.on('myEvent', function(ev) {
	 * 		markTrigger = ev;
	 * 	});
	 * 	object.trigger('myEvent', { result: true });
	 *  let ret = markTrigger.result;
	 *  // @expect(ret == true);
	 * @public
	 */
	trigger(type, ev, options) {
		if (this.destroyed) {
			return null;
		}

		return this.app.eventManager.dispatchEvent(type.toLowerCase(), this, ev, options);
	}

	/**
	 * Get proxy object.
	 * @returns {THING.BaseObject}
	 * @private
	 */
	getProxyObject() {
		let _private = this[__.private];

		return _private.proxyObject;
	}

	/**
	 * Set proxy object(it will catch all events of this object).
	 * @param {THING.BaseObject} object The object.
	 * @example
	 * 	let car = app.query('#car01')[0];
	 * 	object.setProxyObject(car); // The car will catch all events of object
	 * @private
	 */
	setProxyObject(object) {
		let _private = this[__.private];

		_private.proxyObject = object;
	}

	triggerError(error, callback) {
		this.trigger(EventType.Error, error);
		Utils.error(error);
		callback && callback(error);
	}

	/**
	 * Get all events.
	 * @type {Array<ObjectListenerInfo>}
	 * @private
	 */
	get events() {
		return this.getEvents();
	}

	// #endregion

	// #region Relative

	/**
	 * When set the parent.
	 * @param {THING.BaseObject} parent The parent.
	 * @private
	 */
	onSetParent(parent) {
		let _private = this[__.private];

		// Prevent for the same parent
		if (parent == _private.parent) {
			return;
		}

		// Attach to new parent
		if (parent) {
			parent.addChild(this);
		}

		// Remove it from previous parent
		this._removeFromParent();

		// Update current parent
		_private.parent = parent;
	}

	/**
	 * Get the parent.
	 * @returns {THING.BaseObject}
	 * @private
	 */
	getParent() {
		return this[__.private].parent;
	}

	/**
	 * Get the parnet by type.
	 * @param {String} type The type string.
	 * @returns {THING.BaseObject}
	 * @private
	 */
	getParentByType(type) {
		let typeString = 'is' + type;

		for (let parent = this.parent; parent; parent = parent.parent) {
			if (parent[typeString]) {
				return parent;
			}
		}

		return null;
	}

	/**
	 * Get the parents.
	 * @param {Object} param The parameters.
	 * @param {THING.BaseObject} param.root The root to stop.
	 * @param {Boolean} param.includeSelf True indicates including self object.
	 * @returns {THING.Selector}
	 * @private
	 */
	getParents(param = _defaultParams) {
		let _private = this[__.private];

		let root = param['root'];
		let includeSelf = param['includeSelf'];

		let selector = new Selector();

		if (includeSelf) {
			selector.push(this);
		}

		let parent = _private.parent;
		while (parent) {
			if (parent == root) {
				break;
			}

			selector.push(parent);

			parent = parent.parent;
		}

		return selector;
	}

	/**
	 * Get the brothers except self.
	 * @returns {THING.Selector}
	 * @private
	 */
	getBrothers() {
		let _private = this[__.private];

		let parent = _private.parent;
		if (parent) {
			return parent.children.filter(object => {
				if (object === this) {
					return false;
				}

				return true;
			});
		}

		return new Selector();
	}

	/**
	 * Get the path to specific object.
	 * @param {THING.BaseObject} object The object to check.
	 * @returns {THING.Selector}
	 * @private
	 */
	getPathTo(object) {
		if (!object) {
			return null;
		}

		if (this.isChildOf(object)) {
			let path = this.getParents({ root: object });
			path.push(object);

			return path;
		}
		else if (object.isChildOf(this)) {
			let path = object.getParents({ root: this }).reverse();
			path.push(object);

			return path;
		}
		else {
			// Get all parents
			let targetParents = object.parents.reverse();
			let selfParents = this.parents.reverse();

			// Check whether it has parents
			if (targetParents.length && selfParents.length) {
				// Check whether it come from the same root
				if (targetParents[0] == selfParents[0]) {
					// Find the link parent
					let linkParent;
					while (targetParents.length && selfParents.length) {
						if (targetParents[0] != selfParents[0]) {
							break;
						}

						linkParent = selfParents[0];
						targetParents.splice(0, 1);
						selfParents.splice(0, 1);
					}

					if (linkParent) {
						return selfParents.reverse().concat([linkParent]).concat(targetParents).concat(object);
					}
				}
			}
		}

		return null;
	}

	/**
	 * Check whether it's child.
	 * @param {THING.BaseObject} object The object to check.
	 * @returns {Boolean}
	 * @example
	 * let parent = new THING.BaseObject();
	 * let child = new THING.BaseObject({parent: parent});
	 * let ret = child.isChildOf(parent);
	 *  // @expect(ret == true);
	 */
	isChildOf(object) {
		if (!object) {
			return false;
		}

		let parent = this.parent;
		while (parent) {
			if (parent == object) {
				return true;
			}

			parent = parent.parent;
		}

		return false;
	}

	/**
	 * Check whether it's brother.
	 * @param {THING.BaseObject} object The object to check.
	 * @returns {Boolean}
	 * @example
	 * let parent = new THING.BaseObject();
	 * let child1 = new THING.BaseObject({parent: parent});
	 * let child2 = new THING.BaseObject({parent: parent});
	 * let ret = child1.isBrotherOf(child2);
	 *  // @expect(ret == true);
	 */
	isBrotherOf(object) {
		if (!object) {
			return false;
		}

		if (this.parent != object.parent) {
			return false;
		}

		return true;
	}

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

		_private.children = _private.children || new Selector();
		_private.children.push(object);
	}

	/**
	 * Add object as child.
	 * @param {THING.BaseObject} object The object what you want to add.
	 * @param {Object} options The options.
	 * @returns {Boolean}
	 * @example
	 * let parent = new THING.BaseObject();
	 * let child = new THING.BaseObject();
	 * parent.add(child);
	 * let ret = child.isChildOf(parent);
	 *  // @expect(ret == true);
	 * @public
	 */
	add(object, options) {
		if (!object) {
			return false;
		}

		let parent = object.parent;
		if (parent) {
			// The final parent is passed in as an argument.
			object.onBeforeSetParent(this);
			parent.remove(object, options);
		}

		this.onBeforeAddChild(object);

		this.onAddChild(object, options);

		this.onAfterAddChild(object);

		return true;
	}

	/**
	 * Remove child object.
	 * @param {THING.BaseObject} object The object what you want to remove.
	 * @returns {Boolean}
	 * @example
	 * let parent = new THING.BaseObject();
	 * let child = new THING.BaseObject();
	 * parent.add(child);
	 * let ret = child.isChildOf(parent);
	 *  // @expect(ret == true);
	 * 	parent.remove(child);
	 * ret = child.isChildOf(parent);
	 * 	// @expect(ret == false);
	 * @public
	 */
	remove(object, options) {
		if (!object) {
			return false;
		}

		this.onBeforeRemoveChild(object);

		object.onSetParent(null, null, options);

		this.onAfterRemoveChild(object);

		return true;
	}

	/**
	 * Traverse self and all children.
	 * @param {Function} callback The callback function.
	 * @example
	 * let parent = new THING.BaseObject();
	 * let child1 = new THING.BaseObject({parent: parent});
	 * let child2 = new THING.BaseObject({parent: parent});
	 * let mark = 0;
	 * parent.traverse((child) => {
	 * 		mark++;
	 * });
	 * // @expect(mark == 3)
	 * @public
	 */
	traverse(callback) {
		callback(this);
		if (this.children) {
			let objects = this.children.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
	 * let parent = new THING.BaseObject();
	 * let child1 = new THING.BaseObject({parent: parent});
	 * let child2 = new THING.BaseObject({parent: parent});
	 * let mark = 0;
	 * parent.traverseBranch((child)=>{
	 * 	   mark++;
	 * });
	 * // @expect(mark == 3)
	 * mark = 0;
	 * parent.traverseBranch((child)=>{
	 * 	   mark++;
	 *     if(child.children.length > 0){
	 * 		   return false;
	 * 	   }
	 *	   return true;
	 * });
	 * // @expect(mark == 1)
	 */
	traverseBranch(callback) {
		let isBranch = Utils.parseBoolean(callback(this), true);
		if (!isBranch) {
			return false;
		}

		let _private = this[__.private];

		let children = _private.children;
		if (children) {
			for (let i = 0, l = children.length; i < l; i++) {
				isBranch = children[i].traverseBranch(callback);
				if (!isBranch) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Traverse all parents except self.
	 * @param {Function} callback The callback function.
	 * @deprecated Deprecated, call with the selector returned by the object's parent interface
	 * @private
	 */
	traverseParents(callback) {
		console.warn('Plase use Selector.tarverse instead of tarverseParents');
		for (let parent = this.parent; parent; parent = parent.parent) {
			callback(parent);
		}
	}

	/**
	 * Traverse all children except self.
	 * @param {Function} callback The callback function.
	 * @deprecated Deprecated, call with the selector returned by the object's children interface
	 * @private
	 */
	traverseChildren(callback) {
		console.warn('Plase use Selector.tarverse instead of traverseChildren');
		let _private = this[__.private];

		if (_private.children) {
			let children = _private.children;
			for (let i = 0, l = children.length; i < l; i++) {
				children[i].traverse(callback);
			}
		}
	}

	/**
	 * Find all children except self.
	 * @param {Function} callback The callback function.
	 * @returns {THING.BaseObject}
	 * @private
	 */
	findChildren(callback) {
		let _private = this[__.private];

		let children = _private.children;
		if (children) {
			for (let i = 0, l = children.length; i < l; i++) {
				let child = children[i];

				if (callback(child) === true) {
					return child;
				}

				let result = child.findChildren(callback);
				if (result) {
					return result;
				}
			}
		}

		return null;
	}

	/**
	 * Check whether has any children.
	 * @returns {Boolean}
	 * @example
	 * 	let parent = new THING.BaseObject();
	 *  let child = new THING.BaseObject({parent: parent});
	 * 	let ret = parent.hasChildren();
	 * 	// @expect(ret == true);
	 */
	hasChildren() {
		let _private = this[__.private];

		return !!_private.children;
	}

	addRelationship(value) {
		this.relationship.addRelationship(value);
	}

	removeRelationship(value) {
		this.relationship.removeRelationship(value);
	}

	/**
	 * Get/Set parent.
	 * @type {THING.BaseObject}
	 * @example
	 * 	let parent = new THING.BaseObject();
	 *  let child = new THING.BaseObject({parent: parent});
	 * 	let ret = child.parent == parent;
	 * 	// @expect(ret == true);
	 * @public
	 */
	get parent() {
		return this[__.private].parent;
	}
	set parent(value) {
		if (value) {
			value.add(this);
		}
		else if (this.parent) {
			this.parent.remove(this);
		}
	}

	/**
	 * Get/Set relationships.
	 * @type {Array<THING.Relationship>}
	 * @example
	 * let object = new THING.Object3D();
	 * let source = new THING.Object3D();
	 * let target = new THING.Object3D();
	 * let relationship = new THING.Relationship({
	 *      type: 'control',
	 *      source: source,
	 *      target: target
	 * });
	 * object.addRelationship(relationship);
	 * let ret = object.relationships[0].type == 'control';
	 * // @expect(ret == true)
	 * @public
	 */
	get relationships() {
		return this.relationship.relationships || [];
	}

	/**
	 * Get the parents.
	 * @type {THING.Selector}
	 * @example
	 * 	let box1 = new THING.Box();
	 * 	let box2 = new THING.Box({parent: box1});
	 * 	let parents = box2.parents;
	 *  // @expect(parents.length == 2);
	 * @public
	 */
	get parents() {
		return this.getParents();
	}

	/**
	 * Get the brothers.
	 * @type {THING.Selector}
	 * @example
	 * 	let box1 = new THING.Box();
	 * 	let box2 = new THING.Box({parent: box1});
	 * 	let box3 = new THING.Box({parent: box1});
	 * 	let brothers = box3.brothers;
	 *  // @expect(brothers.length == 1);
	 * @public
	 */
	get brothers() {
		return this.getBrothers();
	}

	/**
	 * Get children.
	 * @type {THING.Selector}
	 * @example
	 * let object = new THING.BaseObject();
	 * let child= new THING.BaseObject({parent: object});
	 * let children = object.children;
	 * let ret = children.length == 1;
	 * // @expect(ret == true)
	 * @public
	 */
	get children() {
		let _private = this[__.private];

		return _private.children || _dummySelector;
	}

	// #endregion

	// #region Selector

	/**
	 * Test whether fit condition or not.
	 * @param {String} condition The condition to check.
	 * @returns {Boolean}
	 * @private
	 */
	test(condition) {
		return Selector.test(condition, this);
	}

	/**
	 * @typedef {Object} ObjectQueryOptions
	 * @property {Boolean} recursive True indicates query in recursive mode.
	 * @property {Boolean} includeSelf True indicates including self.
	 */

	/**
	 * Query children by condition.
	 * @param {String} condition The condition to select objects.
	 * @param {ObjectQueryOptions} options? The options.
	 * @returns {THING.Selector}
	 * @example
	 * let object = new THING.BaseObject();
	 * let child= new THING.BaseObject({parent: object});
	 * child.userData = {power: 1000};
	 * let children = object.children.query('[userData/power>100]');
	 * let ret = children.length == 1;
	 * // @expect(ret == true)
	 * @public
	 */
	query(condition, options = {}) {
		let recursive = Utils.parseValue(options['recursive'], true);
		let includeSelf = options['includeSelf'];
		let selector = options['dynamic'] ? new DynamicSelector(this, condition, options) : new Selector();

		if (this.isRootObject && recursive) {
			selector.query(condition, this.app.objectManager.objects, selector);
		}
		else {
			if (includeSelf) {
				if (this.test(condition)) {
					selector.push(this);
				}
			}
			if (recursive) {
				if (Utils.isString(condition)) {
					// All
					var objs = [];
					if (condition == '*') {
						this.children.traverse(child => {
							if (child.queryable) {
								objs.push(child)
							}
						});
					}
					// Expression
					else if (this._isExpression(condition)) {
						let expression = Selector.buildExpression(condition);
						if (expression) {
							this.children.traverse(child => {
								if (Selector.testByExpression(expression, child) && child.queryable) {
									objs.push(child);
								}
							});
						}
					}
					// Compare name
					else {
						this.children.traverse(child => {
							if (child.name == condition && child.queryable) {
								objs.push(child);
							}
						});
					}
					selector.push(objs);
				}
				// Reg Expression
				else if (Utils.isRegExp(condition)) {
					if (this.children) {
						var objs = [];
						this.children.traverse(child => {
							if (Selector.testByRegExpName(condition, child) && child.queryable) {
								objs.push(child);
							}
						});
						selector.push(objs);
					}
				}
				// Function
				else if (Utils.isFunction(condition)) {
					if (this.children) {
						var objs = [];
						this.children.traverse(child => {
							if (Selector.testByFunction(condition, child) && child.queryable) {
								objs.push(child);
							}
						});
						selector.push(objs);
					}
				}
			}
			else {
				if (this.children) {
					selector = this.children.query(condition);
				}
			}
		}
		return selector;
	}

	/**
	 * Query children by name.
	 * @param {String} condition The condition to select objects.
	 * @param {ObjectQueryOptions} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let object = new THING.BaseObject();
	 * let child= new THING.BaseObject({parent: object, name: 'liming'});
	 * let result = object.queryByName('liming');
	 * let ret = result[0].name == 'liming';
	   * // @expect(ret == true)
	 * @public
	 */
	queryByName(condition, options = {}) {
		return this._queryByAttribute(this, options, (c) => { return c.name == condition });
	}

	/**
	 * Query children by reg exp.
	 * @param {String} condition The condition to select objects.
	 * @param {ObjectQueryOptions} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let object = new THING.BaseObject();
	 * let child1= new THING.BaseObject({parent: object, name: 'car1'});
	 * let child2= new THING.BaseObject({parent: object, name: 'car2'});
	 * let result = object.queryByRegExp(/car/);
	 * let ret = result.length == 2;
	 *  //@expect(ret == true)
	 * @public
	 */
	queryByRegExp(condition, options = {}) {
		return this.query(condition, options);
	}

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

	/**
	 * Query children by tag.
	 * @param {String} condition The condition to select objects.
	 * @param {ObjectQueryOptions} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let object = new THING.BaseObject();
	 * let child1= new THING.Object3D({parent: object, name: 'car1'});
	 * child1.tags.add('testCar');
	 * let child2= new THING.BaseObject({parent: object, name: 'car2'});
	 * let result = object.queryByTags('testCar');
	 * let ret = result.length == 1;
	 *  //@expect(ret == true)
	 * @public
	 */
	queryByTags(condition, options = {}) {
		return this._queryByCondition('tags(' + condition + ')', options, 'tags');
	}

	/**
	 * Query children by uuid.
	 * @param {String} condition The condition to select objects.
	 * @param {ObjectQueryOptions} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let object = new THING.BaseObject();
	 * let child1= new THING.BaseObject({parent: object, uuid: '1000'});
	 * let child2= new THING.BaseObject({parent: object});
	 * let result = object.queryByUUID('1000');
	 * let ret = result[0].uuid == '1000';
	 *  //@expect(ret == true)
	 * @public
	 */
	queryByUUID(condition, options = {}) {
		return this._queryByAttribute(this, options, (c) => { return c.uuid == condition });
	}

	/**
	 * Query children by id.
	 * @param {String} condition The condition to select objects.
	 * @param {ObjectQueryOptions} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let object = new THING.BaseObject();
	 * let child1= new THING.BaseObject({parent: object});
	 * child1.id = '10000';
	 * let child2= new THING.BaseObject({parent: object});
	 * let result = object.queryById('10000');
	 * let ret = result[0].id == '10000';
	 *  //@expect(ret == true)
	 * @public
	 */
	queryById(condition, options = {}) {
		return this._queryByAttribute(this, options, (c) => { return c.id == condition });
	}

	/**
	 * Query children by type.
	 * @param {String} condition The condition to select objects.
	 * @param {ObjectQueryOptions} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * let object = new THING.BaseObject();
	 * let child1= new THING.Box({parent: object, id: '10000'});
	 * let child2= new THING.BaseObject({parent: object});
	 * let result = object.queryByType('Box');
	 * let ret = result[0].id == '10000';
	 *  //@expect(ret == true)
	 * @public
	 */
	queryByType(condition, options = {}) {
		var key = "is" + condition;
		return this._queryByAttribute(this, options, (c) => { return c[key] == true });
	}

	/**
	 * Query children by userData.
	 * @param {String} condition The condition to select objects.
	 * @param {ObjectQueryOptions} options The options.
	 * @returns {THING.Selector}
	 * @example
	   * let object = new THING.BaseObject();
	 * let child1= new THING.Box({parent: object});
	 * child1.userData['power'] = 100;
	 * let child2= new THING.BaseObject({parent: object});
	 * let result = object.queryByUserData('power=100');
	 * let ret = result[0].userData.power == 100;
	 *  //@expect(ret == true)
	 * @public
	 */
	queryByUserData(condition, options = {}) {
		return this._queryByCondition(condition, options, 'userData');
	}

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

	_queryByAttribute(root, options, cb) {
		let selector = options['dynamic'] ? new DynamicSelector(this, condition) : new Selector();
		let recursive = Utils.parseValue(options['recursive'], true);
		let includeSelf = options['includeSelf'];
		// RootObject query from all objects
		if (root.isRootObject && recursive) {
			selector._queryByAttribute(this.app.objectManager.objects, cb, selector);
		}
		else {
			let objects = [];
			if (includeSelf) {
				if (cb(root) && root.queryable) {
					objects.push(root);
				}
			}
			if (recursive) {
				this.children.traverse(c => {
					if (cb(c) && c.queryable) {
						objects.push(c);
					}
				});
			}
			else {
				objects = objects.concat(this.children.objects.filter((c) => { return cb(c) && c.queryable; }));
			}
			selector.push(objects);
		}
		return selector;
	}

	_queryByCondition(condition, options = {}, mode) {
		let selector = options['dynamic'] ? new DynamicSelector(this, condition) : new Selector();
		let recursive = Utils.parseValue(options['recursive'], true);
		let includeSelf = options['includeSelf'];
		var objects = [];
		if (Utils.isString(condition)) {
			let expression = Selector.buildExpression(condition, mode);
			if (expression) {
				// RootObject query from all objects
				if (this.isRootObject && recursive) {
					selector._queryByCondition(condition, this.app.objectManager.objects, selector, mode);
				}
				else {
					if (includeSelf) {
						if (Selector.testByExpression(expression, this) && this.queryable) {
							objects.push(this);
						}
					}
					if (recursive) {
						this.children.traverse(child => {
							if (Selector.testByExpression(expression, child) && child.queryable) {
								objects.push(child);
							}
						});
					}
					else {
						this.children.forEach((child) => {
							if (Selector.testByExpression(expression, child) && child.queryable) {
								objects.push(child);
							}
						})
					}
				}
			}
		}
		selector.push(objects);
		return selector;
	}

	/**
	 * Query children by condition in async mode.
	 * @param {String} condition The condition to select objects.
	 * @param {ObjectQueryOptions} options The options.
	 * @returns {Promise<any>}
	 * @private
	 */
	queryAsync(condition, options = {}) {
		let recursive = Utils.parseValue(options['recursive'], true);
		let includeSelf = options['includeSelf'];

		return Selector.queryAsync(condition, this, recursive).then(selector => {
			if (includeSelf) {
				if (this.test(condition)) {
					selector.push(this);
				}
			}

			return selector;
		});
	}

	/**
	 * Find children by condition.
	 * @param {String} condition The condition to select objects.
	 * @param {ObjectQueryOptions} options The options.
	 * @returns {THING.BaseObject}
	 * @private
	 */
	find(condition, options = {}) {
		let recursive = Utils.parseValue(options['recursive'], true);
		let includeSelf = options['includeSelf'];

		if (includeSelf) {
			if (this.test(condition)) {
				return this;
			}
		}

		let _private = this[__.private];

		if (recursive) {
			if (Utils.isString(condition)) {
				// All
				if (condition == '*') {
					return this.children[0];
				}
				else {
					let expression = Selector.buildExpression(condition);
					if (expression) {
						return this.findChildren(child => {
							return Selector.testByExpression(expression, child);
						});
					}
				}
			}
			// Reg Expression
			else if (Utils.isRegExp(condition)) {
				if (_private.children) {
					return this.findChildren(child => {
						return Selector.testByRegExpName(condition, child);
					});
				}
			}
			// Function
			else if (Utils.isFunction(condition)) {
				if (_private.children) {
					return this.findChildren(child => {
						return Selector.testByFunction(condition, child);
					});
				}
			}
		}
		else {
			if (_private.children) {
				return _private.children.find(condition);
			}
		}

		return null;
	}

	// #endregion

	get isBaseObject() {
		return true;
	}

}

export { BaseObject }