import { ImageWrapType, ImageMappingType, ImageFilterType, InheritType } from '../const';

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

import { CubeTexture } from '../resources/CubeTexture';

import { ImageTexture } from '../resources/ImageTexture';

import { Style } from '../resources/Style';



var _registeredResolvers = [];

const cCanIgnoreKeys = {

	'relationships': false,

	'blueprints': false,

	'selections': false,

	'plugins': false,

	'files': false,

	'predownloads': false,

	'actions': false,

	'renderSettings': true,

	'viewPoint': true,

};

const __ = {

	private: Symbol('private'),

}

function _initPrivateMembers(that, param) {

	that[__.private] = {};

	const _private = that[__.private];



	_private.checkIgnore = (key) => {

		if (!cCanIgnoreKeys.hasOwnProperty(key)) {

			return false;

		}

		if (_private.ignore.hasOwnProperty(key)) {

			return _private.ignore[key];

		}

		else {

			return cCanIgnoreKeys[key];

		}

	}

	_private.checkAndConcat = (target, memberName, memberData) => {

		let ignore = _private.checkIgnore(memberName);

		if (!ignore) {

			_private.concatArrayMember(target, memberName, memberData)

		}

	}

	_private.concatArrayMember = (target, memberName, memberData) => {

		if (!target || !memberName || !memberData) {

			return;

		}



		let data = memberData;

		if (!Array.isArray(memberData)) {

			data = [memberData];

		}

		target[memberName] = target[memberName].concat(data);

	}



	_private.checkAndSet = (target, memberName, memberData) => {

		let ignore = _private.checkIgnore(memberName);

		if (!ignore) {

			_private.setArrayMember(target, memberName, memberData)

		}

	}

	_private.setArrayMember = (target, memberName, memberData) => {

		if (!target || !memberName || !memberData) {

			return;

		}



		let data = memberData;

		if (!Array.isArray(memberData)) {

			data = [memberData];

		}



		if (data.length > 0) {

			target[memberName] = data;

		}

	}



	// #region processResolvers

	_private.processResolvers = () => {

		_registeredResolvers.forEach(item => {

			let type = item.type;

			let resolver = new item.resolver({

				app: _private.app,

				toolkit: _private.toolkit,

			});

			_private.resolvers.set(type, resolver);

		});

	}

	// #region processObjects

	_private.resolveURL = (url) => {

		let baseURL = _private.getBaseURL();

		if (url.startsWith(baseURL)) {

			url = url.replace(baseURL, '');

		}



		let workPath = _private.getWorkPath();

		if (workPath && url.startsWith(workPath)) {

			url = url.replace(workPath, '');

			if (_private.workPathPrefix) {

				url = _private.workPathPrefix + url;

			}

			else {

				url = "/" + url;

			}

		}

		return url;

	}

	_private.processURI = (oriURI) => {

		let retURI = null;

		if (Array.isArray(oriURI)) {

			retURI = oriURI.map(uri => {

				uri = _private.resolveURL(uri);

				return uri;

			});

		}

		else {

			retURI = _private.resolveURL(oriURI);

		}

		return retURI;

	}

	// collect resource

	_private.getIndexByURI = (uri) => {

		let index = null;

		if (Array.isArray(uri)) {

			index = uri.map(url => {

				return _private.getIndexByURI(url);

			});

		}

		else {

			index = _private.URIs.indexOf(uri);

			if (index == -1) {

				index = _private.URIs.push(uri) - 1;

			}

		}

		return index;

	}

	_private.getURIByIndex = (index) => {

		let uri = null;

		if (Array.isArray(index)) {

			uri = index.map(i => {

				return _private.getURIByIndex(i);

			});

		}

		else {

			uri = _private.URIs[index];

		}



		return uri;

	}



	_private.collectResource = (resource) => {

		if (!resource) {

			return

		}



		const data = {}

		if (resource.isSceneRoot) {

			data['isSceneRoot'] = resource.isSceneRoot;

		}



		if (resource.isSceneNode) {

			data['isSceneNode'] = resource.isSceneNode;

		}



		if (resource.nodeName) {

			data['nodeName'] = resource.nodeName;

		}



		if (resource.inverseRotationMode) {

			data['inverseRotationMode'] = resource.inverseRotationMode;

		}



		if (resource.excludeNodeNames) {

			data['excludeNodeNames'] = resource.excludeNodeNames;

		}



		if (resource.instanceCount) {

			data['instanceCount'] = resource.instanceCount;

		}



		if (resource.instanceId) {

			data['instanceId'] = resource.instanceId;

		}



		if (resource.instanceStyle) {

			data['instanceStyle'] = Object.assign({}, resource.instanceStyle);

			if (data.instanceStyle.map && data.instanceStyle.map.url) {

				data.instanceStyle.map = Object.assign({}, resource.instanceStyle.map);

				data.instanceStyle.map.url = _private.processURI(data.instanceStyle.map.url);

			}

		}



		if (resource.url) {

			let uri = _private.processURI(resource.url);

			data['uri'] = uri;

		}



		return Object.keys(data).length ? data : null;

	};



	// collect body

	_private.collectBody = (object) => {

		// If there are no children, "node" and "bodynode" are the same

		if (object.node === object.bodyNode) {

			return

		}



		const body = object.body

		let localPosition = body.localPosition;

		let localQuaternion = body.localQuaternion;

		let localScale = body.localScale;

		let halfSize = body.getLocalBoundingBox().halfSize;

		return { localPosition, localQuaternion, localScale, halfSize };

	};

	// collect tags

	_private.collectTags = (tags) => {

		let ret = [];

		tags.forEach(tag => {

			let index = _private.tags.indexOf(tag);

			if (index == -1) {

				ret.push(_private.tags.push(tag) - 1);

			}

			else {

				ret.push(index);

			}

		})

		return ret;

	};

	// collect image

	_private.collectImage = (image) => {

		if (Utils.isNull(image)) {

			return null;

		}

		else if (Array.isArray(image)) {

			return {

				type: 'NumberArray',

				value: image

			}

		}



		let imageData = null;

		if (Utils.isNull(image.url)) {

			return null;

		}

		else if (image instanceof ImageTexture) {

			let url = _private.processURI(image.url);

			imageData = {

				type: 'ImageTexture',

				value: _private.getIndexByURI(url),

			}



			image.flipY == false || (imageData.flipY = image.flipY);

			let defaultMinFilterType = image.generateMipmaps ? ImageFilterType.LinearMipmapLinearFilter : ImageFilterType.LinearFilter

			image.minFilterType == defaultMinFilterType || (imageData.minFilterType = image.minFilterType);

			image.magFilterType == ImageFilterType.LinearFilter || (imageData.magFilterType = image.magFilterType);



			image.mappingType == ImageMappingType.UV || (imageData.mappingType = image.mappingType);

			image.wrapTypeT == ImageWrapType.Repeat || (imageData.wrapTypeT = image.wrapType);

			image.wrapTypeS == ImageWrapType.Repeat || (imageData.wrapTypeS = image.wrapType);

		}

		else if (image instanceof CubeTexture) {

			let url = _private.processURI(image.url);

			imageData = {

				type: 'CubeTexture',

				value: _private.getIndexByURI(url),

			}

			image.flipY == false || (imageData.flipY = image.flipY);

			let defaultMinFilterType = image.generateMipmaps ? ImageFilterType.LinearMipmapLinearFilter : ImageFilterType.LinearFilter

			image.minFilterType == defaultMinFilterType || (imageData.minFilterType = image.minFilterType);

			image.magFilterType == ImageFilterType.LinearFilter || (imageData.magFilterType = image.magFilterType);



			image.mappingType == ImageMappingType.CubeReflection || (imageData.mappingType = image.mappingType);

			image.wrapTypeT == ImageWrapType.ClampToEdge || (imageData.wrapTypeT = image.wrapType);

			image.wrapTypeS == ImageWrapType.ClampToEdge || (imageData.wrapTypeS = image.wrapType);

		}



		return imageData;

	}

	// collect style.

	_private.collectStyle = (style) => {

		let styleData = Object.assign({}, style);

		let images = styleData.images;

		if (images) {

			for (let key in images) {

				let image = _private.collectImage(images[key]);

				if (Utils.isNull(image)) {

					delete images[key];

				}

				else {

					styleData[key] = image;

				}

			}

			delete styleData.images;

		}



		let mark = JSON.stringify(styleData);

		let data = _private.stylesData.get(mark);

		if (!data) {

			data = {

				index: _private.stylesData.size,

				data: styleData

			}

			_private.stylesData.set(mark, data);

		}



		return data.index;

	}

	// collect children

	_private.collectChildren = (object) => {

		let children = []

		object.children.forEach(child => {

			let objectData = _private.collectObject(child);

			if (objectData) {

				children.push(objectData.index);

			}

		})

		return children;

	}

	_private.processObjectData = (data) => {

		// _customObjectProcessCallback

		if (_private._customObjectProcessCallback) {

			data = _private._customObjectProcessCallback(data);

		}



		// process resolvers

		_private.resolvers.forEach(resolver => {

			resolver.resolveExportObject(data);

		});



		// process uri

		if (data.resource) {

			if (data.resource.uri) {

				let index = _private.getIndexByURI(data.resource.uri)

				if (index >= 0) {

					data.resource.uri = index;

				}

			}



			let style = data.resource.instanceStyle;

			if (style && style.map && style.map.url) {

				style.map.url = _private.getIndexByURI(style.map.url);

			}

		}



		// process tags

		if (data.tags) {

			let tags = _private.collectTags(data.tags);

			if (tags.length) {

				data.tags = tags;

			}

		}



		// process style

		if (data.style) {

			let styleIndex = _private.collectStyle(data.style);

			if (styleIndex >= 0) {

				data.styleIndex = styleIndex;

			}

			delete data.style;

		}



		return data;

	}

	_private.isPrefabRoot = (object) => {

		if (_private.rootObjects.includes(object) && _private.extensions.isPrefab) {

			return true;

		}

		return false;

	}

	_private.needCollectChildren = (object) => {

		if (!object || !object.children || object.children.length == 0) {

			return false;

		}



		// if (object.type == 'Map') {

		// 	return false;

		// }



		if (_private.isPrefabRoot(object)) {

			return true;

		}



		return (!object.isPrefabObject);

	}

	_private.needCollect = (object) => {

		if (!object) {

			return false;

		}



		if (object.isPrivateObject) {

			return false;

		}



		// do not collect subobject which maybe the child of prefab or gltf scene

		if (object.isSubObject && !_private.extensions.isPrefab) {

			return false;

		}



		if (_private.onFilterObject) {

			return _private.onFilterObject(object);

		}



		return true;

	}

	_private.getChangedData = (curData, defaultData) => {

		let equals = (v1, v2) => {

			if (v1 == v2) {

				return true;

			}

			if (Utils.isArray(v1) && Utils.isArray(v2)) {

				return JSON.stringify(v1) === JSON.stringify(v2);

			}

			if (Utils.isObject(v1) && Utils.isObject(v2)) {

				return Utils.equalsObject(v1, v2);

			}



			return false;

		}

		let data = {};

		for (let key in curData) {

			if (!equals(curData[key], defaultData[key])) {

				data[key] = curData[key];

			}

		}

		return data;

	}

	_private.getStyleChangedData = (style) => {

		let changedStyle = {};

		if (!style || !style.data) {

			return changedStyle;

		}



		const styleData = style.getValues()



		let data = _private.getChangedData(styleData.data, Style.cDefaultValues);

		if (!Utils.isEmptyObject(data)) {

			changedStyle = Object.assign(changedStyle, data);

		}

		let edge = _private.getChangedData(styleData.edge, Style.cEdgeDefaultValues);

		if (!Utils.isEmptyObject(edge)) {

			changedStyle.edge = edge;

		}

		let effect = _private.getChangedData(styleData.effect, Style.cEffectDefaultValues);

		if (!Utils.isEmptyObject(effect)) {

			changedStyle.effect = effect;

		}

		let images = _private.getChangedData(styleData.images, Style.cImagesDefaultValues);

		if (!Utils.isEmptyObject(images)) {

			changedStyle.images = images;

		}

		if (styleData.attributes && !Utils.isEmptyObject(styleData.attributes)) {

			changedStyle.attributes = styleData.attributes;

		}



		// cUVMatrixDefaultValues is default value for every prop of uvData

		if (styleData.uvData) {

			let uvData = {};

			for (let key in styleData.uvData) {

				let uvDataItem = _private.getChangedData(styleData.uvData[key], Style.cUVMatrixDefaultValues);

				if (!Utils.isEmptyObject(uvDataItem)) {

					uvData[key] = uvDataItem;

				}

			}

			if (!Utils.isEmptyObject(uvData)) {

				changedStyle.uvData = uvData;

			}

		}



		return changedStyle;

	}

	_private.collectProperty = (data, propKey, object, callback) => {

		if (_private.onHookProperty) {

			let succ = _private.onHookProperty(data, propKey, object);

			if (succ) {

				return;

			}

		}

		if (callback) {

			callback();

		}

	}

	_private.collectObject = (object) => {

		if (!_private.needCollect(object)) {

			return null;

		}



		let objectData = _private.objectsData.get(object.uuid);

		if (objectData) {

			return objectData;

		}



		let data = {};

		_private.objects.push(object);



		_private.collectProperty(data, 'id', object, () => {

			Utils.setAttributeIfExist(data, 'id', object);

		})

		_private.collectProperty(data, 'uuid', object, () => {

			Utils.setAttributeIfExist(data, 'uuid', object);

		})

		_private.collectProperty(data, 'type', object, () => {

			Utils.setAttributeIfExist(data, 'type', object);

		})

		_private.collectProperty(data, 'dtType', object, () => {

			Utils.setAttributeIfExist(data, 'dtType', object);

		})

		_private.collectProperty(data, 'visible', object, () => {

			Utils.setAttributeIfExist(data, 'visible', object, (v) => { return !v; });

		})

		_private.collectProperty(data, 'active', object, () => {

			Utils.setAttributeIfExist(data, 'active', object, (v) => { return !v; });

		})

		_private.collectProperty(data, 'queryable', object, () => {

			Utils.setAttributeIfExist(data, 'queryable', object, (v) => { return !v; });

		})

		_private.collectProperty(data, 'destroyable', object, () => {

			Utils.setAttributeIfExist(data, 'destroyable', object, (v) => { return !v; });

		})

		_private.collectProperty(data, 'renderOrder', object, () => {

			Utils.setAttributeIfExist(data, 'renderOrder', object, (v) => { return v != 0; });

		})

		_private.collectProperty(data, 'castShadow', object, () => {

			Utils.setAttributeIfExist(data, 'castShadow', object, (v) => { return !v; });

		})

		_private.collectProperty(data, 'receiveShadow', object, () => {

			Utils.setAttributeIfExist(data, 'receiveShadow', object, (v) => { return !v; });

		})

		_private.collectProperty(data, 'alwaysOnTop', object, () => {

			Utils.setAttributeIfExist(data, 'alwaysOnTop', object, (v) => { return !v; });

		})

		_private.collectProperty(data, 'pickable', object, () => {

			Utils.setAttributeIfExist(data, 'pickable', object, (v) => { return v; });

		})

		_private.collectProperty(data, 'pivot', object, () => {

			Utils.setAttributeIfExist(data, 'pivot', object);

		})

		_private.collectProperty(data, 'name', object, () => {

			Utils.setAttributeIfExist(data, 'name', object, (v) => { return v != ''; });

		})

		_private.collectProperty(data, 'styleTag', object, () => {

			Utils.setAttributeIfExist(data, 'styleTag', object, (v) => { return v != ''; });

		})

		_private.collectProperty(data, 'position', object, () => {

			Utils.setAttributeIfExist(data, 'position', object, (v) => { return v[0] != 0 || v[1] != 0 || v[2] != 0; }, 'localPosition');

		})

		_private.collectProperty(data, 'scale', object, () => {

			Utils.setAttributeIfExist(data, 'scale', object, (v) => { return v[0] != 1 || v[1] != 1 || v[2] != 1; }, 'localScale');

		})

		_private.collectProperty(data, 'rotation', object, () => {

			Utils.setAttributeIfExist(data, 'rotation', object, (v) => { return v[0] != 0 || v[1] != 0 || v[2] != 0 || v[3] != 1; }, 'localQuaternion');

		})

		_private.collectProperty(data, 'userData', object, () => {

			Utils.setAttributeIfExist(data, 'userData', object, (v) => { return !!Object.keys(v).length; });

		})

		_private.collectProperty(data, 'internalData', object, () => {

			Utils.setAttributeIfExist(data, 'internalData', object, (v) => { return !!Object.keys(v).length; });

		})

		_private.collectProperty(data, 'layerMask', object, () => {

			Utils.setAttributeIfExist(data, 'layerMask', object, (v) => { return v != 1; });

		})

		_private.collectProperty(data, 'tags', object, () => {

			if (object.tags && object.tags.size) {

				data.tags = object.tags;

			}

		})

		_private.collectProperty(data, 'extras', object, () => {

			if (!Utils.isFunction(object.onExportExternalData)) {

				return;

			}

			let externalData = object.onExportExternalData();

			if (externalData) {

				data.extras = externalData;

			}

		})

		_private.collectProperty(data, 'inherit', object, () => {

			if (!object.inherit) {

				return;

			}

			let inherit = {};

			object.inherit._keys.forEach(key => {

				let value = object.inherit[key];

				if (value != InheritType.Normal) {

					inherit[key] = value;

				}

			})

			if (!Utils.isEmptyObject(inherit)) {

				data.inherit = inherit;

			}

		})



		if (object.resource) {

			let resData = _private.collectResource(object.resource);

			if (resData) {

				data.resource = resData;

			}

		}



		// instanceGroupName属性非必须,可以理解为配置信息,因此存在extras里

		if (object.instanceGroupName) {

			data.extras = data.extras || {}

			data.extras.instanceGroupName = object.instanceGroupName;

		}



		if (object.body) {

			let body = _private.collectBody(object);

			if (body) {

				data.body = {};

				Utils.setAttributeIfExist(data.body, 'position', body, (v) => { return v[0] != 0 || v[1] != 0 || v[2] != 0; }, 'localPosition');

				Utils.setAttributeIfExist(data.body, 'scale', body, (v) => { return v[0] != 1 || v[1] != 1 || v[2] != 1; }, 'localScale');

				Utils.setAttributeIfExist(data.body, 'rotation', body, (v) => { return v[0] != 0 || v[1] != 0 || v[2] != 0 || v[3] != 1; }, 'localQuaternion');

				Utils.setAttributeIfExist(data.body, 'halfSize', body);

			}



			let style = _private.getStyleChangedData(object.body.style)

			if (!Utils.isEmptyObject(style)) {

				data.style = style;

			}

		}



		let options = Object.assign(_private.toolkit, _private.options);

		let components = object.onExportComponents(options);

		if (object.isPrefabObject) {

			components = components.filter(item => {

				let componentClass = Utils.getRegisteredClass(item.type);

				return !Utils.isNull(componentClass);

			});

		}

		if (components.length) {

			data.components = components;

		}



		// process data

		objectData = {

			data: _private.processObjectData(data),

			index: _private.objectsData.size

		}

		_private.objectsData.set(data.uuid, objectData);



		// collect children

		if (_private.needCollectChildren(object)) {

			data.children = _private.collectChildren(object);

		}



		return objectData;

	}

	_private.processObjects = () => {

		_private.rootObjects.forEach(object => {

			_private.collectObject(object);

		})



		let app = Utils.getCurrentApp();

		let roots = Utils.uniqueObjects(_private.rootObjects);

		roots.forEach(root => {

			let objectData = _private.objectsData.get(root.uuid);

			let data = objectData ? objectData.data : null;

			if (data) {

				// prefab's parent must be null

				if (_private.extensions.isPrefab) {

					data.parent = null;

				}

				else if (root.parent) {

					// if the object's parent is app.root the save as null

					if (app && app.root && app.root.uuid == root.parent.uuid) {

						data.parent = null;

					}

					else {

						data.parent = { uuid: root.parent.uuid };

					}



					// set indexOfParent

					let object = app.queryByUUID(data.uuid)[0];

					if (object) {

						data.indexOfParent = object.indexOfParent;

					}

				}

			}

		})

	}

	// #endregion



	// #region processRelationships

	_private.arrayFromMap = (data, map, callback) => {

		let ret = [];

		if (!data) {

			return ret;

		}

		else if (Utils.isArray(data)) {

			data.forEach(item => {

				let index = callback(item.uuid, map);

				if (index >= 0) {

					ret.push(index);

				}

			});

		}

		else {

			let index = callback(data.uuid, map);

			if (index >= 0) {

				ret.push(index);

			}

		}

		return ret;

	}

	_private.collectRelationship = (relationship) => {

		let data = {

			uuid: relationship.uuid,

			name: relationship.name,

			type: relationship.type,

			queryDirection: relationship.queryDirection,

			tags: relationship.tags

		};



		let callback = (uuid, map) => {

			let object = map.get(uuid);

			if (object) {

				return object.index;

			}

			return { uuid: uuid };

		}

		let source = _private.arrayFromMap(relationship.source, _private.objectsData, callback);

		if (source.length > 0) {

			data.source = source;

		}

		let target = _private.arrayFromMap(relationship.target, _private.objectsData, callback);

		if (target.length > 0) {

			data.target = target;

		}

		_private.relationshipsData.push(data);

	}

	_private.processRelationships = () => {

		if (!_private.checkIgnore('relationships')) {

			_private.relationships.forEach(relationship => {

				_private.collectRelationship(relationship);

			})

		}

	}

	// #endregion



	// #region processSelections

	_private.processSelections = () => {

		if (_private.checkIgnore('selections')) {

			return;

		}

		_private.selections.forEach(selection => {

			let objects = selection.objects;

			if (objects) {

				selection.objects = [];

				if (Array.isArray(objects)) {

					objects.forEach(object => {

						// if we can not find the object, we ignore it

						let data = _private.objectsData.get(object.uuid);

						if (data) {

							selection.objects.push(data.index);

						}

					})

				}

				else {

					let data = _private.objectsData.get(objects.uuid);

					if (data) {

						selection.objects.push(data.index);

					}

				}

			}



			let style = selection.style?.modifier;

			if (style) {

				let styleIndex = _private.collectStyle(style);

				if (styleIndex >= 0) {

					selection.style = styleIndex;

				}

			}

		});

	}

	// #endregion



	// #region processRenderSettings

	_private.processBackground = () => {

		let background = _private.collectImage(_private.renderSettings.background);

		if (Utils.isNull(background)) {

			_private.renderSettings.background = null;

		}

		else {

			_private.renderSettings.background = background;

		}

	}

	_private.processEnvMap = () => {

		let environment = _private.renderSettings.environment;

		if (Utils.isNull(environment)) {

			delete _private.renderSettings.environment;

			return;

		}



		// 特殊处理: 如果envMap为null,仍需设置,目的为清空环境图

		if (environment.envMap != null) {

			let envMap = _private.collectImage(environment.envMap);

			if (Utils.isNull(envMap)) {

				delete environment.envMap;

			}

			else {

				environment.envMap = envMap;

			}

		}

	}

	_private.processRenderSettings = () => {

		if (!_private.checkIgnore('renderSettings')) {

			let app = Utils.getCurrentApp();

			_private.renderSettings = app ? Object.assign({}, app.renderSettings) : {};

			_private.processBackground();

			_private.processEnvMap();

		}

	}

	// #endregion



	_private.process = () => {

		_private.processResolvers();

		_private.processObjects();

		_private.processRelationships();

		_private.processSelections();

		_private.processRenderSettings();

	}



	_private.exportStyles = () => {

		let stylesData = Array.from(_private.stylesData.values());

		stylesData.sort(function (a, b) { return a.index - b.index });

		let styles = stylesData.map(style => { return style.data });

		return styles;

	}

	_private.exportObjects = () => {

		let objectsData = Array.from(_private.objectsData.values());

		objectsData.sort(function (a, b) { return a.index - b.index });

		let objects = objectsData.map(object => {

			let data = Object.assign({}, object.data);

			if ('styleIndex' in data) {

				data.style = data.styleIndex;

				delete data.styleIndex;

			}

			return data;

		});

		return objects;

	}

	_private.exportResolvers = () => {

		let resolvers = [];

		for (let [key, value] of _private.resolvers) {

			let resolver = {};

			resolver.type = key;

			resolver.data = value.onExport();

			if (resolver.data) {

				resolvers.push(resolver);

			}

		}

		return resolvers;

	}

	_private.exportInstanceObjects = () => {

		let instanceObjects = []

		for (let index = 0; index < _private.objects.length; index++) {

			let object = _private.objects[index];

			// check valid

			if (!object.scale || object.scale[0] <= 0 || object.scale[1] <= 0 || object.scale[2] <= 0) {

				continue;

			}



			if (object.isInstancedDrawing) {

				instanceObjects.push(index);

			}

		}



		return instanceObjects;

	}

	_private.exportComponentsOfRoot = () => {

		let data = {};

		let app = Utils.getCurrentApp();

		if (!app) {

			return data;

		}

		for (const [key, value] of app.components) {

			let param = value.onExport ? value.onExport() : null;

			if (param) {

				data[key] = {

					name: key,

					type: value.constructor.className,

					param

				}

			}

		}

		return data;

	}

	_private.exportExtensions = () => {

		let extensions = Object.assign({}, _private.extensions);

		let instances = _private.exportInstanceObjects();

		if (instances.length > 0) {

			extensions.instances = instances;

		}

		let componentsOfRoot = _private.exportComponentsOfRoot()

		if (!Utils.isEmptyObject(componentsOfRoot)) {

			extensions.componentsOfRoot = componentsOfRoot;

		}

		return extensions;

	}

	_private.exportViewpoint = () => {

		let viewpoint = {};

		if (!_private.checkIgnore('viewPoint')) {

			viewpoint.position = _private.viewpoint.position

			viewpoint.target = _private.viewpoint.target

			let app = Utils.getCurrentApp();

			if (app && app.camera) {

				viewpoint.position = viewpoint.position || app.camera.position

				viewpoint.target = viewpoint.target || app.camera.target

			}

		}

		return viewpoint;

	}

	_private.export = () => {

		let data = {};

		data.baseURL = _private.getBaseURL();

		data.$schema = _private.schemaURL;

		data.version = "2.0.0";

		data.description = "ThingJS Scene Format";

		data.author = "uino";



		let styles = _private.exportStyles();

		if (!Utils.isEmptyObject(styles)) {

			data.styles = styles;

		}

		let objects = _private.exportObjects();

		if (!Utils.isEmptyObject(objects)) {

			data.objects = objects;

		}

		let resolvers = _private.exportResolvers();

		if (!Utils.isEmptyObject(resolvers)) {

			data.resolvers = resolvers;

		}



		let extensions = _private.exportExtensions();

		if (!Utils.isEmptyObject(extensions)) {

			data.extensions = extensions;

		}



		let viewpoint = _private.exportViewpoint();

		if (!Utils.isEmptyObject(viewpoint)) {

			data.viewpoint = viewpoint;

		}



		let renderSettings = Object.assign({}, _private.renderSettings);

		if (!Utils.isEmptyObject(renderSettings) && !_private.checkIgnore('renderSettings')) {

			data.renderSettings = renderSettings;

		}



		_private.checkAndSet(data, 'tags', _private.tags);

		_private.checkAndSet(data, 'URIs', _private.URIs);

		_private.checkAndSet(data, 'blueprints', _private.blueprints);

		_private.checkAndSet(data, 'selections', _private.selections);

		_private.checkAndSet(data, 'files', _private.files);

		_private.checkAndSet(data, 'scripts', _private.scripts);

		_private.checkAndSet(data, 'preDownloads', _private.preDownloads);

		_private.checkAndSet(data, 'plugins', _private.plugins);

		_private.checkAndSet(data, 'relationships', _private.relationshipsData);

		_private.checkAndSet(data, 'actions', _private.actions);

		return data;

	}



	_private.getWorkPath = () => {

		let app = Utils.getCurrentApp();



		let workPath = '';

		if (_private.workPath) {

			workPath = _private.workPath;

		}

		else if (_private.options.workPath) {

			workPath = _private.options.workPath

		}

		else if (app && app.workPath) {

			workPath = app.workPath;

		}

		else {

			workPath = Utils.workPath;

		}



		workPath = workPath.replaceAll('\\', '/');

		if (!workPath.endsWith('/')) {

			workPath = workPath + '/';

		}

		return workPath;

	}



	_private.getBaseURL = () => {

		let baseURL = _private.baseURL;

		baseURL = baseURL.replaceAll('\\', '/');

		if (!baseURL.endsWith('/')) {

			baseURL = baseURL + '/';

		}

		return baseURL;

	}



	_private.reset = (options = {}) => {

		_private.baseURL = options.baseURL || _private.baseURL;

		_private.schemaURL = options.schemaURL || _private.schemaURL;

		_private.onFilterObject = Utils.isFunction(options.onFilterObject) ? param.onFilterObject : _private.onFilterObject;

		_private.onHookProperty = Utils.isFunction(param.onHookProperty) ? param.onHookProperty : _private.onHookProperty;

		_private.tags.length = 0;

		_private.URIs.length = 0;

		_private.objects.length = 0;

		_private.rootObjects.length = 0;

		_private.selections.length = 0;

		_private.blueprints.length = 0;

		_private.files.length = 0;

		_private.scripts.length = 0;

		_private.preDownloads.length = 0;

		_private.plugins.length = 0;

		_private.relationships.length = 0;

		_private.actions.length = 0;

		_private.objectsData.clear();

		_private.stylesData.clear();

		_private.relationshipsData.length = 0;

		_private.renderSettings = {};

		_private.extensions = {};

		_private.viewpoint = {};

		_private.resolvers.clear();

		_private.ignore = options.ignore || _private.ignore;

		_private.options = Object.assign({}, options);

		_private._customObjectProcessCallback = null;

	}



	_private.init = (param) => {

		_private.baseURL = param.baseURL || 'https://model.3dmomoda.com/';

		_private.schemaURL = param.schemaURL || "https://raw.githubusercontent.com/UINOSOFT/thingjs/master/docs/tsf/schema/tsf.schema.json";

		_private.onFilterObject = Utils.isFunction(param.onFilterObject) ? param.onFilterObject : null;

		_private.onHookProperty = Utils.isFunction(param.onHookProperty) ? param.onHookProperty : null;

		_private.tags = [];

		_private.URIs = [];

		_private.objects = [];

		_private.rootObjects = [];

		_private.selections = [];

		_private.blueprints = [];

		_private.files = [];

		_private.scripts = [];

		_private.preDownloads = [];

		_private.plugins = [];

		_private.relationships = [];

		_private.actions = [];

		_private.objectsData = new Map();

		_private.stylesData = new Map();

		_private.relationshipsData = [];

		_private.renderSettings = {};

		_private.extensions = {};

		_private.viewpoint = {};

		_private.resolvers = new Map();

		_private.options = Object.assign({}, param);

		_private.ignore = _private.options.ignore || {};

		_private.toolkit = {

			getURIByIndex: _private.getURIByIndex,

			getIndexByURI: _private.getIndexByURI,

			resolveURL: _private.resolveURL

		}

		_private._customObjectProcessCallback = null;

	}



	_private.init(param);

}

/**
 * SceneExporter - 场景导出器
 * 
 * 用于导出场景数据,支持多种格式的场景导出。
 * 
 * @class SceneExporter
 * @memberof THING
 * @public
 * 
 **/
class SceneExporter {



	constructor(param = {}) {

		_initPrivateMembers(this, param);

	}



	/**

	 * reset.

	 * @param {object} options The options.

	 * @private

	 */

	reset(options = {}) {

		let _private = this[__.private];

		_private.reset(options);

	}



	/**

	 * add the objects to be exporter.

	 * @param {Array|BaseObject} objects The objects which will be export.

	 */

	addObjects(objects) {

		let _private = this[__.private];

		_private.checkAndConcat(_private, 'rootObjects', objects);

	}



	/**

	 * add the selections to the exporter

	 * @param {Array|object} selections The selections which will be export.

	 */

	addSelections(selections) {

		let _private = this[__.private];

		_private.checkAndConcat(_private, 'selections', selections);

	}



	/**

	 * add the blueprints to the exporter

	 * @param {Array|object} blueprints The blueprints which will be export.

	 */

	addBlueprints(blueprints) {

		let _private = this[__.private];

		_private.checkAndConcat(_private, 'blueprints', blueprints);

	}



	/**

	 * add files to the exporter

	 * @param {Array|string} files The files which will be export.

	 */

	addFiles(files) {

		let _private = this[__.private];

		_private.checkAndConcat(_private, 'files', files);

	}



	/**

	 * add the scripts to the exporter

	 * @param {Array|string} scripts The scripts which will be export.

	 */

	addScripts(scripts) {

		let _private = this[__.private];

		_private.checkAndConcat(_private, 'scripts', scripts);

	}



	/**

	 * add the preDownloads to the exporter

	 * @param {Array|string} preDownloads The preDownloads which will be export.

	 */

	addPreDownloads(preDownloads) {

		let _private = this[__.private];

		_private.checkAndConcat(_private, 'preDownloads', preDownloads);

	}



	/**

	 * add the plugins to the exporter

	 * @param {Array|object} plugins The plugins which will be export.

	 */

	addPlugins(plugins) {

		let _private = this[__.private];

		_private.checkAndConcat(_private, 'plugins', plugins);

	}



	/**

	 * add the relationships to the exporter

	 * @param {Array|object} relationships The relationships which will be export.

	 */

	addRelationships(relationships) {

		let _private = this[__.private];

		_private.checkAndConcat(_private, 'relationships', relationships);

	}



	/**

	 * add extensions to the exporter

	 * @param {object} extensions The extension which will be export.

	 */

	addExtensions(extensions) {

		let _private = this[__.private];

		for (let key in extensions) {

			_private.extensions[key] = extensions[key];

		}

	}



	/**

	 * add actions to the exporter

	 * @param {object} action The action data.

	 */

	addAction(actions) {

		let _private = this[__.private];

		_private.checkAndConcat(_private, 'actions', actions);

	}



	/**

	 * add viewpoint to the exporter

	 * @param {object} viewpoint The viewpoint of the scene which will be export.

	 */

	addViewpoint(viewpoint = {}) {

		let _private = this[__.private];

		if (viewpoint.position) {

			_private.viewpoint.position = viewpoint.position;

		}

		if (viewpoint.target) {

			_private.viewpoint.target = viewpoint.target;

		}

	}



	/**

	 * add work path to the exporter

	 * @param {string} workPath The Work Path that the uri should be cutted

	 */

	setWorkPath(workPath, prefix = null) {

		let _private = this[__.private];

		if (!workPath) {

			return;

		}



		_private.workPath = workPath;

		_private.workPathPrefix = prefix;

	}



	/**

	 * ouput scene file.

	 * @returns {object} scene data.

	 * @private

	 */

	export() {

		let _private = this[__.private];

		_private.process();

		return _private.export();

	}



	set CustomObjectProcessCallback(callback) {

		let _private = this[__.private];

		_private._customObjectProcessCallback = callback;

	}

	get CustomObjectProcessCallback() {

		let _private = this[__.private];

		return _private._customObjectProcessCallback;

	}



	// 注册解析器;

	// type:解析器类型名称,必须对应解析器类名

	// resolver:解析器类型

	static registerResolver(type, resolver) {

		let item = {

			type: type,

			resolver: resolver

		}

		if (!_registeredResolvers.includes(item)) {

			_registeredResolvers.push(item);

		}

	}



}



export { SceneExporter }