(function ($) {
    // 2010 - November 2011 Zachary Johnson www.zachstronaut.com
    // jQuery plugin to support flicks and taps with both touch and mouse
    // Dual MIT and GPL license, same as jQuery: http://jquery.org/license/
    
    if (typeof Date.now === 'undefined') {Date.now = function () {return new Date().getTime();};} // Stupid IE8
    
    // TODO: move these to a default options object that can be overridden by arg of enableEventsTapFlick()
    var _longTapSpeed = 500;	    // finger must be released after more than _longTapSpeed milliseconds to trigger longtap, and less than _longTapSpeed to trigger a tap
    var _tapMaxDeviance = 13;	    // finger must move less than _tapMaxDeviance pixels between start+end
    var _flickSpeed = 500;		    // start+move_end must happen in less than _flickSpeed milliseconds
	var _flickMinDistance = 50;	    // finger must move more than _flickMinDistance pixels (must be > _tapMaxDeviance)
	var _flickAxisMaxDeviance = 200;    // must be less than this amount of change on Y access to trigger a left/right flick event, must be less than this amount of change on X access to trigger a up/down flick event
	
	
	var _mouseToTouchEvent = function (e, handler) {
		var evt = {changedTouches: [{identifier: 1, pageX: e.pageX, pageY: e.pageY}]};
		
		handler(evt);
	};
	
    $.fn.enableEventsTapFlick = function () {
        $(this).each(function () {
            if ($(this).hasClass('tapFlickEventsEnabled')) {
                return;
            }
            
            var _touchStartPos = false;
            var _touchStartTime = false;
        	
            var $self = $(this);
            var el = $(this).get(0);
            
            
            var _startHandler = function (e) {
                if (e.changedTouches.length > 0) {
        			_touchStartPos = [e.changedTouches[0].pageX, e.changedTouches[0].pageY];
        			_touchStartTime = Date.now();
        		}
        	};

        	// var _moveHandler = function (e) {};

        	var _endHandler = function (e) {
                if (e.changedTouches.length > 0) {
                    var t = Date.now();
                    
                    var change = {
                        x: e.changedTouches[0].pageX - _touchStartPos[0],
                        y: e.changedTouches[0].pageY - _touchStartPos[1]
                    };
                    
        			if (t - _touchStartTime < _flickSpeed) {
        			    if (Math.abs(change.y) < _flickAxisMaxDeviance) {
        			        if (change.x > _flickMinDistance) {
            					$self.trigger('flick', ['right']);

            				} else if (change.x < -_flickMinDistance) {
                                $self.trigger('flick', ['left']);
            				}
        			    }
        			    
        			    if (Math.abs(change.x) < _flickAxisMaxDeviance) {
        			        if (change.y > _flickMinDistance) {
            					$self.trigger('flick', ['down']);

            				} else if (change.y < -_flickMinDistance) {
                                $self.trigger('flick', ['up']);
            				}
        			    }
        			}
        			
        			if (Math.abs(change.x) + Math.abs(change.y) < _tapMaxDeviance) {
        			    if (t - _touchStartTime < _longTapSpeed) {
        			        $self.trigger('tap', [e.changedTouches[0]]);
        			        
        			    } else {
        			        $self.trigger('longtap', [e.changedTouches[0]]);
        			    }
        			}
        		}
        	};
            
            
            if (typeof el.ontouchstart === 'undefined') {
                $(this).bind('mousedown', function (e) {_mouseToTouchEvent(e, _startHandler);});
                // $(this).bind('mousemove', function (e) {_mouseToTouchEvent(e, _moveHandler);});
                $(this).bind('mouseup', function (e) {_mouseToTouchEvent(e, _endHandler);});
                
            } else {
                el.addEventListener('touchstart', _startHandler, false);
                // el.addEventListener('touchmove', _moveHandler, false);
                el.addEventListener('touchend', _endHandler, false);
            }
        });
        
        $(this).addClass('tapFlickEventsEnabled')
        
        return this;
    }
}(jQuery));


(function ($) {
    // 2011 Zachary Johnson www.zachstronaut.com
    // jQuery plugin to make using CSS transitions with fallbacks easier
    // Dual MIT and GPL license, same as jQuery: http://jquery.org/license/
    
    var _transitionProperty = null;
    var _transitionEvent = null;
    
    $.fn.getTransitionProperty = function ()
    {
        if (_transitionProperty !== null) {
            return _transitionProperty;
        }
        
        _transitionProperty = false;
        
        // Try transition first for forward compatibility
        var properties = ['transition', 'WebkitTransition', 'msTransition', 'MozTransition', 'OTransition'];
        var p;
        while (p = properties.shift())
        {
            if (typeof this.get(0).style[p] !== 'undefined')
            {
                _transitionProperty = p;
            }
        }

        return _transitionProperty;
    };
    
    $.fn.getTransitionEvent = function ()
    {
        if (_transitionEvent !== null) {
            return _transitionEvent;
        }
        
        _transitionEvent = false;

        switch ($(this).getTransitionProperty()) {
            case false:
                break;
            
            case 'WebkitTransition':
                _transitionEvent = 'webkitTransitionEnd';
                break;
            
            case 'OTransition':
                _transitionEvent = 'oTransitionEnd';
                break;
            
            default:
                _transitionEvent = 'transitionend';
                break;
        }
        
        return _transitionEvent;
    };
    
    $.fn.transition = function (beforeFunc, afterFunc, fallbackFunc) {
        if ($(this).length) {
            if ($(this).getTransitionProperty() === false) {
                fallbackFunc.call(this);
                return this;
            }

            if (typeof afterFunc !== 'undefined' && afterFunc) {
                var el = $(this).get(0);
                var eventName = $(this).getTransitionEvent();

                var eff = function (e) {
                    afterFunc.call(this, e);
                    el.removeEventListener(eventName, eff, false);
                }

                el.addEventListener(
                    eventName,
                    eff,
                    false
                );
            }

            if (typeof beforeFunc !== 'undefined' && beforeFunc) {
                beforeFunc.call(this);
            }
        }
        
        return this;
    };
}(jQuery));


(function ($) {
    var _propsObj = null;
    
    // Monkey patch jQuery 1.3.1+ css() method to support CSS 'transform'
    // property uniformly across Safari/Chrome/Webkit, Firefox 3.5+, IE 9+, and Opera 11+.
    // 2009-2011 Zachary Johnson www.zachstronaut.com
    // Updated 2011.05.04 (May the fourth be with you!)
    $.fn.getTransformProperty = function ()
    {
        if (_propsObj && _propsObj['transform']) {
            return _propsObj['transform'];
        }
        
        // Try transform first for forward compatibility
        // In some versions of IE9, it is critical for msTransform to be in
        // this list before MozTranform.
        var properties = ['transform', 'WebkitTransform', 'msTransform', 'MozTransform', 'OTransform'];
        var p;
        while (p = properties.shift())
        {
            if (typeof this.get(0).style[p] !== 'undefined')
            {
                return p;
            }
        }

        // No support?
        return false;
    }

    var proxied = $.fn.css;
    $.fn.css = function (arg, val)
    {
        // Temporary solution for current 1.6.x incompatibility, while
        // preserving 1.3.x compatibility, until I can rewrite using CSS Hooks
        if (_propsObj === null)
        {
            if (typeof $.cssProps != 'undefined')
            {
                _propsObj = $.cssProps;
            }
            else if (typeof $.props != 'undefined')
            {
                _propsObj = $.props;
            }
            else
            {
                _propsObj = {}
            }
        }

        // 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 _propsObj['transform'] == 'undefined'
            &&
            (
                arg == 'transform'
                ||
                (
                    typeof arg == 'object'
                    && typeof arg['transform'] != 'undefined'
                )
            )
        )
        {
            _propsObj['transform'] = $(this).getTransformProperty();
        }

        // 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.)
        //
        // But, only do the forced mapping if the correct CSS property
        // is not 'transform' and is something else.
        if (_propsObj['transform'] != 'transform')
        {
            // Call in form of css('transform' ...)
            if (arg == 'transform')
            {
                arg = _propsObj['transform'];

                // User wants to GET the transform CSS, and in jQuery 1.4.3
                // calls to css() for transforms return a matrix rather than
                // the actual string specified by the user... avoid that
                // behavior and return the string by calling jQuery.style()
                // directly
                if (typeof val == 'undefined' && jQuery.style)
                {
                    return jQuery.style(this.get(0), arg);
                }
            }

            // Call in form of css({'transform': ...})
            else if
            (
                typeof arg == 'object'
                && typeof arg['transform'] != 'undefined'
            )
            {
                arg[_propsObj['transform']] = arg['transform'];
                delete arg['transform'];
            }
        }

        return proxied.apply(this, arguments);
    };
}(jQuery));

(function ($) {
    // Monkey patch jQuery 1.3.1+ to add support for setting or animating CSS
    // scale and rotation independently.
    // 2009-2010 Zachary Johnson www.zachstronaut.com
    // Updated 2010.11.06, August 2011, and November 2011
    var rotateUnits = 'deg';

    $.fn.rotate = function (val)
    {
        var style = $(this).css('transform') || 'none';

        if (typeof val == 'undefined')
        {
            if (style)
            {
                var m = style.match(/rotate\(([^)]+)\)/);
                if (m && m[1])
                {
                    return m[1];
                }
            }

            return 0;
        }

        var m = val.toString().match(/^(-?\d+(\.\d+)?)(.+)?$/);
        if (m)
        {
            if (m[3])
            {
                rotateUnits = m[3];
            }

            $(this).css(
                'transform',
                style.replace(/none|rotate\([^)]*\)/, '') + 'rotate(' + m[1] + rotateUnits + ')'
            );
        }

        return this;
    }

    // Note that scale is unitless.
    $.fn.scale = function (val, duration, options)
    {
        var style = $(this).css('transform');

        if (typeof val == 'undefined')
        {
            if (style)
            {
                var m = style.match(/scale\(([^)]+)\)/);
                if (m && m[1])
                {
                    return m[1];
                }
            }

            return 1;
        }

        $(this).css(
            'transform',
            style.replace(/none|scale\([^)]*\)/, '') + 'scale(' + val + ')'
        );

        return this;
    }

    // Ugly hacks below with duplicating this... it is important to not share this units
    // objects between all animations or they will overwrite each other
    var translateUnits = {x: 'px', y: 'px', z: 'px'};

    var supports3d = null; // null until set to true or false
    
    var supportsTranslate = null; // null until set to true or false
    
    var strToValAndUnits = function (str) {
        var r = {val: false, units: false};
        
        var m = str.toString().match(/^(-?\d+(\.\d+)?)(.+)?$/);
        if (m)
        {
            if (m[3])
            {
                r.units = m[3];
            }

            r.val = m[1];
        }
        
        return r;
    }
    
    $.fn.translate3d = function (x, y, z)
    {
        if (supports3d === null) {
            supports3d = (typeof $(this).get(0).style.WebkitPerspective !== 'undefined');
        }
        
        if (!supports3d) {
            return $(this).translate(x, y);
        }
        
        var style = $(this).css('transform') || 'none';
        
        if (typeof x === 'undefined')
        {
            if (style)
            {
                var m = style.match(/translate3d\(([^),]+),\s*([^),]+),\s*([^),]+)\)/);
                if (m && m[1] && m[2] && m[3])
                {
                    return {x: m[1], y: m[2], z: m[3]};
                }
            }

            return {x: 0, y: 0, z: 0};
        }
        
        var p = {x: false, y: false, z: false};

        translateUnits = $(this).data('translateUnits') || {x: translateUnits.x, y: translateUnits.y, z: translateUnits.z};

        var oX = strToValAndUnits(x);
        p.x = oX.val;
        if (oX.units) {
            translateUnits.x = oX.units;
        }

        var oY = strToValAndUnits(y);
        p.y = oY.val;
        if (oY.units) {
            translateUnits.y = oY.units;
        }
        
        var oZ = strToValAndUnits(z);
        p.z = oZ.val;
        if (oZ.units) {
            translateUnits.z = oZ.units;
        }

        if (p.x !== false && p.y !== false && p.z !== false)
        {
            $(this).css(
                'transform',
                style.replace(/none|translate3d\([^)]*\)/, '') + 'translate3d(' + p.x + translateUnits.x + ',' + p.y + translateUnits.y + ',' + p.z + translateUnits.z + ')'
            );
        }

        $(this).data('translateUnits', translateUnits);

        return this;
    }

    $.fn.translate = function (x, y)
    {
        if (supportsTranslate === null) {
            supportsTranslate = ($(this).getTransformProperty() !== false);
        }
        
        if (!supportsTranslate) {
            return fallbackPositioning(this, x, y);
        }
        
        var style = $(this).css('transform') || 'none';

        if (typeof x === 'undefined')
        {
            if (style)
            {
                var m = style.match(/translate\(([^),]+),\s*([^),]+)\)/);
                if (m && m[1] && m[2])
                {
                    return {x: m[1], y: m[2]};
                }
            }

            return {x: 0, y: 0};
        }

        var p = {x: false, y: false};

        translateUnits = $(this).data('translateUnits') || {x: translateUnits.x, y: translateUnits.y, z: translateUnits.z};

        var oX = strToValAndUnits(x);
        p.x = oX.val;
        if (oX.units) {
            translateUnits.x = oX.units;
        }

        var oY = strToValAndUnits(y);
        p.y = oY.val;
        if (oY.units) {
            translateUnits.y = oY.units;
        }

        if (p.x !== false && p.y !== false)
        {
            $(this).css(
                'transform',
                style.replace(/none|translate\([^)]*\)/, '') + 'translate(' + p.x + translateUnits.x + ',' + p.y + translateUnits.y + ')'
            );
        }

        $(this).data('translateUnits', translateUnits);

        return this;
    }
    
    var fallbackPositioning = function (self, left, top) {
        var styleLeft = $(self).css('left');
        var styleTop = $(self).css('top');
        
        if (typeof left === 'undefined') {
            return {x: styleLeft, y: styleTop};
        }
        
        var p = {x: false, y: false};

        translateUnits = $(self).data('translateUnits') || {x: translateUnits.x, y: translateUnits.y, z: translateUnits.z};

        var oX = strToValAndUnits(left);
        p.x = oX.val;
        if (oX.units) {
            translateUnits.x = oX.units;
        }

        var oY = strToValAndUnits(top);
        p.y = oY.val;
        if (oY.units) {
            translateUnits.y = oY.units;
        }

        if (p.x !== false && p.y !== false)
        {
            $(self).css({left: p.x + translateUnits.x, top: p.y + translateUnits.y});
        }
        
        $(self).data('translateUnits', translateUnits);
        
        return self;
    }

    // fx.cur() must be monkey patched because otherwise it would always
    // return 0 for current rotate and scale values
    var curProxied = $.fx.prototype.cur;
    $.fx.prototype.cur = function ()
    {
        translateUnits = $(this.elem).data('translateUnits') || {x: translateUnits.x, y: translateUnits.y, z: translateUnits.z};
        
        if (this.prop == 'rotate')
        {
            return parseFloat($(this.elem).rotate());
        }
        else if (this.prop == 'scale')
        {
            return parseFloat($(this.elem).scale());
        }
        else if (this.prop == 'translateX')
        {
            var x = $(this.elem).translate().x;

            // When using fallback, we get a pixel value for left even if we set it with % units
            if (x.toString().match(/px$/) && translateUnits.x == '%')
            {
                x = parseFloat(x);
                x = (x / $(this.elem).parent().width()) * 100;
                return x;
            }
            
            return parseFloat(x);
        }
        else if (this.prop == 'translateX3d')
        {
            var x = $(this.elem).translate3d().x;

            // When using fallback, we get a pixel value for left even if we set it with % units
            if (x.toString().match(/px$/) && translateUnits.x == '%')
            {
                x = parseFloat(x);
                x = (x / $(this.elem).parent().width()) * 100;
                return x;
            }
            
            return parseFloat(x);
        }
        else if (this.prop == 'translateY')
        {
            var y = $(this.elem).translate().y;

            // When using fallback, we get a pixel value for top even if we set it with % units
            if (y.toString().match(/px$/) && translateUnits.y == '%')
            {
                y = parseFloat(y);
                y = (y / $(this.elem).parent().height()) * 100;
                return y;
            }
            
            return parseFloat(y);
        }
        else if (this.prop == 'translateY3d')
        {
            var y = $(this.elem).translate().y;

            // When using fallback, we get a pixel value for top even if we set it with % units
            if (y.toString().match(/px$/) && translateUnits.y == '%')
            {
                y = parseFloat(y);
                y = (y / $(this.elem).parent().height()) * 100;
                return y;
            }
            
            return parseFloat(y);
        }

        return curProxied.apply(this, arguments);
    }

    $.fx.step.rotate = function (fx)
    {
        $(fx.elem).rotate(fx.now + rotateUnits);
    }

    $.fx.step.scale = function (fx)
    {
        $(fx.elem).scale(fx.now);
    }
    
    $.fx.step.translateX = function (fx)
    {
        translateUnits = $(fx.elem).data('translateUnits') || {x: translateUnits.x, y: translateUnits.y, z: translateUnits.z};
        $(fx.elem).translate(fx.now + translateUnits.x, 0); // Hacky, overwrites Y
    }
    
    $.fx.step.translateX3d = function (fx)
    {
        translateUnits = $(fx.elem).data('translateUnits') || {x: translateUnits.x, y: translateUnits.y, z: translateUnits.z};
        $(fx.elem).translate3d(fx.now + translateUnits.x, 0, 0); // Hacky, overwrites Y
    }
    
    $.fx.step.translateY = function (fx)
    {
        translateUnits = $(fx.elem).data('translateUnits') || {x: translateUnits.x, y: translateUnits.y, z: translateUnits.z};
        $(fx.elem).translate(0, fx.now + translateUnits.y); // Hacky, overwrites X
    }
    
    $.fx.step.translateY3d = function (fx)
    {
        translateUnits = $(fx.elem).data('translateUnits') || {x: translateUnits.x, y: translateUnits.y, z: translateUnits.z};
        $(fx.elem).translate3d(0, fx.now + translateUnits.y, 0); // Hacky, overwrites X
    }

    /*

    Starting on line 3905 of jquery-1.3.2.js we have this code:

    // We need to compute starting value
    if ( unit != "px" ) {
        self.style[ name ] = (end || 1) + unit;
        start = ((end || 1) / e.cur(true)) * start;
        self.style[ name ] = start + unit;
    }

    This creates a problem where we cannot give units to our custom animation
    because if we do then this code will execute and because self.style[name]
    does not exist where name is our custom animation's name then e.cur(true)
    will likely return zero and create a divide by zero bug which will set
    start to NaN.

    The following monkey patch for animate() gets around this by storing the
    units used in the rotation definition and then stripping the units off.

    */

    var animateProxied = $.fn.animate;
    $.fn.animate = function (prop)
    {
        translateUnits = $(this).data('translateUnits') || {x: translateUnits.x, y: translateUnits.y, z: translateUnits.z};
        
        if (typeof prop['rotate'] != 'undefined')
        {
            var m = prop['rotate'].toString().match(/^(([+-]=)?(-?\d+(\.\d+)?))(.+)?$/);
            if (m && m[5])
            {
                rotateUnits = m[5];
            }

            prop['rotate'] = m[1];
        }
        else if (typeof prop['translateX'] !== 'undefined')
        {
            var m = prop['translateX'].toString().match(/^(([+-]=)?(-?\d+(\.\d+)?))(.+)?$/);
            if (m && m[5])
            {
                translateUnits.x = m[5];
            }

            prop['translateX'] = m[1];
        }
        else if (typeof prop['translateX3d'] !== 'undefined')
        {
            var m = prop['translateX3d'].toString().match(/^(([+-]=)?(-?\d+(\.\d+)?))(.+)?$/);
            if (m && m[5])
            {
                translateUnits.x = m[5];
            }

            prop['translateX3d'] = m[1];
        }
        else if (typeof prop['translateY'] !== 'undefined')
        {
            var m = prop['translateY'].toString().match(/^(([+-]=)?(-?\d+(\.\d+)?))(.+)?$/);
            if (m && m[5])
            {
                translateUnits.y = m[5];
            }

            prop['translateY'] = m[1];
        }
        else if (typeof prop['translateY3d'] !== 'undefined')
        {
            var m = prop['translateY3d'].toString().match(/^(([+-]=)?(-?\d+(\.\d+)?))(.+)?$/);
            if (m && m[5])
            {
                translateUnits.y = m[5];
            }

            prop['translateY3d'] = m[1];
        }
        
        $(this).data('translateUnits', translateUnits);

        return animateProxied.apply(this, arguments);
    }
}(jQuery));

