"use strict";

var _ = require("lodash"),
	COLOR = require("color"),
	Condicio = require("condicio"),
	Matrix = require("gl-matrix"),
	Node = require("./Node.js"),
	jQuery = require("jquery"),
    Effect = require("./Effect.js"),
    TransparencyEffect = require("./TransparencyEffect.js"),
	LightEffect = require("./LightEffect.js"),
	ParamValidation = require("../ParamValidation.js"),
	Utility = require("../Utility.js"),
	CvtForJson = require("../CvtForJson.js"),
	ClipEffect = require("./Effects.js").ClipEffect,
	DraggableEffect = require("./Effects.js").DraggableEffect,
	Style = require("./Style.js");
	


/**
 * @class View3D.Dragger
 * 拖拽器
 * 拖拽器可用于创建可拖拽效果接口从而实现拖拽模型的效果
 */
function Dragger(objectID, callbackID, callback) {
    this.objectID = objectID;
	this.callbackID = callbackID;
	this.callback = callback;
};

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

/**
 * @hide
 * 启用dragger
 *
 * @param {Session} session	连接实例
 */
Dragger.prototype.on = function(session){ 
	session.registerCallback(this.callbackID, this.callback, "Dragger", true);
};

/**
 * @hide
 * 结束Dragger
 *
 * @param {Session} session	连接实例
 */
Dragger.prototype.off = function(session){ 
	session.unregisterCallback(this.callbackID);
};

/**
 * @class View3D.EffectFactory
 * 提供创建不同{@link View3D.Effect}的接口
 *
 * 应用不应该构建,应该通过{@link View3D}直接获取
 */
function EffectFactory(session, objectID) {
    this.session = session;
    this.objectID = objectID;
};

/**
 * 创建变色效果
 *
 * @param {Node[]} 	nodes						要改变颜色的节点数组
 * @param {Color}  	color						目标颜色,颜色中的alpha值无效
 *
 * @return {View3D.Effect}
 */
EffectFactory.prototype.createColorChangeEffect = function (nodes, color) {
	ParamValidation.checkIsTypeArray(nodes, Node,  "The  type of nodes must be 'Node Array' and valid!");
	Condicio.checkArgument(nodes.length !== 0, "The number of nodes to highlight must`t be NULL");
    //Condicio.checkIsType(color, COLOR, "The type of color must be 'Color'");
	
    var nodeIDs = CvtForJson.cvtNodeArray(nodes);
    var effectID = Utility.genGUID();
	var params = {
		effectID: effectID,
		nodeIDs: nodeIDs,
		color: color.rgbaArray()
	};
    this.session.request(this.objectID, "createColorChangeEffect", params);

    return new Effect(effectID);
};

/**
 * 创建隐藏节点效果
 *
 * @param {Node[]} nodes		目标节点集合
 *
 * @return {View3D.Effect}
 */
EffectFactory.prototype.createHideEffect = function (nodes) {
	ParamValidation.checkIsTypeArray(nodes, Node, "The type of nodes must be 'Node Array' and valid");
    Condicio.checkArgument(nodes.length !== 0, "The number of target-nodes must`t be NULL!");

    var nodeIDs = CvtForJson.cvtNodeArray(nodes);

    var effectID = Utility.genGUID();
	var params = {
		effectID: effectID,
		nodeIDs: nodeIDs,
	};
    this.session.request(this.objectID, "createHideEffect", params);

    return new Effect(effectID);
};

/**
 * 创建剖切效果
 *
 * 整个场景(view)暂时仅支持六个剖切(对应的剖切号0~5)
 *
 * @param {Number}      no	                            							剖切号(0~5),不能存在相同的剖切号
 * @param {vec3}        pos	                            							剖切面位置
 * @param {vec3}        dir	                          	  							剖切方向,以pos为点以dir为法线构造剖切面。以该剖切面为边界,dir方向一侧的模型正常显示,另一侧的模型则被剖切不显示
 * @param {Node[]}      destNodes	                   	 							要剖切的节点数组(数组中的节点必须存在于场景中)
 * @param {Object}      ctrlParam	                   								剖切控制器参数
 * @param {Number}      ctrlParam.scale	               								剖切控制器矩形大小,这是一个像素值
 * @param {boolean}		[ctrlParam.showClipCtrl = true]								是否显示剖切控制器
 * @param {Color}		[ctrlParam.color = COLOR('rgb(0, 254, 255)').alpha(0.2)]	剖切控制器矩形的颜色,颜色中的alpha值有效
 * @param {boolean}		[ctrlParam.showBorder = false]								是否显示剖切控制器矩形的边框
 * @param {Function}    ctrlParam.pickHandler	        							剖切控制器点击回调
 * @param {Error}       ctrlParam.pickHandler.error									剖切控制器点击回调参数,内部异常,如果没有异常则为null
 * @param {vec3}        ctrlParam.pickHandler.position								剖切控制器点击回调参数,当前剖切面位置
 * @param {vec3}        ctrlParam.pickHandler.direction								剖切控制器点击回调参数,所点选的方向控制器所代表的方向
 *
 * @return {View3D.Effect}
 */
EffectFactory.prototype.createClipEffect = function (no, pos, dir, destNodes, ctrlParam) {
    Condicio.checkIsNumber(no, "The clip no must be Number!");
    ParamValidation.checkIsVec3(pos, "The type of clip-face-position must be 'vec3' and valid!");
	ParamValidation.checkIsVec3(dir, "The type of clip-direction must be 'vec3' and valid!");
	Condicio.checkArgument(Matrix.vec3.length(dir) !== 0, "The clip-direction must be a vector of length not 0");
    ParamValidation.checkIsTypeArray(destNodes, Node, "The type of destNodes must be 'Node Array' and valid!");
    Condicio.checkArgument(destNodes.length !== 0, "The number of destNodes must`t be NULL!");
    Condicio.checkIsObject(ctrlParam, "The type of clip-controller must be 'Object'!");
    Condicio.checkIsNumber(ctrlParam.scale, "The type of clip-matrix-size must be Number!");
    Condicio.checkArgument(ctrlParam.scale > 0, "The ctrlParam.scale must >0!");
    Condicio.checkIsFunction(ctrlParam.pickHandler, "The type of clip-controller-pickHandler must be 'Function'!");

	ctrlParam.color = ctrlParam.color || COLOR('rgb(0, 254, 255)').alpha(0.2);
	ctrlParam.showBorder = ctrlParam.showBorder || false;
	if(Condicio.isUndefined(ctrlParam.showClipCtrl))
		ctrlParam.showClipCtrl = true;
	
	Condicio.checkIsBoolean(ctrlParam.showClipCtrl, "The showClipCtrl must be 'boolean'!");
	Condicio.checkIsBoolean(ctrlParam.showBorder, "The showBorder must be 'boolean'!");
	//Condicio.checkIsType(ctrlParam.color, COLOR, "The type of color must be 'Color'");
	
	var nodeIDs = CvtForJson.cvtNodeArray(destNodes);
	
	var pickHandlerID = Utility.genGUID();
	var pickHandlerWrapper = function (error, result) {
		if (!error) {
			var vec3pos = result.pos;
			var vec3dir = result.dir;
			ctrlParam.pickHandler(error, CvtForJson.cvtToVec3(vec3pos), CvtForJson.cvtToVec3(vec3dir));
		}
		else
			ctrlParam.pickHandler(error, null, null);
	};
	
    var effectID = Utility.genGUID();
	var params = {
		effectID: effectID,
		no: no,
		pos: CvtForJson.cvtVec3(pos),
		dir: CvtForJson.cvtVec3(dir),
		nodeIDs: nodeIDs,
		ctrlParam: {
			scale: ctrlParam.scale,
			color: ctrlParam.color.rgbaArray(),
			showBorder: ctrlParam.showBorder,
			showClipCtrl: ctrlParam.showClipCtrl
		},
		pickHandlerID: pickHandlerID
	};
    this.session.request(this.objectID, "createClipEffect", params);

    return new ClipEffect(effectID, pickHandlerID, pickHandlerWrapper);
};

/**
 * 创建node边框效果
 *
 * @param   {Object}    option
 * @param 	{Node[]} 	option.nodes      							创建边框的目标节点(节点必须存在于场景中)
 * @param 	{Color}  	[option.color = (255, 0, 0)]        		创建边框的颜色(备注:颜色中的alpha值无效)
 * @param 	{Number} 	[option.width = 5]        					创建边框的宽度 
 *
 * @return {view3D.Effect}
 */
EffectFactory.prototype.createOutlineEffect = function(option){
	if(Condicio.isUndefined(option.color))
		option.color = COLOR('rgb(255, 0, 0)').alpha(1);
	if(Condicio.isUndefined(option.width))
		option.width = 5;
	
	ParamValidation.checkIsTypeArray(option.nodes, Node, "The type of nodes must be 'Node Array' and valid");
    Condicio.checkArgument(0 != option.nodes.length, "The number of nodes must`t be NULL!");
	//Condicio.checkIsType(option.color, COLOR, "The type of color must be 'Color'");
	Condicio.checkIsNumber(option.width, "The type of width must be Number!");
	Condicio.checkArgument(option.width > 0, "The width must > 0!");
	
	var nodeIDs = CvtForJson.cvtNodeArray(option.nodes);
    var effectID = Utility.genGUID();
	var params = {
		effectID: effectID,
		nodeIDs: nodeIDs,
		color: option.color.rgbaArray(),
		width: option.width		
	};
    this.session.request(this.objectID, "createOutlineEffect", params);
	
	return new Effect(effectID);
};

/**
 * 创建透明节点效果
 *
 * @param {Object} options
 * @param {Node[]} options.destNodes				目标节点集合
 * @param {Number} [options.transparencyValue = 1]	透明度(范围0~1),0为全透明,1为不透明(值:0<=transparencyValue<=1)
 *
 * @return {View3D.TransparencyEffect}
 */
EffectFactory.prototype.createTransparencyEffect = function (options) {
	if(Condicio.isUndefined(options.transparencyValue))
		options.transparencyValue = 1.0;
	
	
    ParamValidation.checkIsTypeArray(options.destNodes, Node, "The type of destNodes must be 'Node Array' and valid");
    Condicio.checkArgument(0 != options.destNodes.length, "The number of destNodes must`t be NULL!");
    Condicio.checkIsNumber(options.transparencyValue, "The type of transparencyValue must be 'number'!");
    Condicio.checkArgument(options.transparencyValue >= 0 && options.transparencyValue <= 1, "The transparencyValue must: >= 0 && <= 1 !");
    
    var nodeIDs = CvtForJson.cvtNodeArray(options.destNodes);

    var effectID = Utility.genGUID();
	var params = {
		effectID: effectID,
		nodeIDs: nodeIDs,
		transparencyValue: options.transparencyValue
	};
    this.session.request(this.objectID, "createTransparencyEffect", params);

    return new TransparencyEffect(this.session, effectID);
};

/**
 * 创建开口效果
 * 
 * @param {Node[]} nodes	        应用此开口效果的节点数组(目前节点只支持Drawing加载出的Node)
 * @param {vec3[]} openingBoundary	多边形各个点坐标(开口的区域依赖于传入的点序,点依次和下一个点之间构成开口区域的一条边,
									最后一个点和第一个构成一条边)
 * @return {View3D.Effect}
 */
EffectFactory.prototype.createOpeningEffect = function (nodes, openingBoundary) {
    ParamValidation.checkIsTypeArray(nodes, Node, "The  type of nodes must be 'Node Array' and valid!");
    ParamValidation.checkIsVec3Array(openingBoundary, "The  type of openPts must be 'vec3 Array' and valid!");
    Condicio.checkArgument(openingBoundary.length > 2, "The size of openPts must greater than 2");

    var nodeIDs = CvtForJson.cvtNodeArray(nodes);
	
	var ptsNumberArray = CvtForJson.cvtVec3Array(openingBoundary);

    var effectID = Utility.genGUID();
	var params = {
		effectID: effectID,
		nodeIDs: nodeIDs,
		openingBoundary: ptsNumberArray
	};
    this.session.request(this.objectID, "createOpeningEffect", params);

    return new Effect(effectID);
};

/**
 * 创建位移拖拽器,拖拽该拖拽器可以实现位置移动
 * 该拖拽器可用于创建可拖拽效果
 * 
 * @param {vec3}        pos									拖拽控制器的初始位置
 * @param {Function}    dragCallback	    				拖拽回调
 * @param {Error}       dragCallback.error					表示拖拽中出现的错误
 * @param {Number}      dragCallback.type					表示当前拖拽回调的类型:1-一次拖拽完成;2-拖拽过程中
 * @param {vec3}        dragCallback.pos					表示当前拖拽器所在位置
 *
 * @return {View3D.Dragger}
 */
EffectFactory.prototype.createAxisTranslationDragger = function (pos, dragCallback) {
	ParamValidation.checkIsVec3(pos, "The  type of pos must be 'vec3' and valid!");
	Condicio.checkIsFunction(dragCallback, "The type of dragCallback must be 'Function'!");
	
    var draggerID = Utility.genGUID();
	var posArray = CvtForJson.cvtVec3(pos);
	var dragCallbackID = Utility.genGUID();
	var dragCallbackWrapper = function (error, result) {
		if (!error) {
			var type = result.type;
			var vec3pos = result.pos;
			dragCallback(error, type, CvtForJson.cvtToVec3(vec3pos));
		}
		else
			dragCallback(error, null, null);
	};
	
	var params = {
		draggerID: draggerID,
		pos: posArray,
		dragCallbackID: dragCallbackID
	};
    this.session.request(this.objectID, "createAxisTranslationDragger", params);

    return new Dragger(draggerID, dragCallbackID, dragCallbackWrapper);
};

/**
 * 创建单方向位移拖拽器,拖拽该拖拽器可以实现位置移动
 * 该拖拽器可用于创建可拖拽效果
 * 
 * @param {Object}      option										创建拖拽器的参数
 * @param {vec3}        option.pos									拖拽控制器的初始位置
 * @param {vec3}        option.direction							拖拽控制器的拖拽方向
 * @param {Object}		[option.dragDistance]						可拖拽移动的距离:单位:毫米,取值[0, +∞)(以初始位置为起点,基于拖拽方向可向前向后拖拽的距离)
 * @param {Number}		option.dragDistance.forward					向前可拖拽的距离
 * @param {Number}		option.dragDistance.backward				向后可拖拽的距离
 * @param {Color}       option.color								拖拽控制器的颜色(颜色中的alpha值无效)
 * @param {Function}    option.dragCallback	    					拖拽回调
 * @param {Error}       option.dragCallback.error					表示拖拽中出现的错误
 * @param {Number}      option.dragCallback.type					表示当前拖拽回调的类型:1-一次拖拽完成; 2-拖拽过程中
 * @param {vec3}        option.dragCallback.pos						表示当前拖拽器所在位置
 *
 * @return {View3D.Dragger}
 */
EffectFactory.prototype.create1DTranslationDragger = function (option) {
	ParamValidation.checkIsVec3(option.pos, "The  type of option.pos must be 'vec3' and valid!");
	ParamValidation.checkIsVec3(option.direction, "The  type of option.direction must be 'vec3' and valid!");
	Condicio.checkArgument(Matrix.vec3.length(option.direction) > 0, "The  type of option.direction must be 'vec3' and valid!");
	//Condicio.checkIsType(option.color, COLOR, "The type of option.color must be 'Color'");
	Condicio.checkIsFunction(option.dragCallback, "The type of option.dragCallback must be 'Function'!");
	
	var dragDistance;
	if(Condicio.isUndefined(option.dragDistance)){
		dragDistance = {
			forward : -1,
			backward : -1
		}
	}
	else{
		Condicio.checkArgument(option.dragDistance.forward >= 0 && option.dragDistance.backward >= 0, "The option.draggerableDistance must >= 0!");
		dragDistance = option.dragDistance;
	}
	
    var draggerID = Utility.genGUID();
	var posArray = CvtForJson.cvtVec3(option.pos);
	var dirArray = CvtForJson.cvtVec3(option.direction);
	var colorArray = option.color.rgbaArray();
	var dragCallbackID = Utility.genGUID();
	var dragCallbackWrapper = function (error, result) {
		if (!error) {
			var type = result.type;
			var vec3pos = result.pos;
			option.dragCallback(error, type, CvtForJson.cvtToVec3(vec3pos));
		}
		else
			option.dragCallback(error, null, null);
	};
	
	var params = {
		draggerID: draggerID,
		pos: posArray,
		direction: dirArray,
		color: colorArray,
		dragDistance: dragDistance, 
		dragCallbackID: dragCallbackID
	};
    this.session.request(this.objectID, "create1DTranslationDragger", params);

    return new Dragger(draggerID, dragCallbackID, dragCallbackWrapper);
};

/**
 * 创建可拖拽效果
 * 创建时需要传入一个拖拽器,可以实现节点被这个拖拽的效果
 * 
 * @param {Node[]} 		nodes	       	应用可拖拽效果的节点集合,只有可变换的(transformable)节点才可以应用此效果
 *										可变换节点的来源有:1.使用createTransformation接口创建的节点
 *										2. 使用接口loadModel加载进来的自定义模型,用户非常确认此模型或者模型中的一部分是可变换的
										3. 使用接口loadOGFModel加载进来的模型。4. 使用接口loadPGFModel加载进来的模型
 * @param {Dragger}     dragger			拖拽器
 *
 * @return {View3D.Effect}
 */
EffectFactory.prototype.createDraggableEffect = function (nodes, dragger) {
	ParamValidation.checkIsTypeArray(nodes, Node, "The  type of nodes must be 'Node Array' and valid!");
	Condicio.checkIsType(dragger, Dragger, "The type of dragger must be 'Dragger'!");
	
	var nodeIDs = CvtForJson.cvtNodeArray(nodes);
	var effectID = Utility.genGUID();
	var params = {
		effectID: effectID,
		nodeIDs: nodeIDs,
		draggerID: dragger.objectID
	};
    this.session.request(this.objectID, "createDraggableEffect", params);
	
	return new DraggableEffect(effectID, dragger);
};

/**
 * 释放效果资源
 *
 * @param {View3D.Effect[]} effects 待释放的效果资源集合
 */
EffectFactory.prototype.releaseEffect = function (effects) {
    ParamValidation.checkIsTypeArray(effects, Effect, "The type of effects must be 'Effect Array' and valid");

    var objectIDs = [];
    for (var i = 0; i < effects.length; ++i) {
		objectIDs.push(effects[i].objectID);
    }

    this.session.request(this.objectID, "releaseEffect", { objectIDs: objectIDs });
};

var MOVING_MODE = {
	NoMoving: 0,
	FollowingCamera: 1	
};

/**
 * 创建光源
 *
 * @param  {Object}     option            		    					创建光照参数
 * @param  {Object}		option.lightStyle  								光照基本属性,参照View3D.Style.LightStyle的定义
 * @param  {Number} 	option.lightNo    								光源编号			
 * @param  {Object} 	option.movingMode								光源移动方式
 * @param  {Number}     option.movingMode.mode							光源移动时,选择移动方式MOVING_MODE
 *																		{ NoMoving: 0,FollowingCamera: 1}	
 * @param  {Number}     [option.movingMode.radian=π/4]					<a href="https://fulongtech.atlassian.net/wiki/spaces/RenderingTech/pages/75956914">参数相关详情请参考</a>
 * @param  {vec3}       [option.movingMode.translate=(0,0,0)]			<a href="https://fulongtech.atlassian.net/wiki/spaces/RenderingTech/pages/75956914">参数相关详情请参考</a>
 *
 * @return {View3D.LightEffect}
 */
EffectFactory.prototype.createLightEffect = function (option){
	
	Condicio.checkIsObject(option, "The type of option must be 'Object'!");
	var newStyle = jQuery.extend({}, Style.LightStyle, option.lightStyle);	
	ParamValidation.checkIsLightStyle(newStyle);
	
	Condicio.checkNotUndefined(option.lightNo, "option.lightNo is undefined!");
	Condicio.checkIsNumber(option.lightNo, "The type of option.lightNo must be Number!");
	Condicio.checkArgument((option.lightNo >= 0 && option.lightNo <8 ), "The value of option.lightNo is not valid");
	newStyle.lightNo = option.lightNo;
	
	Condicio.checkNotUndefined(option.movingMode, "option.movingMode is undefined!");
	Condicio.checkIsObject(option.movingMode, "The type of option.movingMode must be 'Object'!");
	option.movingMode.mode = option.movingMode.mode || MOVING_MODE.NoMoving;
	var movingMode = {
		mode: option.movingMode.mode
	}
	if(movingMode.mode == MOVING_MODE.FollowingCamera) {		
		movingMode.radian = option.movingMode.radian || 0.785;
		Condicio.checkIsNumber(movingMode.radian, "The type of options.movingMode.radian must be Number!");
		
		movingMode.translate = option.movingMode.translate || Matrix.vec3.fromValues(0,0,0);
		ParamValidation.checkIsVec3(movingMode.translate, "The  type of options.movingMode.translate must be 'vec3' and valid!");
		movingMode.translate =  CvtForJson.cvtVec3(movingMode.translate);
	}
	
	var effectID = Utility.genGUID();
	var lightStyle = {
		diffuse: newStyle.diffuse.rgbaArray(),
		specular: newStyle.specular.rgbaArray(),
		ambient: newStyle.ambient.rgbaArray(),		
		position: CvtForJson.cvtVec4(newStyle.position),
		spotCutoff: newStyle.spotCutoff,
		range: newStyle.range,
		direction: CvtForJson.cvtVec3(newStyle.direction)
	} 
	var params = {
		effectID: effectID,
		lightNo: newStyle.lightNo,
		lightStyle:lightStyle,
		movingMode: movingMode,
	};
	
	this.session.request(this.objectID, "createLightEffect", params);
	return new LightEffect(this.session, effectID);
};


module.exports = EffectFactory;