/*
 * transform: A light jQuery cssHooks for 2d transform
 *
 * limitations:
 * - requires jQuery 1.4.3+
 * - Should you use the *translate* property, then your elements need to be absolutely positionned in a relatively positionned wrapper **or it will fail in IE678**.
 * - incompatible with "matrix(...)" transforms
 * - transformOrigin is not accessible
 *
 * latest version and complete README available on Github:
 * https://github.com/louisremi/jquery.transform.js
 *
 * Copyright 2011 @louis_remi
 * Licensed under the MIT license.
 *
 * This saved you an hour of work?
 * Send me music http://www.amazon.co.uk/wishlist/HNTU0468LQON
 *
 */
(function( $ ) {

/*
 * Feature tests and global variables
 */
var div = document.createElement("div"),
	divStyle = div.style,
	propertyName = "transform",
	suffix = "Transform",
	testProperties = [
		"O" + suffix,
		"ms" + suffix,
		"Webkit" + suffix,
		"Moz" + suffix,
		// prefix-less property
		propertyName
	],
	i = testProperties.length,
	supportProperty,
	supportMatrixFilter,
	propertyHook,
	rMatrix = /Matrix([^)]*)/;

// test different vendor prefixes of this property
while ( i-- ) {
	if ( testProperties[i] in divStyle ) {
		$.support[propertyName] = supportProperty = testProperties[i];
		continue;
	}
}
// IE678 alternative
if ( !supportProperty ) {
	$.support.matrixFilter = supportMatrixFilter = divStyle.filter === "";
}
// prevent IE memory leak
div = divStyle = null;

// px isn't the default unit of this property
$.cssNumber[propertyName] = true;

/*
 * fn.css() hooks
 */
$.cssHooks[propertyName] = propertyHook = {
	// One fake getter to rule them all 
	get: function( elem ) {
		var transform = $.data( elem, "transform" ) || {
			translate: [0,0],
			rotate: 0,
			scale: [1,1],
			skew: [0,0]
		};
		transform.toString = function() {
			return "translate("+this.translate[0]+"px,"+this.translate[1]+"px) rotate("+this.rotate+"rad) scale("+this.scale+") skew("+this.skew[0]+"rad,"+this.skew[1]+"rad)";
		}
		return transform;
	},
	set: function( elem, value, animate ) {
		if ( typeof value === "string" ) {
			value = components(value);
		}

		var translate = value.translate,
			rotate = value.rotate,
			scale = value.scale,
			skew = value.skew,
			elemStyle = elem.style,
			currentStyle,
			filter;

		$.data( elem, "transform", value );

		// We can improve performance by avoiding unnecessary transforms
		// skew is the less likely to be used
		if (!skew[0] && !skew[1]) {
			skew = 0;
		}

		if ( supportProperty ) {
			elemStyle[supportProperty] = "translate("+translate[0]+"px,"+translate[1]+"px) rotate("+rotate+"rad) scale("+scale+")"+(skew?" skew("+skew[0]+"rad,"+skew[1]+"rad)" : "");

		} else if ( supportMatrixFilter ) {

			if ( !animate ) {
				elemStyle.zoom = 1;
			}

			var cos = Math.cos(rotate),
				sin = Math.sin(rotate),
				M11 = cos*scale[0],
				M12 = -sin*scale[1],
				M21 = sin*scale[0],
				M22 = cos*scale[1],
				tanX,
				tanY,
				Matrix,
				centerOrigin;

			if ( skew ) {
				tanX = Math.tan(skew[0]);
				tanY = Math.tan(skew[1]);
				M11 += M12*tanY;
				M12 += M11*tanX;
				M21 += M22*tanY;
				M22 += M21*tanX;
			}

			Matrix = [
				"Matrix("+
					"M11="+M11,
					"M12="+M12,
					"M21="+M21,
					"M22="+M22,
					"SizingMethod='auto expand'"
			].join();
			filter = ( currentStyle = elem.currentStyle ) && currentStyle.filter || elemStyle.filter || "";

			elemStyle.filter = rMatrix.test(filter) ?
				filter.replace(rMatrix, Matrix) :
				filter + " progid:DXImageTransform.Microsoft." + Matrix + ")";

			// center the transform origin, from pbakaus's Transformie http://github.com/pbakaus/transformie
			if ( (centerOrigin = $.transform.centerOrigin) ) {
				elemStyle[centerOrigin == "margin" ? "marginLeft" : "left"] = -(elem.offsetWidth/2) + (elem.clientWidth/2) + "px";
				elemStyle[centerOrigin == "margin" ? "marginTop" : "top"] = -(elem.offsetHeight/2) + (elem.clientHeight/2) + "px";
			}

			// We assume that the elements are absolute positionned inside a relative positionned wrapper
			elemStyle.left = translate[0] + "px";
			elemStyle.top = translate[1] + "px";
		}
	}
};

/*
 * fn.animate() hooks
 */
$.fx.step.transform = function( fx ) {
	var elem = fx.elem,
		start = fx.start,
		end = fx.end,
		pos = fx.pos,
		transform = {},
		coef;

	// fx.end and fx.start need to be converted to their translate/rotate/scale/skew components
	// so that we can interpolate them
	if ( !start || typeof end === "string" ) {
		// the following block can be commented out with jQuery 1.5.1+, see #7912
		if (!start) {
			start = propertyHook.get( elem );
		}

		// force layout only once per animation
		if ( supportMatrixFilter ) {
			elem.style.zoom = 1;
		}

		// end has to be parsed
		fx.end = end = components(end);
	}

	/*
	 * We want a fast interpolation algorithm.
	 * This implies avoiding function calls and sacrifying DRY principle:
	 * - avoid $.each(function(){})
	 * - round values using bitewise hacks, see http://jsperf.com/math-round-vs-hack/3
	 */
	transform.translate = [
		(start.translate[0] + (end.translate[0] - start.translate[0]) * pos + .5) | 0,
		(start.translate[1] + (end.translate[1] - start.translate[1]) * pos + .5) | 0
	];
	transform.rotate = start.rotate + (end.rotate - start.rotate) * pos;
	transform.scale = [
		start.scale[0] + (end.scale[0] - start.scale[0]) * pos,
		start.scale[1] + (end.scale[1] - start.scale[1]) * pos
	];
	transform.skew = [
		start.skew[0] + (end.skew[0] - start.skew[0]) * pos,
		start.skew[1] + (end.skew[1] - start.skew[1]) * pos
	];

	propertyHook.set( elem, transform, true );
};

/*
 * Utility functions
 */
// parse transform components of a transform string not containing "matrix(...)"
function components( transform ) {
	// split the != transforms
	transform = transform.split(")");

	var translate = [0,0],
		rotate = 0,
		scale = [1,1],
		skew = [0,0],
		i = transform.length -1,
		trim = $.trim,
		split, value;

	// add components
	while ( i-- ) {
		split = transform[i].split("(");
		value = split[1];

		switch ( trim(split[0]) ) {
			case "translateX":
				translate[0] += parseInt(value, 10);
				break;

			case "translateY":
				translate[1] += parseInt(value, 10);
				break;

			case "translate":
				value = value.split(",");
				translate[0] += parseInt(value[0], 10);
				translate[1] += parseInt(value[1] || 0, 10);
				break;

			case "rotate":
				rotate += toRadian(value);
				break;

			case "scaleX":
				scale[0] *= value;

			case "scaleY":
				scale[1] *= value;

			case "scale":
				value = value.split(",");
				scale[0] *= value[0];
				scale[1] *= (value.length>1? value[1] : value[0]);
				break;

			case "skewX":
				skew[0] += toRadian(value);
				break;

			case "skewY":
				skew[1] += toRadian(value);
				break;

			case "skew":
				value = value.split(",");
				skew[0] += toRadian(value[0]);
				skew[1] += toRadian(value[1] || "0");
				break;
		}
	}

	return {
		translate: translate,
		rotate: rotate,
		scale: scale,
		skew: skew
	};
}

// converts an angle string in any unit to a radian Float
function toRadian(value) {
	return ~value.indexOf("deg") ?
		parseInt(value,10) * (Math.PI * 2 / 360):
		~value.indexOf("grad") ?
			parseInt(value,10) * (Math.PI/200):
			parseFloat(value);
}

$.transform = {
	centerOrigin: "margin",
	radToDeg: function( rad ) {
		return rad * 180 / Math.PI;
	}
};

})( jQuery );
