(function($){
  
var rupper = /([A-Z])/g;

// from http://technology.razorfish.com/2010/02/08/detecting-css-transitions-support-using-javascript/
var cssTransitionsSupported;
$.detectCSSTransitionsSupport = function() {
  if (cssTransitionsSupported == undefined) {
    var div = document.createElement('div');
    div.innerHTML = '<div style="-webkit-transition:color 0s linear;-moz-transition:color 0s linear;"></div>';
    cssTransitionsSupported = (div.firstChild.style.webkitTransition !== undefined) || (div.firstChild.style.MozTransition !== undefined);
    delete div;
  }
}

// ----------
// Function: stopCssAnimation
// Stops an animation in its tracks!
$.fn.stopCssAnimation = function(){
  $.detectCSSTransitionsSupport();
  if (!cssTransitionsSupported) {
    this.stop();
  } else {
    this.each(function(){
      // When you remove the CSS Transition properties
      // it doesn't just end the animation where it was,
      // instead it jumps the animation to the end state.
      // Thus, to stop an animation, you first have to get
      // its current computed values, remove the transition
      // properties, and then finally set the elements
      // values appropriatly.
    
      // Get the computed values of the animated properties
      var $el = $(this);
      var props = $el.data("cssAnimatedProps");
      var cStyle = window.getComputedStyle(this, null);
      var cssValues = {};     
      for(var prop in props){
        prop = prop.replace( rupper, "-$1" ).toLowerCase();
        cssValues[prop] = cStyle.getPropertyValue(prop);
      }
    
      // Remove the CSS Transition CSS
      $el.css({
        '-moz-transition-property': 'none',  
        '-moz-transition-duration': '',  
        '-moz-transition-timing-function': '',
        '-webkit-transition-property': 'none',  
        '-webkit-transition-duration': '',  
        '-webkit-transition-timing-function': ''    
      });
    
      // Set the saved computed properties
      for( var prop in cssValues ){
        $el.css(prop, cssValues[prop]);
      }
    
      // Cancel any onComplete function
      $el.data("cssAnimatedProps", null);
      var timeoutId = $el.data("cssTimeoutId");
      if( timeoutId != null ) {
        clearTimeout(timeoutId);
        $el.data("cssTimeoutId", null);
      }
    });
  }
};

// ----------
// Function: animateWithCSS
// Uses CSS transitions to animate the element. 
// 
// Parameters: 
//   Takes the same properties as jQuery's animate function.
//
//   The only difference is that the easing paramater can now be:
//   bounce, swing, linear, ease-in, ease-out, cubic-bezier, or
//   manually defined as cubic-bezier(x1,y1,x2,y2);
$.fn.animateWithCss = function(props, speed, easing, callback) {
  $.detectCSSTransitionsSupport();
  if (!cssTransitionsSupported) {
    this.animate(props, speed, easing, callback);
  } else {
    var optall = jQuery.speed(speed, easing, callback);  

    if ( jQuery.isEmptyObject( props ) ) {
      return this.each( optall.complete );
    }

    var easings = {
      bounce: 'cubic-bezier(0.0, 0.35, .5, 1.3)', 
      linear: 'linear',
      swing: 'ease-in-out'
    };

    optall.easing = optall.easing || "swing";
    optall.easing = easings[optall.easing] ? easings[optall.easing] : optall.easing;

    // The latest versions of Firefox do not animate from a non-explicitly set
    // css properties. So for each element to be animated, go through and
    // explicitly define 'em.
    this.each(function(){
      $(this).data("cssAnimatedProps", props);
      var cStyle = window.getComputedStyle(this, null);      
      for(var prop in props){
        prop = prop.replace( rupper, "-$1" ).toLowerCase();
        $(this).css(prop, cStyle.getPropertyValue(prop));
      }    
    });

    this.css({
      '-moz-transition-property': 'all', // TODO: just animate the properties we're changing  
      '-moz-transition-duration': optall.duration + 'ms',  
      '-moz-transition-timing-function': optall.easing,
      '-webkit-transition-property': 'all', // TODO: just animate the properties we're changing  
      '-webkit-transition-duration': optall.duration + 'ms',  
      '-webkit-transition-timing-function': optall.easing
    });

    this.css(props);

    var self = this;
    var timeoutId = setTimeout(function() {    
      self.css({
        '-moz-transition-property': 'none',  
        '-moz-transition-duration': '',  
        '-moz-transition-timing-function': '',
        '-webkit-transition-property': 'none',  
        '-webkit-transition-duration': '',  
        '-webkit-transition-timing-function': ''
      });

      self.data("cssAnimatedProps", null); 
      self.data("cssTimeoutId", null);        

      if(jQuery.isFunction(optall.complete))
        optall.complete.apply(self);
    }, optall.duration);
    this.data( "cssTimeoutId", timeoutId );
  }
  
  return this;
}

})(jQuery);