﻿(function ($) {
	var c_transform = 'transform';
	var c_animation = 'animation';
	var c_transition = 'transition';

	function getTransformPropertyNoDefault(element) {
		// Try transform first for forward compatibility
		var properties = [c_transform, 'WebkitTransform', 'MozTransform', 'OTransform'];
		var p;
		while (p = properties.shift()) {
			if (typeof element.style[p] != 'undefined') {
				return p;
			}
		}

		return null;
	}

	function getAnimationPropertyNoDefault(element) {
		// Try animation first for forward compatibility
		var properties = [c_animation, 'WebkitAnimation', 'MozAnimation', 'OAnimation'];
		var p;
		while (p = properties.shift()) {
			if (typeof element.style[p] != 'undefined') {
				return p;
			}
		}

		return null;
	}

	function getTransitionPropertyNoDefault(element) {
		// Try transition first for forward compatibility
		var properties = [c_transition, 'WebkitTransition', 'MozTransition', 'OTransition'];
		var p;
		while (p = properties.shift()) {
			if (typeof element.style[p] != 'undefined') {
				return p;
			}
		}

		return null;
	}

	// Monkey patch jQuery 1.3.1+ css() method to support CSS 'transform'
	// property uniformly across Webkit/Safari/Chrome and Firefox 3.5.
	// 2009 Zachary Johnson www.zachstronaut.com
	function getTransformProperty(element) {
		return getTransformPropertyNoDefault(element) || c_transform;
	}

	var proxied = $.fn.css;
	$.fn.css = function (arg, value) {
		// Find the correct browser specific property and setup the mapping using
		// $.props which is used internally by jQuery.attr() when setting CSS
		// properties via either the css(name, value) or css(properties) method.
		// The problem with doing this once outside of css() method is that you
		// need a DOM node to find the right CSS property, and there is some risk
		// that somebody would call the css() method before body has loaded or any
		// DOM-is-ready events have fired.
		if (typeof $.props[c_transform] == 'undefined' &&
			(arg == c_transform || (typeof arg == 'object' && typeof arg[c_transform] != 'undefined'))) {
			$.props[c_transform] = getTransformProperty(this.get(0));
		}

		// We force the property mapping here because jQuery.attr() does
		// property mapping with jQuery.props when setting a CSS property,
		// but curCSS() does *not* do property mapping when *getting* a
		// CSS property.  (It probably should since it manually does it
		// for 'float' now anyway... but that'd require more testing.)
		if (arg == c_transform) {
			arg = $.props[c_transform];
		}

		if (typeof arg == 'object' && arg[c_transform]) {
			if (typeof arg[c_transform] == 'object')
				arg[c_transform] = convertToString(arg[c_transform]);

			if ($.props[c_transform] != c_transform)
				arg[$.props[c_transform]] = arg[c_transform];
		}

		return proxied.apply(this, arguments);
	};

	function matchSingleton(value, regex, defaultVal) {
		var singletonMatches = value.match(regex);
		if (singletonMatches && singletonMatches.length > 1)
			return Number(singletonMatches[1]);
		else return defaultVal === undefined ? 0 : defaultVal;
	};

	function converToObject(value, onlyValues) {
		if (typeof value != "string")
			return {};

		var transform = {
			translate: "",
			translateX: 0,
			translateY: 0,
			scale: "",
			scaleX: 1,
			scaleY: 1,
			rotate: 0,
			skew: "",
			skewX: 0,
			skewY: 0
		};

		// Scale
		var scaleMatches = value.match(/scale\(([-\d\.\s,]+)\)/);
		if (scaleMatches && scaleMatches.length > 1) {
			transform.scale = scaleMatches[1];
			var scaleSplit = transform.scale.split(/,\s/);

			transform.scaleX = Number(scaleSplit[0]);
			transform.scaleY = scaleSplit.length > 1
				? Number(scaleSplit[1])
				: transform.scaleX;
		}
		else {
			transform.scaleX = matchSingleton(value, /scaleX\(([-\d\.]+)\)/, 1);
			transform.scaleY = matchSingleton(value, /scaleY\(([-\d\.]+)\)/, 1);
			transform.scale = transform.scaleX + ", " + transform.scaleY;
		}

		// Translate
		var translateMatches = value.match(/translate\(([-\d\.\s,pxPX]+)\)/);
		if (translateMatches && translateMatches.length > 1) {
			transform.translate = translateMatches[1];
			var translateSplit = transform.translate.replace(/px/g, "").split(/,\s/);

			transform.translateX = Number(translateSplit[0]);
			transform.translateY = translateSplit.length > 1
				? Number(translateSplit[1])
				: transform.translateX;
		}
		else {
			transform.translateX = matchSingleton(value, /translateX\(([-\d\.]+)[pxPX]{2}\)/);
			transform.translateY = matchSingleton(value, /translateY\(([-\d\.]+)[pxPX]{2}\)/);
			transform.translate = transform.translateX + "px, " + transform.translateY + "px";
		}

		// Rotate
		transform.rotate = matchSingleton(value, /rotate\(([-\d]+)[degDEG]{3}\)/);

		// Skew
		var skewMatches = value.match(/skew\(([-\d\.\s,degDEG]+)\)/);
		if (skewMatches && skewMatches.length > 1) {
			transform.skew = skewMatches[1];
			var skewSplit = transform.skew.replace(/deg/g, "").split(/,\s/);

			transform.skewX = Number(skewSplit[0]);
			transform.skewY = skewSplit.length > 1
				? Number(skewSplit[1])
				: transform.skewX;
		}
		else {
			transform.skewX = matchSingleton(value, /skewX\(([-\d\.]+)[degDEG]{3}\)/);
			transform.skewY = matchSingleton(value, /skewY\(([-\d\.]+)[degDEG]{3}\)/);
			transform.skew = transform.skewX + "deg, " + transform.skewY + "deg";
		}

		if (onlyValues) {
			delete transform.scale
			delete transform.translate;
			delete transform.skew;
		}

		return transform;
	};

	function convertToUnit(value, unit) {
		return value + (isNaN(value) ? "" : unit);
	};

	function convertToString(transform) {
		var value = "";

		if (transform.scale) {
			value += "scale(" + transform.scale + ") ";
		}
		else if (transform.scaleX && transform.scaleY) {
			value += "scale(" + transform.scaleX + ", " + transform.scaleY + ") ";
		}
		else {
			if (transform.scaleX)
				value += "scaleX(" + transform.scaleX + ") ";

			if (transform.scaleY)
				value += "scaleY(" + transform.scaleY + ") ";
		}

		if (transform.translate) {
			value += "translate(" + convertToUnit(transform.translate, "px") + ") ";
		}
		else if (transform.translateX && transform.translateY) {
			value += "translate(" + convertToUnit(transform.translateX, "px") + ", " + convertToUnit(transform.translateY, "px") + ") ";
		}
		else {
			if (transform.translateX)
				value += "translateX(" + convertToUnit(transform.translateX, "px") + ") ";

			if (transform.translateY)
				value += "translateY(" + convertToUnit(transform.translateY, "px") + ") ";
		}

		if (transform.rotate)
			value += "rotate(" + convertToUnit(transform.rotate, "deg") + ") ";

		if (transform.skew) {
			value += "skew(" + convertToUnit(transform.skew, "deg") + ") ";
		}
		else if (transform.skewX && transform.skewY) {
			value += "skew(" + convertToUnit(transform.skewX, "deg") + ", " + convertToUnit(transform.skewY, "deg") + ") ";
		}
		else {
			if (transform.skewX)
				value += "skewX(" + convertToUnit(transform.skewX, "deg") + ") ";

			if (transform.skewY)
				value += "skewY(" + convertToUnit(transform.skewY, "deg") + ") ";
		}

		if (value.length > 1)
			value = value.substring(0, value.length - 1);

		return value;
	};

	function getTransform(element, onlyValues) {
		if (typeof $.props[c_transform] == 'undefined')
			$.props[c_transform] = getTransformProperty(element);

		var currentState = converToObject(element.style[$.props[c_transform]], onlyValues);

		return currentState;
	};

	$.extend({
		transform: function () {
			return {
				transform: getTransformPropertyNoDefault(document.body) != null,
				animation: getAnimationPropertyNoDefault(document.body) != null,
				transition: getTransitionPropertyNoDefault(document.body) != null
			};
		}
	});

	$.fn.transform = function (transform, reset) {
		if (transform !== undefined) {
			if (reset)
				this.css(c_transform, convertToString(transform));
			else this.each(function () {
				var currentState = getTransform(this, true);
				var transformString = convertToString($.extend({}, currentState, transform));
				$(this).css(c_transform, transformString);
			});

			return;
		}

		var firstElement = this.get(0);
		var currentState = getTransform(firstElement);

		return getTransform(firstElement);
	};

	$.fn.perform = function (transform, speed, callback) {
		if (jQuery.isEmptyObject(transform)) {
			return this.each(callback);
		}

		speed = speed
			? speed
			: $.fx.speeds._default;

		return this.each(function () {
			var element = this;
			var $element = $(element);
			var currentState = getTransform(element, true);

			var step = speed / 13;

			var timeoutKey = "timeout.perform";

			var oldTimeouts = $element.data(timeoutKey);
			if (oldTimeouts)
				$.each(oldTimeouts, function (name, value) { clearInterval(value); });

			$element.data(timeoutKey, new Object());

			$.each(transform, function (name, value) {
				var start = currentState[name], end = transform[name];
				var current = start;

				if (start == end)
					return true;

				var up = (start < end), increment = (end - start) / step;

				$element.data(timeoutKey)[name] = setInterval(function () {
					current = current + increment;

					if ((current == end) || (up && current > end) || (!up && current < end)) {
						var newTransform = new Object();
						newTransform[name] = end;

						$element.css(c_transform, convertToString($.extend(currentState, newTransform)));

						var timeouts = $element.data(timeoutKey);
						if (timeouts && timeouts[name]) {
							clearInterval(timeouts[name]);
							delete timeouts[name];

							if ($.isFunction(callback) && $.isEmptyObject(timeouts))
								callback.call(element);
						}

						return true;
					}
					else {
						var newTransform = new Object();
						newTransform[name] = current;
						$element.css(c_transform, convertToString($.extend(currentState, newTransform)));
					}
				}, 13);
			});

			return true;
		});
	};
})(jQuery);
