jui.define("util.math", [ "util.base" ], function(_) {

	// 2x1 or 3x1 or ?x1 형태의 매트릭스 연산
	function matrix(a, b) {
		var m = [];

		for(var i = 0, len = a.length; i < len; i++) {
			var sum = 0;

			for(var j = 0, len2 = a[i].length; j < len2; j++) {
				sum += a[i][j] * b[j];
			}

			m.push(sum);
		}

		return m;
	}

	// 2x2 or 3x3 or ?x? 형태의 매트릭스 연산
	function deepMatrix(a, b) {
		var m = [], nm = [];

		for(var i = 0, len = b.length; i < len; i++) {
			m[i] = [];
			nm[i] = [];
		}

		for(var i = 0, len = b.length; i < len; i++) {
			for(var j = 0, len2 = b[i].length; j < len2; j++) {
				m[j].push(b[i][j]);
			}
		}

		for(var i = 0, len = m.length; i < len; i++) {
			var mm = matrix(a, m[i]);

			for(var j = 0, len2 = mm.length; j < len2; j++) {
				nm[j].push(mm[j]);
			}
		}

		return nm;
	}

	function matrix3d(a, b) {
		var m = new Float32Array(4);

		m[0] = a[0][0] * b[0] + a[0][1] * b[1] + a[0][2] * b[2]  + a[0][3] * b[3];
		m[1] = a[1][0] * b[0] + a[1][1] * b[1] + a[1][2] * b[2]  + a[1][3] * b[3];
		m[2] = a[2][0] * b[0] + a[2][1] * b[1] + a[2][2] * b[2]  + a[2][3] * b[3];
		m[3] = a[3][0] * b[0] + a[3][1] * b[1] + a[3][2] * b[2]  + a[3][3] * b[3];

		return m;
	}

	function deepMatrix3d(a, b) {
		var nm = [
			new Float32Array(4),
			new Float32Array(4),
			new Float32Array(4),
			new Float32Array(4)
		];

		var m = [
			new Float32Array([b[0][0],b[1][0],b[2][0],b[3][0]]),
			new Float32Array([b[0][1],b[1][1],b[2][1],b[3][1]]),
			new Float32Array([b[0][2],b[1][2],b[2][2],b[3][2]]),
			new Float32Array([b[0][3],b[1][3],b[2][3],b[3][3]])
		];

		nm[0][0] = a[0][0] * m[0][0] + a[0][1] * m[0][1] + a[0][2] * m[0][2]  + a[0][3] * m[0][3];
		nm[1][0] = a[1][0] * m[0][0] + a[1][1] * m[0][1] + a[1][2] * m[0][2]  + a[1][3] * m[0][3];
		nm[2][0] = a[2][0] * m[0][0] + a[2][1] * m[0][1] + a[2][2] * m[0][2]  + a[2][3] * m[0][3];
		nm[3][0] = a[3][0] * m[0][0] + a[3][1] * m[0][1] + a[3][2] * m[0][2]  + a[3][3] * m[0][3];

		nm[0][1] = a[0][0] * m[1][0] + a[0][1] * m[1][1] + a[0][2] * m[1][2]  + a[0][3] * m[1][3];
		nm[1][1] = a[1][0] * m[1][0] + a[1][1] * m[1][1] + a[1][2] * m[1][2]  + a[1][3] * m[1][3];
		nm[2][1] = a[2][0] * m[1][0] + a[2][1] * m[1][1] + a[2][2] * m[1][2]  + a[2][3] * m[1][3];
		nm[3][1] = a[3][0] * m[1][0] + a[3][1] * m[1][1] + a[3][2] * m[1][2]  + a[3][3] * m[1][3];

		nm[0][2] = a[0][0] * m[2][0] + a[0][1] * m[2][1] + a[0][2] * m[2][2]  + a[0][3] * m[2][3];
		nm[1][2] = a[1][0] * m[2][0] + a[1][1] * m[2][1] + a[1][2] * m[2][2]  + a[1][3] * m[2][3];
		nm[2][2] = a[2][0] * m[2][0] + a[2][1] * m[2][1] + a[2][2] * m[2][2]  + a[2][3] * m[2][3];
		nm[3][2] = a[3][0] * m[2][0] + a[3][1] * m[2][1] + a[3][2] * m[2][2]  + a[3][3] * m[2][3];

		nm[0][3] = a[0][0] * m[3][0] + a[0][1] * m[3][1] + a[0][2] * m[3][2]  + a[0][3] * m[3][3];
		nm[1][3] = a[1][0] * m[3][0] + a[1][1] * m[3][1] + a[1][2] * m[3][2]  + a[1][3] * m[3][3];
		nm[2][3] = a[2][0] * m[3][0] + a[2][1] * m[3][1] + a[2][2] * m[3][2]  + a[2][3] * m[3][3];
		nm[3][3] = a[3][0] * m[3][0] + a[3][1] * m[3][1] + a[3][2] * m[3][2]  + a[3][3] * m[3][3];

		return nm;
	}

	function inverseMatrix3d(me) {
		var te = [
			new Float32Array(4),
			new Float32Array(4),
			new Float32Array(4),
			new Float32Array(4)
		];

		var n11 = me[0][0], n12 = me[0][1], n13 = me[0][2], n14 = me[0][3];
		var n21 = me[1][0], n22 = me[1][1], n23 = me[1][2], n24 = me[1][3];
		var n31 = me[2][0], n32 = me[2][1], n33 = me[2][2], n34 = me[2][3];
		var n41 = me[3][0], n42 = me[3][1], n43 = me[3][2], n44 = me[3][3];

		te[0][0] = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44;
		te[0][1] = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44;
		te[0][2] = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44;
		te[0][3] = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
		te[1][0] = n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44;
		te[1][1] = n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44;
		te[1][2] = n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44;
		te[1][3] = n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34;
		te[2][0] = n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44;
		te[2][1] = n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44;
		te[2][2] = n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44;
		te[2][3] = n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34;
		te[3][0] = n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43;
		te[3][1] = n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43;
		te[3][2] = n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43;
		te[3][4] = n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33;

		var det = 1 / (n11 * te[0][0] + n21 * te[0][1] + n31 * te[0][2] + n41 * te[0][3]);

		if(det === 0) {
			te = [
				new Float32Array([ 1, 0, 0, 0 ]),
				new Float32Array([ 0, 1, 0, 0 ]),
				new Float32Array([ 0, 0, 1, 0 ]),
				new Float32Array([ 0, 0, 0, 1 ])
			];
		} else {
			te[0][0] *= det; te[0][1] *= det; te[0][2] *= det; te[0][3] *= det;
			te[1][0] *= det; te[1][1] *= det; te[1][2] *= det; te[1][3] *= det;
			te[2][0] *= det; te[2][1] *= det; te[2][2] *= det; te[2][3] *= det;
			te[3][0] *= det; te[3][1] *= det; te[3][2] *= det; te[3][4] *= det;
		}

		return te;
	}

	/**
	 * @class util.math
	 *
	 * Math Utility
	 *
	 * @singleton
	 */
	var self = {

		/**
		 * @method rotate
		 *
		 * 2d rotate
		 *
		 * @param {Number} x
		 * @param {Number} y
		 * @param {Number} radian	roate 할 radian
		 * @return {Object}
		 * @return {Number} return.x  변환된 x
		 * @return {Number} return.y  변환된 y
		 *
 		 */
		rotate : function(x, y, radian) {
			return {
				x : x * Math.cos(radian) - y * Math.sin(radian),
				y : x * Math.sin(radian) + y * Math.cos(radian)
			}
		},

		resize : function(maxWidth, maxHeight, objectWidth, objectHeight) {
			var ratio = objectHeight / objectWidth;

			if (objectWidth >= maxWidth && ratio <= 1) {
				objectWidth = maxWidth;
				objectHeight = maxHeight * ratio;
			} else if (objectHeight >= maxHeight) {
				objectHeight = maxHeight;
				objectWidth = maxWidth / ratio;
			}

			return { width : objectWidth, height : objectHeight};
		},

		/**
		 * @method radian
		 *
		 * convert degree to radian
		 *
		 * @param {Number} degree
		 * @return {Number} radian
		 */
		radian : function(degree) {
			return degree * Math.PI / 180;
		},

		/**
		 * @method degree
		 *
		 * convert radian to degree
		 *
		 * @param {Number} radian
		 * @return {Number} degree
		 */
		degree : function(radian) {
			return radian * 180 / Math.PI;
		},

        angle : function(x1, y1, x2, y2) {
            var dx = x2 - x1,
                dy = y2 - y1;

            return Math.atan2(dy, dx);
        },

		/**
		 * @method interpolateNumber
		 *
		 * a, b 의 중간값 계산을 위한 callback 함수 만들기
		 *
		 * @param {Number} a	first value
		 * @param {Number} b 	second value
		 * @return {Function}
		 */
		interpolateNumber : function(a, b) {
            var dist = (b - a);
			return function(t) {
				return a + dist * t;
			}
		},

		// 중간값 round 해서 계산하기
		interpolateRound : function(a, b) {

            var dist = (b - a);
            return function(t) {
                return Math.round(a + dist * t);
            }
		},

		getFixed : function (a, b) {
			var aArr = (a+"").split(".");
			var aLen = (aArr.length < 2) ? 0 : aArr[1].length;

			var bArr = (b+"").split(".");
			var bLen = (bArr.length < 2) ? 0 : bArr[1].length;

			return (aLen > bLen) ? aLen : bLen;

		},

		fixed : function (fixed) {


			var fixedNumber = this.getFixed(fixed, 0);
			var pow = Math.pow(10, fixedNumber);

			var func = function (value) {
				return Math.round(value * pow) / pow;
			};

			func.plus = function (a, b) {
				return Math.round((a * pow) + (b * pow)) / pow;
			};

			func.minus = function (a, b) {
				return Math.round((a * pow) - (b * pow)) / pow;
			};

			func.multi = function (a, b) {
				return Math.round((a * pow) * (b * pow)) / (pow*pow);
			};

			func.div = function (a, b) {
				var result = (a * pow) / (b * pow);
				var pow2 = Math.pow(10, this.getFixed(result, 0));
				return Math.round(result*pow2) / pow2;
			};

			func.remain = function (a, b) {
				return Math.round((a * pow) % (b * pow)) / pow;
			};

			return func;
		},

		round: function (num, fixed) {
			var fixedNumber = Math.pow(10, fixed);

			return Math.round(num * fixedNumber) / fixedNumber;
		},

		plus : function (a, b) {
			var pow = Math.pow(10, this.getFixed(a, b));

			return Math.round((a * pow) + (b * pow)) / pow;
		},

		minus : function (a, b) {
			var pow = Math.pow(10, this.getFixed(a, b));
			return Math.round((a * pow) - (b * pow)) / pow;
		},

		multi : function (a, b) {
			var pow = Math.pow(10, this.getFixed(a, b));
			return Math.round((a * pow) * (b * pow)) / (pow*pow);
		},

		div : function (a, b) {
			var pow = Math.pow(10, this.getFixed(a, b));

			var result = (a * pow) / (b * pow);
			var pow2 = Math.pow(10, this.getFixed(result, 0));
			return Math.round(result*pow2) / pow2;
		},

		remain : function (a, b) {
			var pow = Math.pow(10, this.getFixed(a, b));
			return Math.round((a * pow) % (b * pow)) / pow;
		},

		/**
		 * 특정 구간의 값을 자동으로 계산 
		 * 
		 * @param {Object} min
		 * @param {Object} max
		 * @param {Object} ticks
		 * @param {Object} isNice
		 */
		nice : function(min, max, ticks, isNice) {
			isNice = isNice || false;

			if (min > max) {
				var _max = min;
				var _min = max;
			} else {
				var _min = min;
				var _max = max;
			}

			var _ticks = ticks;
			var _tickSpacing = 0;
			var _range = [];
			var _niceMin;
			var _niceMax;

			function niceNum(range, round) {
				var exponent = Math.floor(Math.log(range) / Math.LN10);
				var fraction = range / Math.pow(10, exponent);
				var nickFraction;

				if (round) {
					if (fraction < 1.5)
						niceFraction = 1;
					else if (fraction < 3)
						niceFraction = 2;
					else if (fraction < 7)
						niceFraction = 5;
					else
						niceFraction = 10;
				} else {
					if (fraction <= 1)
						niceFraction = 1;
					else if (fraction <= 2)
						niceFraction = 2;
					else if (fraction <= 5)
						niceFraction = 5;
					else
						niceFraction = 10;

					//console.log(niceFraction)
				}

				return niceFraction * Math.pow(10, exponent);

			}

			function caculate() {
				_range = (isNice) ? niceNum(_max - _min, false) : _max - _min;
				_tickSpacing = (isNice) ? niceNum(_range / _ticks, true) : _range / _ticks;
				_niceMin = (isNice) ? Math.floor(_min / _tickSpacing) * _tickSpacing : _min;
				_niceMax = (isNice) ? Math.floor(_max / _tickSpacing) * _tickSpacing : _max;

			}

			caculate();

			return {
				min : _niceMin,
				max : _niceMax,
				range : _range,
				spacing : _tickSpacing
			}
		},

		matrix: function(a, b) {
			if(_.typeCheck("array", b[0])) {
				return deepMatrix(a, b);
			}

			return matrix(a, b);
		},

		matrix3d: function(a, b) {
			if(b[0] instanceof Array || b[0] instanceof Float32Array) {
				return deepMatrix3d(a, b);
			}

			return matrix3d(a, b);
		},

		inverseMatrix3d: function(a) {
			return inverseMatrix3d(a);
		},

		scaleValue: function(value, minValue, maxValue, minScale, maxScale) {
			// 최소/최대 값이 같을 경우 처리
			minValue = (minValue == maxValue) ? 0 : minValue;

			var range = maxScale - minScale,
				tg = range * getPer();

			function getPer() {
				var range = maxValue - minValue,
					tg = value - minValue,
					per = tg / range;

				return per;
			}

			return tg + minScale;
		}
	}

	return self;
}, null, true);