"use strict";

var _ = require("lodash"),
	Condicio = require("condicio"),
	Matrix = require("gl-matrix"),
	CvtForJson = require("../CvtForJson.js"),
	Node = require("./Node.js"),
	Interaction = require("./Interaction.js"),
	Viewpoint = require("./Viewpoint.js"),
	Utility = require("../Utility.js"),
	ImageDisplay = require("../ImageDisplay.js"),
	StreamDisplay = require("../StreamDisplay.js"),
	ParamValidation = require("../ParamValidation.js"),
	Roamer = require("./Roamer.js"),
	jQuery = require("jquery"),
	Filter = require("./Filter.js"),
	Error = require("../Error.js"),
	COLOR = require("color"),
	Effect = require("./Effect.js"),
	LightEffect = require("./LightEffect.js"),
	NodeFactory = require("./NodeFactory.js"),
	EffectFactory = require("./EffectFactory.js"),
	InteractionFactory = require("./InteractionFactory.js"),
	RoamerFactory = require("./RoamerFactory.js"),
	cvtToNodeArray = require("./CvtToNodeArray"),
	Command = require("./Command.js");

var InvalidArgumentError = Error.InvalidArgumentError;

/**
 * @class View3D.View3D
 * 三维渲染场景
 *
 * 创建节点工厂、创建效果工厂、创建交互工厂
 *
 * 提供HTML与三维显示窗口的通信接口
 *
 * 不应该直接创建,应该通过{@link Factory}提供的createView3D得到
 */
function View3D(session, elem, result, mode, enableSSL, proxyUri, callback) {
	var _this = this;

	this.session = session;
	this.objectID = result.viewID;
	this.rootNode = null;

	/**
	* @property {NodeFactory}   		NodeFactory 		节点创建工厂
	*/
	this.NodeFactory = new NodeFactory(this.session, "NodeFactory");
	/**
	* @property {EffectFactory} 		EffectFactory		效果创建工厂
	*/
	this.EffectFactory = new EffectFactory(this.session, "EffectFactory");
	/**
	* @property {InteractionFactory}	InteractionFactory	交互创建工厂
	*/
	this.InteractionFactory = new InteractionFactory(this.session, "InteractionFactory");
	/**
	* @property {RoamerFactory}	RoamerFactory	漫游创建工厂
	*/
	this.RoamerFactory = new RoamerFactory(this.session, "RoamerFactory");

	this.interaction = new Interaction("default interaction");
	this.roamer = null;

	this.lightEffects = {};
	if (!Condicio.isUndefined(result.lightEffectIDs.skyLightEffect)) {
		this.lightEffects.skyLightEffect = new LightEffect(this.session, result.lightEffectIDs.skyLightEffect);
		this.addEffect(this.lightEffects.skyLightEffect);
	}
	if (!Condicio.isUndefined(result.lightEffectIDs.cameraLightEffect)) {
		this.lightEffects.cameraLightEffect = new LightEffect(this.session, result.lightEffectIDs.cameraLightEffect);
		this.addEffect(this.lightEffects.cameraLightEffect);
	}

	switch (mode) {
		case 0:
			this.display = new ImageDisplay(session, this.objectID, elem, enableSSL, proxyUri);
			callback(null, this);
			break;
		case 1:
			var callbackWrapper = function (error) {
				if (!error)
					callback(null, _this);
				else {
					_this.release();
					callback(error, null);
				}
			};
			this.display = new StreamDisplay(session, this.objectID, elem, result.turnServer, callbackWrapper);
			break;
	}

	this.performanceListenerID = null;
};

View3D.prototype = _.create(View3D.prototype, {
	constructor: View3D
});

View3D.prototype.release = function () {
	if (this.performanceListenerID) {
		this.session.unregisterCallback(this.performanceListenerID);
	}

	this.display.release();
};


View3D.prototype.getPresetLightEffects = function () {
	return this.lightEffects;
};

/**
 * 切换场景根节点
 *
 * @param  {Node} node 新场景根节点,可以为空
 *
 * @return {Node}
 */
View3D.prototype.swapRootNode = function (node) {
	if (Condicio.isNull(node))
		this.session.request(this.objectID, "swapRootNode", { nodeID: "null" });
	else {
		Condicio.checkIsType(node, Node, "The type of rootNode must be 'Node'!");
		this.session.request(this.objectID, "swapRootNode", { nodeID: node.objectID });
	}

	var oldRoot = this.rootNode;
	this.rootNode = node;
	return oldRoot;
};

/**
 * 增加节点
 *
 * @param {Node} node 		待增加的节点
 * @param {Node} parentNode 父节点
 */
View3D.prototype.addNode = function (node, parentNode) {
	Condicio.checkIsType(node, Node, "The type of childNodes must be 'Node'!");
	Condicio.checkIsType(parentNode, Node, "The type of parentNode must be 'Node'!");

	var params = {
		nodeID: node.objectID,
		parentNodeID: parentNode.objectID
	};
	this.session.request(this.objectID, "addNode", params);
};

/**
 * 删除节点
 * 
 * 只能对没有effect的节点生效
 *
 * @param {Node} node 		待删除的节点
 * @param {Node} parentNode 父节点
 */
View3D.prototype.removeNode = function (node, parentNode) {
	Condicio.checkIsType(node, Node, "The type of childNodes must be 'Node'!");
	Condicio.checkIsType(parentNode, Node, "The type of parentNode must be 'Node'!");

	var params = {
		nodeID: node.objectID,
		parentNodeID: parentNode.objectID
	};
	this.session.request(this.objectID, "removeNode", params);
};

/**
 * 增加效果
 * 
 * 注意:只能对场景树中的节点调用此接口
 * 注意:要移除添加了Effect的节点前,应该先调用removeEffect接口,再调用removeNode接口
 * 
 *
 * @param {View3D.Effect} effect 	效果
 */
View3D.prototype.addEffect = function (effect) {
	Condicio.checkNotNull(effect, "Effect must`t be NULL!");
	Condicio.checkNotUndefined(effect, "Effect is undefined!");
	Condicio.checkIsType(effect, Effect, "The type of effect must be 3DEffect");

	this.session.request(this.objectID, "addEffect", { effectID: effect.objectID });

	effect.on(this.session);
};

/**
 * 删除效果
 *
 * @param {View3D.Effect} effect 	效果
 */
View3D.prototype.removeEffect = function (effect) {
	Condicio.checkNotNull(effect, "Effect must`t be NULL!");
	Condicio.checkNotUndefined(effect, "Effect is undefined!");
	Condicio.checkIsType(effect, Effect, "The type of effect must be 3DEffect");

	this.session.request(this.objectID, "removeEffect", { effectID: effect.objectID });

	effect.off(this.session);
};

/**
 * 交换交互操作
 *
 * @param {Interaction}  interaction 新的交互
 *
 * @return {Interaction} 前一个交互结果
 */
View3D.prototype.swapInteraction = function (interaction) {
	Condicio.checkNotNull(interaction, "Interaction must`t be NULL!");
	Condicio.checkNotUndefined(interaction, "Interaction is undefined!");

	// 开启新交互,停止原交互
	var preInteraction = this.interaction;
	this.interaction = interaction;

	if (!Condicio.isNull(preInteraction))
		preInteraction.off(this.session);
	interaction.on(this.session);

	this.session.request(this.objectID, "swapInteraction", { interactionID: interaction.objectID });

	return preInteraction;
};


/**
 * 设置漫游器
 *
 * @param {Roamer}  		roamer 				要设置的漫游器
 *
 * @return {Roamer}  		返回之前的漫游器
 *
 */
View3D.prototype.swapRoamer = function (roamer) {
	Condicio.checkIsType(roamer, Roamer, "The type of roamer must be 'Roamer'");

	var params = {
		roamerID: roamer.objectID
	};
	this.session.request(this.objectID, "swapRoamer", params);

	var preRoamer = this.roamer;
	this.roamer = roamer;

	return preRoamer;
};

/**
 * 查询指定名称的子孙节点
 * 
 * @param {Node} 		node 			父节点(该节点必须是DrawingRootNode)
 * @param {String[]} 	names 			待查找节点的名字,要求名字不能重复,重复的名字在外部事先过滤
 * @param {Function} 	callback 		返回查询结果
 * @param {Error} 		callback.error 	错误
 * @param {Node[]} 		callback.nodes 	查找到的节点集合,如果某个名字对应的节点没有找到,对应位置返回null
 */
View3D.prototype.findDescendantNodes = function (node, names, callback) {
	Condicio.checkIsType(node, Node, "The type of parentNode must be 'Node'!");
	ParamValidation.checkIsTypeArray(names, String, "The type of names must be 'String Array' and valid");
	Condicio.checkArgument(names.length != 0, "The names-Array must`t be NULL!");
	Condicio.checkIsFunction(callback, "The type of callback must be 'Function'!");

	var _this = this;
	var callbackWrapper = function (error, result) {
		if (error)
			callback(error, []);
		else {
			var nodes = result.nodes;

			var tempNodes = [];
			for (var i = 0; i < nodes.length; ++i) {
				if (nodes[i].nodeID !== "")
					tempNodes.push(new Node(_this.session, nodes[i].nodeID, nodes[i].name));
				else
					tempNodes.push(null); // 没找到给一个空
			}

			callback(null, tempNodes);
		}
	};

	var callbackID = Utility.genGUID();
	this.session.registerCallback(callbackID, callbackWrapper, "findDescendantNodes");

	var params = {
		nodeID: node.objectID,
		names: names,
		callbackID: callbackID
	};
	this.session.request(this.objectID, "findDescendantNodes", params);
};

/**
 * 计算定位到一组节点的视角
 * 
 * @param {Object} 						option	 							计算定位视角参数
 * @param {Node[]} 						option.nodes  						需要计算视角的节点集合(出现为空的节点返回错误)
 * @param {vec3} 						[option.direction]					要求视角的方向,若指定该参数将会按照指定的视角方向计算视角,否则选择最适合的视角方向计算视角
 * @param {Number} 						[option.daRatio=0.6]    			以屏幕宽度或者高度的较小值为基数,乘以该百分比作为显示区域大小的边长(值:0<daRate<=1)传入0时会是默认值
 * @param {Function} 					option.callback   					计算完成后回调
 * @param {InvalidArgumentError} 		option.callback.err  				计算失败返回错误:节点集合中的节点存在一个节点有多个父节点,引发错误
 * @param {Viewpoint} 					option.callback.viewpoint  			返回计算的视角
 */
View3D.prototype.calcNodesFocusedViewpoint = function (option) {
	option.direction = option.direction || new Matrix.vec3.fromValues(0, 0, 0);
	option.daRatio = option.daRatio || 0.6;

	Condicio.checkIsType(option, Object, "The type of option must be 'Object'!");
	ParamValidation.checkIsTypeArray(option.nodes, Node, "The type of nodes must be 'Node Array' and valid");
	Condicio.checkArgument(option.nodes.length != 0, "The nodes-Array must`t be NULL!");
	ParamValidation.checkIsVec3(option.direction, "The type of viewpoint-direction must be 'vec3' and valid");

	for (var i = 0; i < option.direction.length; ++i) {
		Condicio.checkArgument(!isNaN(option.direction[i]), "The viewpoint-direction is invalid!");
		Condicio.checkIsNumber(option.direction[i], "The viewpoint-direction must be valid!");
	}
	Condicio.checkIsNumber(option.daRatio, "The type of data-Ratio must be 'Number'!");
	Condicio.checkArgument(option.daRatio <= 1 && option.daRatio > 0, "The data-Ratio must: > 0 && <= 1");
	Condicio.checkIsFunction(option.callback, "The type of callback must be 'Function'!");

	var nodeIDs = [];
	for (var i = 0; i < option.nodes.length; ++i) {
		nodeIDs.push(option.nodes[i].objectID);
	}

	var callbackWrapper = function (error, result) {
		if (!error) {
			var viewpoint = result.viewpoint;

			var eye = CvtForJson.cvtToVec3(viewpoint.eye);
			var center = CvtForJson.cvtToVec3(viewpoint.center);
			var up = CvtForJson.cvtToVec3(viewpoint.up);

			option.callback(null, new Viewpoint(eye, center, up));
		}
		else {
			option.callback(error, null);
		}
	};

	var callbackID = Utility.genGUID();
	this.session.registerCallback(callbackID, callbackWrapper, "calcNodesFocusedViewpoint");

	var params = {
		nodeIDs: nodeIDs,
		direction: [option.direction[0], option.direction[1], option.direction[2]],
		daRatio: option.daRatio,
		callbackID: callbackID
	};
	this.session.request(this.objectID, "calcNodesFocusedViewpoint", params);
};

/**
 * 定位到某个视角
 * 
 * @param {Viewpoint}   viewpoint	    需要定位到的视角
 * @param {Number}      [time = 0.0]    定位视角时移动的时间(单位:毫秒),会形成动画效果(不给时间默认为0,会直接定位过去)
 * @param {Function}    [callback]		定位视角完成之后回调,不给回调会有默认值
 */
View3D.prototype.translateViewpoint = function (viewpoint, time, callback) {
	time = time || 0.0;
	callback = callback || function () { };

	Condicio.checkIsType(viewpoint, Viewpoint, "The type of viewpoint must be 'Viewpoint'!");
	Condicio.checkIsNumber(time, Number, "The type of move-time must be 'Number'!");
	Condicio.checkArgument(time >= 0, "The param time must '>=0");
	Condicio.checkIsFunction(callback, Function, "The type of callback must be 'Function'!");

	time = time / 1000;

	// note:在不需要回调的时候这种调用是否合适
	var callbackWrapper = function (error, result) {
		callback();
	};

	var callbackID = Utility.genGUID();
	this.session.registerCallback(callbackID, callbackWrapper, "translateViewpoint");

	var params = {
		viewpoint: {
			eye: [viewpoint.eye[0], viewpoint.eye[1], viewpoint.eye[2]],
			center: [viewpoint.center[0], viewpoint.center[1], viewpoint.center[2]],
			up: [viewpoint.up[0], viewpoint.up[1], viewpoint.up[2]]
		},
		time: time,
		callbackID: callbackID
	};
	this.session.request(this.objectID, "translateViewpoint", params);
};

/**
 * 获取当前视角
 *
 * @param {Function} 	callback            获取视角完成后的回调
 * @param {Error} 		callback.err		获取视角失败返回错误
 * @param {Viewpoint} 	callback.viewpoint  获取视角成功返回视角 
 */
View3D.prototype.acquireViewpoint = function (callback) {
	Condicio.checkIsFunction(callback, "The type of callback must be 'Function'!");

	var callbackWrapper = function (error, result) {
		if (!error) {
			var viewpoint = result.viewpoint;

			var eye = CvtForJson.cvtToVec3(viewpoint.eye);
			var center = CvtForJson.cvtToVec3(viewpoint.center);
			var up = CvtForJson.cvtToVec3(viewpoint.up);

			callback(null, new Viewpoint(eye, center, up));
		}
		else
			callback(error, null);
	}

	var callbackID = Utility.genGUID();
	this.session.registerCallback(callbackID, callbackWrapper, "acquireViewpoint");

	this.session.request(this.objectID, "acquireViewpoint", { callbackID: callbackID });
};

/**
 * 移动节点(该节点是MatrixTransform节点才支持移动)
 * 支持平移、旋转
 * 进行移动的所有点坐标信息,必须统一为世界坐标系坐标或局部坐标系坐标
 * 注意:当使用matrix进行移动节点的时候,如果此时的matrix是一个复合的mat(有多个旋转、移动),且有移动时间时,
 * 此时的动画效果中间过程是无法预估的运动轨迹,但是始末位置都是对的。
 * 移动方式有两种:1.使用matrix进行节点移动, 2.使用translations或者rotation进行节点移动,二者必用其一。
 * @param {Object}      			option	 													移动节点参数
 * @param {Object[]}    			option.transformations										需要做的移动变换集合(同一组集合中要移动的节点不能重复)
 * @param {Node}        			option.transformations.node  								需要移动的节点
 * @param {mat4}					[option.transformations.matrix]								需要进行变换的矩阵信息(<a href="https://fulongtech.atlassian.net/wiki/spaces/RenderingTech/pages/75957738/VIC">参考文档详见</a>)
 * @param {Boolean}					[option.transformations.isLocalCoordinate = false]			移动信息坐标是否为局部坐标(默认处理方式为世界坐标)
 * @param {vec3}        			[option.transformations.translation]						需要将目标节点向某个方向移动的位置
 * @param {Object}					[option.transformations.rotation]							目标节点旋转参数
 * @param {vec3}					option.transformations.rotation.position 					旋转点
 * @param {vec3}					option.transformations.rotation.direction					旋转轴方向
 * @param {Number}					option.transformations.rotation.angle						旋转角度(角度大于零度,根据其旋转轴方向,使用右手法则,可确定模型旋转方向,负角度则相反)
 * @param {Number}      			[option.duration = 0]										移动节点经过的时间(单位:毫秒)
 * @param {Function}    			option.callback 				    						节点移动完成时回调
 * @param {InvalidArgumentError} 	option.callback.err 										错误:某个节点是不可移动的
 */
View3D.prototype.transformNodes = function (option) {
	option.duration = option.duration || 0;

	Condicio.checkIsObject(option, "The type of option must be 'Object'!");
	ParamValidation.checkIsTypeArray(option.transformations, Object, "The type of transformations must be 'Object Array'!");
	Condicio.checkArgument(option.transformations.length !== 0, "The transformations-Array must`t be NULL!");

	var defaultVec3 = Matrix.vec3.fromValues(0, 0, 0);
	var transformations = [];
	for (var i = 0; i < option.transformations.length; ++i) {
		Condicio.checkIsType(option.transformations[i].node, Node, "Every node must be valid!");
		var isLocalCoordinate = option.transformations[i].isLocalCoordinate || false;


		//使用mat或者translation/rotation进行变换

		if ((Condicio.isUndefined(option.transformations[i].matrix))
			&& (Condicio.isUndefined(option.transformations[i].translation))
			&& (Condicio.isUndefined(option.transformations[i].rotation))) {
			console.error("参数错误");
			return;
		}

		if (!Condicio.isUndefined(option.transformations[i].matrix)) {
			ParamValidation.checkIsMat4(option.transformations[i].matrix, "The type of matrix must be 'mat4' and valid");

			var transformMat = [];
			for (var n = 0; n < 16; n++)
				transformMat.push(option.transformations[i].matrix[n]);

			transformations.push({ nodeID: option.transformations[i].node.objectID, matrix: transformMat });
		}

		var param = {
			nodeID: option.transformations[i].node.objectID,
			isLocalCoordinate: isLocalCoordinate
		};

		if (!Condicio.isUndefined(option.transformations[i].translation)) {
			ParamValidation.checkIsVec3(option.transformations[i].translation, "The type of translation must be 'vec3' and valid");

			var translation = [];
			for (var j = 0; j < 3; ++j)
				translation.push(option.transformations[i].translation[j]);

			param.translation = translation;
		}

		if (!Condicio.isUndefined(option.transformations[i].rotation)) {
			Condicio.checkIsType(option.transformations[i].rotation, Object, "The type of rotation must be 'Object'!");
			ParamValidation.checkIsVec3(option.transformations[i].rotation.position, "The type of rotation.position must be 'vec3' and valid");
			ParamValidation.checkIsVec3(option.transformations[i].rotation.direction, "The type of rotation.direction must be 'vec3' and valid");

			var position = [];
			for (var k = 0; k < 3; ++k)
				position.push(option.transformations[i].rotation.position[k]);

			var direction = [];
			for (var l = 0; l < 3; ++l)
				direction.push(option.transformations[i].rotation.direction[l]);

			option.transformations[i].rotation.position = position;
			option.transformations[i].rotation.direction = direction;

			param.rotation = option.transformations[i].rotation;
		}
		transformations.push(param);
	}
	Condicio.checkIsNumber(option.duration, "The type of transform-node-time must be 'Number'!");
	Condicio.checkIsFunction(option.callback, "The type of callback must be 'Function'!");

	var callbackWrapper = function (error, result) {
		if (error) {
			option.callback(error);
		}
		else
			option.callback(null);
	};

	var callbackID = Utility.genGUID();
	this.session.registerCallback(callbackID, callbackWrapper, "transformNodes");

	var params = {
		transformations: transformations,
		duration: option.duration / 1000,
		callbackID: callbackID
	};
	this.session.request(this.objectID, "transformNodes", params);
};

/**
 * 执行Command命令
 *
 * @param {Command} 	command            需要执行的Command
 *
 */
View3D.prototype.executeCommand = function (command) {
	Condicio.checkIsType(command, Command, "The type of command must be 'Command'!");
	Condicio.checkNotNull(command, "command parameter cannot be null");
	Condicio.checkNotUndefined(command, "command parameter cannot be undefined");

	this.session.request(this.objectID, "executeCommand", { commandID: command.objectID });
};

/**
 * 屏幕截图
 *
 * @param {Function} 	callback            获取屏幕截图完成后的回调
 * @param {Error} 		callback.err		获取屏幕截图失败返回错误
 * @param {String} 		callback.screenShot 获取屏幕截图成功返回截图数据
 */
View3D.prototype.captureScreenShot = function (callback) {
	Condicio.checkIsFunction(callback, Function, "The type of 'callback' must be 'Function'!");

	var callbackWrapper = function (error, result) {
		if (error) {
			callback(error, null);
		}
		else
			callback(null, result.imageData);
	};

	var callbackID = Utility.genGUID();
	this.session.registerCallback(callbackID, callbackWrapper, "captureScreenShot");

	this.session.request(this.objectID, "captureScreenShot", { callbackID: callbackID });
};

//判断是否为逆时针顺序的点
function isClockWise(p1, p2, p3, norm) {
	norm = norm || Matrix.vec3.fromValues(0, 0, 1);
	var norm1 = Matrix.vec3.create();
	Matrix.vec3.sub(norm1, p2, p1);
	var norm2 = Matrix.vec3.create();
	Matrix.vec3.sub(norm2, p3, p2);
	var norm0 = Matrix.vec3.create();
	Matrix.vec3.cross(norm0, norm1, norm2);
	return Matrix.vec3.dot(norm0, norm) < 0;
};
//判断是否为逆时针顺序的点集合
function isPtArrayClockWise(pts) {
	if (pts.length < 3) {
		return true;
	}
	if (pts.length == 3) {
		isClockWise(pts[0], pts[1], pts[2]);
	}

	var ptInit = pts[0];
	var xMin = ptInit[0];
	var xMax = ptInit[0];
	var yMin = ptInit[1];
	var yMax = ptInit[1];
	var index1 = 0;
	var index2 = 0;
	var index3 = 0;
	var index4 = 0;

	for (var i = 1; i < 4; ++i) {
		var ptCur = pts[i];
		if (xMin > ptCur[0]) {
			xMin = ptCur[0]
			index1 = i;
		}

		if (xMax < ptCur[0]) {
			xMax = ptCur[0];
			index2 = i;
		}

		if (yMin > ptCur[1]) {
			yMin = ptCur[1];
			index3 = i;
		}

		if (yMax < ptCur[1]) {
			yMax = ptCur[1];
			index4 = i;
		}
	}

	var indexArray = [];
	for (var i = 0; i < 4; ++i) {
		if (i === index1) {
			indexArray.push(i);
		}
		if (i === index2) {
			indexArray.push(i);
		}
		if (i === index3) {
			indexArray.push(i);
		}
		if (i === index4) {
			indexArray.push(i);
		}
	}
	return isClockWise(pts[indexArray[0]], pts[indexArray[1]], pts[indexArray[2]]);
};
//将点序转为逆时针
function setPtsToAntiClock(pt3darr) {
	var vpt3darr = [];
	if (isPtArrayClockWise(pt3darr)) {
		for (var i = 3; i >= 0; --i) {
			vpt3darr.push(pt3darr[i]);
		}
		for (var i = 7; i >= 4; --i) {
			vpt3darr.push(pt3darr[i]);
		}
	}
	else {
		vpt3darr = pt3darr;
	}
	return vpt3darr;
};

var analysePicked = function (picked, session) {
	var pickedArray = [];
	for (var i = 0; i < picked.length; ++i) {
		var pick = new Object;

		pick.scale = picked[i].scale;
		pick.area = picked[i].area;

		pick.nodePath = [];

		var nodePath = picked[i].nodePath;
		for (var j = 0; j < nodePath.length; ++j) {
			pick.nodePath.push(new Node(session, nodePath[j].nodeID, nodePath[j].name));
		}

		pickedArray.push(pick);
	}
	return pickedArray;
};

/**
 * 得到box范围内的节点以及节点在其中所占比例及面积
 * 
 * @param {Object}      option	 				 box求交参数
 * @param {vec3[]}      option.boxPoints		 Box八个顶点(按照先底面后顶面,每个面的点为连续(顺时针或逆时针),上下底面对应)
* @param {Function} 	callback                 计算完成后的回调 
 * @param {Error} 		callback.err		     求交失败返回错误
 * @param {Object[]}    callback.picked          计算结果
 * @param {Number} 		callback.picked.scale    节点在所选区域中的部分占整体的比例
 * @param {Number} 		callback.picked.area     节点在所选区域中的部分占的面积
 * @param {Node[]} 		callback.picked.nodePath 选中的节点路径,即从被选中的节点到根节点的所有节点按序排列
 * 
 *
 */
View3D.prototype.queryNodesInBox = function (option, callback) {

	Condicio.checkIsType(option, Object, "The type of option must be 'Object'!");
	ParamValidation.checkIsTypeArray(option.boxPoints, Matrix.glMatrix.ARRAY_TYPE, "The type of points must be 'Number Array' and valid!");
	Condicio.checkIsFunction(callback, "The type of callback must be 'Function'!");

	var boxPointArray = [];
	for (var i = 0; i < option.boxPoints.length; ++i) {
		var point = [];
		for (var j = 0; j < 3; ++j) {
			point.push(option.boxPoints[i][j]);
		}
		boxPointArray.push(point);
	}

	boxPointArray = setPtsToAntiClock(boxPointArray);

	var _this = this;
	var callbackWrapper = function (error, result) {

		var picks = result.picked;
		//解析点击结果
		var picked = analysePicked(picks, _this.session);

		callback(null, picked);

	}

	var callbackID = Utility.genGUID();
	this.session.registerCallback(callbackID, callbackWrapper, "queryNodesInBox");

	var params = {
		boxPointArray: boxPointArray,
		callbackID: callbackID
	};

	this.session.request(this.objectID, "queryNodesInBox", params);
};

/**
 * 计算一组节点的AABB包围盒
 * 注:只支持对当前加载的模型节点进行计算, 目前不支持对装置根节点计算AABB(就是nodes给一个drawingRoot)
 * 计算的是能够包围所有这组节点的包围盒,而不是分别求出各自的包围盒
 * <a href="https://baike.baidu.com/item/AABB%E7%9B%92/10087682?fr=aladdin">AABB相关解释请参考</a>
 * 
 * @param {Node[]}     	nodes 					 			需要包围的一组节点 
 * @param {Function} 	callback                 			计算完成后的回调 
 * @param {Error} 		callback.err		     			返回错误
 *               		err {InvalidArgumentError}       	节点不支持计算包围盒(如有两个父节点)
 * @param {Object}    	callback.aabb	         			计算结果
 * @param {vec3} 		callback.aabb.min 					左下角坐标
 * @param {vec3} 		callback.aabb.max		 			右上角坐标
 *
 */
View3D.prototype.calcAABB = function (nodes, callback) {
	ParamValidation.checkIsTypeArray(nodes, Node, "The  type of nodes must be 'Node Array' and valid!");

	var nodeIDs = CvtForJson.cvtNodeArray(nodes);

	var callbackWrapper = function (error, result) {
		if (error)
			callback(error, null);
		else {
			var min = result.aabb.min;
			var max = result.aabb.max;

			var aabb = {
				min: CvtForJson.cvtToVec3(min),
				max: CvtForJson.cvtToVec3(max)
			};

			callback(null, aabb);
		}
	}

	var callbackID = Utility.genGUID();
	this.session.registerCallback(callbackID, callbackWrapper, "calcAABB");

	var params = {
		nodeIDs: nodeIDs,
		callbackID: callbackID
	};

	this.session.request(this.objectID, "calcAABB", params);
};

/**
 * 计算一个有边界面与指定节点的相交结果
 * 只会对可见节点求交,可见节点是已加载并且没有被隐藏的节点
 *
 * @param {Object}     	option 					 			求交需要的参数
 * @param {vec3[]} 		option.polygon               		一个平面上的闭合边界点组(必须是凸多边形的边界点)
 * @param {Function} 	option.callback                 	计算完成后的回调 
 * @param {Error} 		option.callback.err		     		返回错误
 *               		err {InvalidArgumentError}       	polygon不满足要求
 * @param {Object[]}    option.callback.results	        	求交结果
 * @param {Node[]} 		option.callback.results.nodePath  	求交到的节点路径,即从被选中的节点到根节点的所有节点按序排列
 * @param {vec3[]} 		option.callback.results.polyline  	交到的点集合
 *
 */

View3D.prototype.queryNodesAcrossPolygon = function (option) {
	Condicio.checkIsType(option, Object, "The type of option must be 'Object'!");
	ParamValidation.checkIsVec3Array(option.polygon, "The type of option.polygon must be 'Number Array' and valid!");
	Condicio.checkIsFunction(option.callback, "The type of option.callback must be 'Function'!");

	var bndPointArray = CvtForJson.cvtVec3Array(option.polygon);

	var _this = this;
	var callbackWrapper = function (error, result) {
		if (error)
			option.callback(error, null);
		else {
			var len = result.intersections.length;
			var itns = [];
			for (var i = 0; i < len; ++i) {
				var polyline = CvtForJson.cvtToVec3Array(result.intersections[i].polyline);
				var np = result.intersections[i].nodePath;
				var nodePath = cvtToNodeArray(np, _this.session);

				var itn = {
					nodePath: nodePath,
					polyline: polyline
				};
				itns.push(itn);
			}

			option.callback(null, itns);
		}
	}

	var callbackID = Utility.genGUID();
	this.session.registerCallback(callbackID, callbackWrapper, "queryNodesAcrossPolygon");

	var params = {
		polygon: bndPointArray,
		callbackID: callbackID
	};

	this.session.request(this.objectID, "queryNodesAcrossPolygon", params);
};

/**
 * 改变场景的背景色
 * @param {Color}  	color				背景颜色,颜色中的alpha值无效
 *
 */
View3D.prototype.changeBackgroundColor = function (color) {
	//Condicio.checkIsType(color, COLOR, "The type of color must be 'Color'");

	var params = {
		color: color.rgbaArray()
	};

	this.session.request(this.objectID, "changeBackgroundColor", params);
};

/**
 * 创建增加效果命令
 *
 * @param  {Effect} effect    想要创建增加效果的命令。  备注:此接口创建的command,当移除Effect时,应使用createRemoveEffectCommand接口,不支持使用removeEffect操作
 *
 * @return {Command}
 */
View3D.prototype.createAddEffectCommand = function (effect) {
	Condicio.checkNotNull(effect, "Effect must`t be NULL!");
	Condicio.checkNotUndefined(effect, "Effect is undefined!");
	Condicio.checkIsType(effect, Effect, "The type of effect must be 3DEffect");

	var commandID = Utility.genGUID();
	var params = {
		commandID: commandID,
		effectID: effect.objectID
	};
	this.session.request(this.objectID, "createAddEffectCommand", params);

	effect.on(this.session);

	return new Command(commandID);
};

/**
 * 创建删除效果命令
 *
 * @param  {Effect} effect    想要创建删除效果的命令。   备注:此接口和createAddEffectCommand接口对应使用,
 *                            createAddEffectCommand创建的command被应用后,若要移除Effect,则必须用此接口,不支持使用removeEffect操作!
 *
 * @return {Command}
 */
View3D.prototype.createRemoveEffectCommand = function (effect) {
	Condicio.checkNotNull(effect, "Effect must`t be NULL!");
	Condicio.checkNotUndefined(effect, "Effect is undefined!");
	Condicio.checkIsType(effect, Effect, "The type of effect must be 3DEffect");

	var commandID = Utility.genGUID();
	var params = {
		commandID: commandID,
		effectID: effect.objectID
	};
	this.session.request(this.objectID, "createRemoveEffectCommand", params);

	effect.off(this.session);

	return new Command(commandID);
};

/**
 * 创建组合命令	
 * 
 * @param  {Command[]} commands		创建组合命令 commands是按顺序执行的。
 * @return {Command}
 */
View3D.prototype.createCompositeCommand = function (commands) {

	ParamValidation.checkIsTypeArray(commands, Command, "The type of commands must be 'Command Array' and valid")
	Condicio.checkArgument((commands.length !== 0), "The commands-Array must`t be NULL!");

	var commandIDs = CvtForJson.cvtCommandArray(commands)
	var commandID = Utility.genGUID();
	var params = {
		commandID: commandID,
		commandIDs: commandIDs
	};
	this.session.request(this.objectID, "createCompositeCommand", params);

	return new Command(commandID);
};

var checkThreePointsCollinear = function (vec3Array) {
	var y = (vec3Array[2][1] - vec3Array[0][1]) / (vec3Array[1][1] - vec3Array[0][1]);
	var x = (vec3Array[2][0] - vec3Array[0][0]) / (vec3Array[1][0] - vec3Array[0][0]);
	var z = (vec3Array[2][2] - vec3Array[0][2]) / (vec3Array[1][2] - vec3Array[0][2]);
	Condicio.checkArgument(x != y || y != z || x != z, "The interface supports up to 32 planes");
};

/**
 * 计算凸多面体范围内的节点以及节点在其中所占比例及面积(最大支持32个面数量的求交)
 * 
 * @param   {Object[]}  planeArray               面存储面的数组
 * @param	{Object}	planeArray.plane		 面对象
 * @param  	{vec3[]} 	planeArray.plane.points  组成面的点
 * @param  	{vec3[]} 	planeArray.plane.normal  该面的法线(取该面三个点,三个点的存放顺序决定这个面的法线,顺序遵循右手定理)
 * 
 * @param {Function} 	callback                 计算完成后的回调 
 * @param {Error} 		callback.err		     求交失败返回错误
 * @param {Object[]}    callback.picked          计算结果
 * @param {Number} 		callback.picked.scale    节点在所选区域中的部分占整体的比例
 * @param {Number} 		callback.picked.area     节点在所选区域中的部分占的面积
 * @param {Node[]} 		callback.picked.nodePath 选中的节点路径,即从被选中的节点到根节点的所有节点按序排列
 */
View3D.prototype.queryNodesInConvexPolyhedron = function (planeArray, callback) {

	ParamValidation.checkIsTypeArray(planeArray, Object, "The type of planeArray must be 'Object Array");

	Condicio.checkArgument(planeArray.length <= 32, "The interface supports up to 32 planes");

	for (var i = 0; i < planeArray.length; i++) {
		var plane = planeArray[i];
		Condicio.checkIsType(plane, Object, "The type of planeArray.plane must be 'Object'!");

		Condicio.checkArgument(plane.points != undefined && plane.points.length != 0, "plane.points Cannot be empty");
		ParamValidation.checkIsVec3Array(plane.points, "The type of planeArray.plane.points must be 'Vec3 Array' and valid!");

		Condicio.checkArgument(plane.normal != undefined && plane.normal.length != 0, "plane.normal Cannot be empty");
		ParamValidation.checkIsVec3Array(plane.normal, "The type of planeArray.plane.normal must be 'Vec3 Array' and valid!");
		Condicio.checkArgument(plane.normal.length === 3, "plane.normal It should be three points");
		checkThreePointsCollinear(plane.normal);
	}

	Condicio.checkIsFunction(callback, "The type of callback must be 'Function'!");

	var planesArray = [];
	for (var i = 0; i < planeArray.length; i++) {
		var plane = planeArray[i];
		for (var j = 0; j < plane.normal.length; j++) {
			var normalPoint = plane.normal[j];
			var arrayPoint = CvtForJson.cvtVec3(normalPoint);
			planesArray.push(arrayPoint);
		}
	}

	var _this = this;
	var callbackWrapper = function (error, result) {
		if (error)
			callback(error, null);
		else {
			var picks = result.picked;
			//解析点击结果
			var picked = analysePicked(picks, _this.session);

			callback(error, picked);
		}
	}

	var callbackID = Utility.genGUID();
	this.session.registerCallback(callbackID, callbackWrapper, "queryNodesInConvexPolyhedron");

	var params = {
		planeArray: planesArray,
		callbackID: callbackID
	};

	this.session.request(this.objectID, "queryNodesInConvexPolyhedron", params);
};

/**
 * 计算组成圆形的点的集合(圆形tracker和该接口组合使用时,二者pointNum需要一致)
 *
 * @param {Object}     	option 					      需要的参数
 * @param {vec3} 		option.startPt                起始点
 * @param {vec3} 	    option.endPt                  终点 
 * @param {Number} 	    option.pointNum               画圆的点数量(整数 3-65536) 
 * 
 * @param {Function} 	callback                      计算完成后的回调 
 * @param {Error} 		callback.err		          求交失败返回错误
 * @param {vec3[]}      callback.circularPointsArray  计算结果数组
 */

View3D.prototype.calculateCircularPoints = function (option, callback) {
	ParamValidation.checkIsVec3(option.startPt, "The type of option.startPt must be 'vec3' and valid");
	ParamValidation.checkIsVec3(option.endPt, "The type of option.endPt must be 'vec3' and valid");
	Condicio.checkArgument(option.startPt[0] != option.endPt[0] && option.startPt[1] != option.endPt[1], "option.startPt and option.endPt The XY of cannot be the same");

	if (option.pointNum === undefined) { option.pointNum = 30; }
	Condicio.checkIsNumber(option.pointNum, "option.pointNum must be a number");
	Condicio.checkArgument(option.pointNum >= 3 && option.pointNum <= 65536 && option.pointNum % 1 === 0, "option.pointNum must be a positive integer with values from 3 to 65536");

	Condicio.checkIsFunction(callback, "The type of callback must be 'Function'!");

	var callbackID = Utility.genGUID();
	var callbackWrapper = function (error, result) {
		if (error)
			callback(error, null);
		else {
			var circularPointsArray = CvtForJson.cvtToVec3Array(result.circularPointsArray);
			callback(null, circularPointsArray);
		}
	}
	this.session.registerCallback(callbackID, callbackWrapper, "calculateCircularPoints");

	var startPoint = CvtForJson.cvtVec3(option.startPt);
	var endPoint = CvtForJson.cvtVec3(option.endPt);

	var params = {
		callbackID: callbackID,
		pointNum: option.pointNum,
		startPoint: startPoint,
		endPoint: endPoint

	};
	this.session.request(this.objectID, "calculateCircularPoints", params);
};

/**
 * 此接口用于圆坑点集合向凸多面体求交接口的对接
 *
 * @param  	{vec3[]} 	topPts     		圆柱或圆台的上底点数组
 * @param  	{vec3[]} 	bottomPts  		圆柱或圆台的下底点数组
 * @return  {Object[]}  planesArray 	返还用于queryNodesInConvexPolyhedron接口的数组
 */
View3D.prototype.coneCvtConvexPolyhedron = function (topPts, bottomPts) {
	// 求交面数组 
	var planeArray = [];
	// 顶面
	planeArray.push(topPts[2]);
	planeArray.push(topPts[1]);
	planeArray.push(topPts[0]);

	//底面
	planeArray.push(bottomPts[0]);
	planeArray.push(bottomPts[1]);
	planeArray.push(bottomPts[2]);

	// 侧面
	var i2;
	for (i2 = 1; i2 < topPts.length; i2++) {
		planeArray.push(topPts[i2]);
		planeArray.push(bottomPts[i2]);
		planeArray.push(topPts[i2 - 1]);
	}
	planeArray.push(topPts[0]);
	planeArray.push(bottomPts[0]);
	planeArray.push(topPts[i2 - 1]);
	
	var planesArray = [];
	for (var i = 0; i < planeArray.length; i = i + 3) {
		var normal = [];
		normal.push(planeArray[i]);
		normal.push(planeArray[i + 1]);
		normal.push(planeArray[i + 2]);

		var plane = {
			points: normal,
			normal: normal
		}
		planesArray.push(plane);
	}
	return planesArray;
};

/**
 * 设置画质清晰度
 *
 * @param {Object} [option]
 * @param {number} [option.staticQuality = 100]	客户端不进行三维场景漫游操作(拖动,放大等操作)时画质的清晰度(0,100]
 *												值越大画质清晰度越高占用带宽越高
 * @param {number} [option.dynamicQuality = 80] 客户端进行三维场景漫游操作(拖动,放大等操作)时画质的清晰度(0,100]
 *												值越小画质清晰度越低占用带宽越低
 */
View3D.prototype.setPictureQuality = function (option) {
	var defaultOption = {
		staticQuality: 100,
		dynamicQuality: 80
	};
	var newOption = jQuery.extend({}, defaultOption, option);


	Condicio.checkIsNumber(newOption.staticQuality, "The type of staticQuality must be 'Number'!");
	Condicio.checkIsNumber(newOption.dynamicQuality, "The type of dynamicQuality must be 'Number'!");
	Condicio.checkArgument(newOption.staticQuality > 0 && newOption.staticQuality <= 100, "option.staticQuality must Less than or equal to 100 and greater than 0!");
	Condicio.checkArgument(newOption.dynamicQuality > 0 && newOption.dynamicQuality <= 100, "option.dynamicQuality must Less than or equal to 100 and greater than 0!");

	this.session.request(this.objectID, "setPictureQuality", newOption);
};

/**
 * 设置性能监听器
 * 当用户漫游浏览三维区域的时候按照固定频率通知当前传输性能
 * @param {Object}      	performanceListener              			性能监听器
 * @param {Number}      	[performanceListener.interval]				通知的频率(x秒/次),默认为5.0,可以带小数,值要求大于0,推荐不低于1,不高于10
 * @param {Functcion}   	performanceListener.notifier     			性能情况通知回调
 * @param {Object} 			performanceListener.notifier.info 			性能信息
 * @param {Number}			performanceListener.notifier.info.imageTransferDelay 			图片传输延时(单位:ms)
 * 							--图片传输延时低于20ms认为是流畅的
 * 							--图片传输延时低于60ms认为可用
 * 							--图片传输延时大于60ms认为不可用为true,代表当前用户正在漫游浏览三维,反之则否
 */
View3D.prototype.setPerformanceListener = function (performanceListener) {
	Condicio.checkIsObject(performanceListener, "The type of performanceMonitor must be 'Object'!");
	performanceListener.interval = performanceListener.interval || 5;
	Condicio.checkIsNumber(performanceListener.interval, "The type of interaval must be 'Number'!");
	Condicio.checkArgument(performanceListener.interval > 0, "Interval must greater than 0!")
	Condicio.checkIsFunction(performanceListener.notifier, "The type of notifier must be 'Function'!");

	var notifierWrapper = function (error, result) {
		performanceListener.notifier(result.info);
	};

	if (this.performanceListenerID) {
		this.session.unregisterCallback(this.performanceListenerID);
	}

	var notifierID = Utility.genGUID();
	this.session.registerCallback(notifierID, notifierWrapper, "setPerformanceListener", true);
	this.performanceListenerID = notifierID;

	var params = {};
	params.performanceListener = {
		notifierID: notifierID,
		interval: performanceListener.interval
	}

	this.session.request(this.objectID, "setPerformanceListener", params);
};
/**
 * 计算一组节点的面片面积和
 * 注:只支持对当前加载的模型节点进行计算, 目前不支持对装置根节点计算面积
 * 计算的是所有节点的面片面积和,不是表面积
 * 
 * @param {Node[]}     	nodes 					 			需要计算的一组节点 
 * @param {Function} 	callback                 			计算完成后的回调 
 * @param {Error} 		callback.err		     			返回错误
 *               		err {InvalidArgumentError}       	节点不支持计算面积(如有两个父节点、DrawingRoot节点等)
 * @param {Number}    	callback.area	         			计算结果,单位平方毫米
 *
 */
View3D.prototype.calcNodesArea = function (nodes, callback) {
	ParamValidation.checkIsTypeArray(nodes, Node, "The  type of nodes must be 'Node Array' and valid!");

	var nodeIDs = CvtForJson.cvtNodeArray(nodes);

	var callbackWrapper = function (error, result) {
		if (error)
			callback(error, null);
		else {
			callback(null, result.area);
		}
	}

	var callbackID = Utility.genGUID();
	this.session.registerCallback(callbackID, callbackWrapper, "calcNodesArea");

	var params = {
		nodeIDs: nodeIDs,
		callbackID: callbackID
	};

	this.session.request(this.objectID, "calcNodesArea", params);
};

module.exports = View3D;