/*
 * Name :			jQuery.rotation
 * Version :		1.2
 * Author :			Léopold HOUDIN
 * Last update :	02/15/2011 16:09
 * Project url :	
 *
 * Based on jQuery.easyRotate by jordanandree ( http://plugins.jquery.com/project/easy-element-rotation )
 *
 * Copyright (c) Houdin Leopold
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function( $ ){


	var methods = {
  
  
		/*
		 * Rotates an object.
		 * $( 'xxx' ).rotation( 'rotation', { [ degrees ] } )
		 * degrees : the object angle after the rotation
		 */
		rotation : function( params ) {
			
			// default params
			var defaults = {
				degrees : 0
			};
			
			// extend the params
			var params = $.extend( defaults, params );
			
			return this.each( function( ) {
				
				// the object
				var $this = $( this );
			
				// calcultations to get the rotation matrix
				var deg2radians	= Math.PI / 180;
				var theta		= ( params.degrees * deg2radians ) % ( 2 * Math.PI );
				while ( theta < 0 ) {
					theta += 2 * Math.PI;
				}
				var costheta	= Math.cos( theta );
				var sintheta	= Math.sin( theta );
				
				// vars for the matrix
				var m11 = parseFloat( costheta ).toFixed( 8 );
				var m21 = parseFloat( sintheta ).toFixed( 8 );
				var m12 = parseFloat( -sintheta ).toFixed( 8 );
				var m22 = parseFloat( costheta ).toFixed( 8 );
				
				// the matrix string
				//     |  costheta  -sintheta  |
				// m = |  sintheta   costheta  |
				var matrix = 'matrix(' + m11 + ', ' + m21 + ', ' + m12 + ', ' + m22 + ', 0, 0);';
				
				// if IE filters are present
				if ( this.filters ) {
					// rotate in IE is a little bit more complicated, because the default rotation is not a centered rotation,
					// this is why the script below is made of four parts :
					// 1°. saving initial params
					// 2°. calcultating the translation
					// 3°. rotating the object
					// 4°. translating the object from the new basis to the old one
					
					// 1°. saving initial params
					if ( !this._rotationInitial ) {
						this._rotationInitial = {
							width	: Number( parseFloat( $this.width( ) ).toFixed( 8 ) ),
							height	: Number( parseFloat( $this.height( ) ).toFixed( 8 ) ),
							left	: Number( parseFloat( $this.css( 'left' ) ).toFixed( 8 ) ),
							top		: Number( parseFloat( $this.css( 'top' ) ).toFixed( 8 ) )
						};
					}
					
					// 2°. calculations to get the translation vector
					var w				= this._rotationInitial[ 'width' ];
					var h				= this._rotationInitial[ 'height' ];
					
					var original_x		= w / 2;
					var original_y		= h / 2;
					
					var a, b;
					
					if ( (theta>=0) && (theta<=Math.PI/2) ) {
						a = w;
						b = h;
					} else if ( (theta>Math.PI/2) && (theta<=Math.PI) ) {
						a = h;
						b = w;
						theta -= (Math.PI / 2);
					} else if ( (theta>Math.PI) && (theta<=3*Math.PI/2) ) {
						a = w;
						b = h;
						theta -= Math.PI;
					} else {
						a = h;
						b = w;
						theta -= (3 * Math.PI / 2) ;
					}
					
					var original_left	= this._rotationInitial[ 'left' ];
					var original_top	= this._rotationInitial[ 'top' ];
					
					var a1				= b * Math.sin( theta );
					var b1				= b * Math.cos( theta );
					
					var d				= 0.5 * Math.sqrt( (a*a) + (b*b) );
					var psi				= Math.acos( 0.5 * a / d );
					
					var a2				= d * Math.cos( theta + psi );
					var b2				= d * Math.sin( theta - psi );
					
					var new_x			= a1 + a2;
					var new_y			= b1 + b2;
					
					var dx				= original_x - new_x;
					var dy				= original_y - new_y;
					
					var new_left		= Math.round( parseFloat( original_left + dx ) ) + 'px';
					var new_top			= Math.round( parseFloat( original_top + dy ) ) + 'px';
					
					// 3°. rotate the object
					
					this.style.filter = 'progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\');';
					this.filters.item( 0 ).M11 = costheta;
					this.filters.item( 0 ).M21 = sintheta;
					this.filters.item( 0 ).M12 = -sintheta;
					this.filters.item( 0 ).M22 = costheta;
					
					// 4°. translate the object from the new basis to the old one
					
					$this.css( {
						left: new_left,
						top: new_top
					});
					
				// else for Safari, Firefox, Opera, etc
				} else {
					this.setAttribute( 'style',	'position:absolute; -moz-transform: ' + matrix + 
												'; -webkit-transform: ' + matrix + 
												'; -o-transform: ' + matrix + '' );		
				}
			});
		},
		
		
		/*
		 * Creates a rotation animation.
		 * $( 'xxx' ).rotation( 'animatedRotation', { [ initial_degrees ], [ current_degrees ], [ final_degrees ], [ duration ], [ easing ], [ callback ] } )
		 * initial_degrees	: a number indicating the initial object angle
		 * current_degrees	: a number indicating if the animation has already began (or notionally)
		 * final_degrees	: a number indicating the final object angle (the angle to reach)
		 * duration			: a number determining how long the animation will run
		 * easing			: a string indicating which easing function to use for the animation
		 * callback			: a function to call once the animation is complete
		 */
		animatedRotation : function( params ) {
		
			if ( params.initial_degrees && !params.current_degrees )
				params.current_degrees = params.initial_degrees;
		
			// default params
			var defaults = {
				initial_degrees	: 0,
				current_degrees	: 0,
				final_degrees	: 0,
				duration		: 0,
				easing			: 'swing',
				callback		: function( ) { }
			};
			
			// extends the params
			var params = $.extend( defaults, params );
			
			return this.each( function( ) {
				var ar = new _AnimatedRotation( this, params );
			});
		},
		
		
		/*
		 * Creates an infinite rotation animation.
		 * $( 'xxx' ).rotation( 'infiniteAnimatedRotation', { [ initial_degrees ], [ current_degrees ], [ speed ] } )
		 * initial_degrees	: a number indicating the initial object angle
		 * current_degrees	: a number indicating if the animation has already began (or notionally)
		 * speed			: a number indicating of how much degrees the object have to be rotated
		 */
		infiniteAnimatedRotation : function( params ) {
		
			if ( params.initial_degrees && !params.current_degrees )
				params.current_degrees = params.initial_degrees;
		
			// default params
			var defaults = {
				initial_degrees	: 0,
				current_degrees	: 0,
				speed			: 0
			};
			
			// extends the params
			var params = $.extend( defaults, params );
			
			return this.each( function( ) {
				var iar = new _InfiniteAnimatedRotation( this, params );
			});
		}
		
	};
	
	
	/*
	 * intern class used for the animated rotation
	 */
	_AnimatedRotation = function( obj, params ) {
	
		// reference to this
		var _ref				= this;
	
		// the attributes
		this._$this				= $( obj );
		this._initial_degrees	= params.initial_degrees;
		this._current_degrees	= params.initial_degrees;
		this._final_degrees		= params.final_degrees;
		this._duration			= params.duration;
		this._easing			= params.easing;
		this._callback			= params.callback;
		this._step				= 0;
		
		// the methods
		this._process = function( ) {
			this._step++;
			
			this._current_degrees = ( this._easing == 'swing' ) ? this._swing( ) : this._linear( );
			
			this._$this.rotation( 'rotation', { degrees : this._current_degrees } );
			
			if ( Math.abs( this._current_degrees - this._final_degrees ) > 0 ) {
				setTimeout( function ( ) { _ref._process( ); }, 20 );
			} else {
				this._$this.rotation( 'rotation', { degrees : this._final_degrees } );
				this._callback( );
			}
		};
		
		this._linear = function( ) {
			return this._initial_degrees + ( 20 * this._step * ( this._final_degrees - this._initial_degrees ) / this._duration );
		};
		
		this._swing = function( ) {
			return ( ( -Math.cos( 20 * this._step * Math.PI / this._duration ) / 2 ) + 0.5 ) * ( this._final_degrees - this._initial_degrees ) + this._initial_degrees;
		};
		
		if ( ( this._duration != 0 ) && ( this._initial_degrees != this._final_degrees ) )
			setTimeout( function ( ) { _ref._process( ); }, 20 );
		
	}
	
	
	/*
	 * intern class used for the infinite animated rotation
	 */
	_InfiniteAnimatedRotation = function( obj, params ) {
	
		// reference to this
		var _ref				= this;
	
		// the attributes
		this._$this				= $( obj );
		this._initial_degrees	= params.initial_degrees;
		this._current_degrees	= params.initial_degrees;
		this._speed				= params.speed;
		
		// the methods
		this._process = function( ) {
		
			this._current_degrees += 20 * this._speed;
			if ( this._current_degrees > 360 ) {
				this._current_degrees = this._current_degrees - 360;
			} else if ( this._current_degrees < -360 ) {
				this._current_degrees = this._current_degrees + 360;
			}
			
			this._$this.rotation( 'rotation', { degrees : this._current_degrees } );
			
			setTimeout( function ( ) { _ref._process( ); }, 20 );
		};
		
		if ( this._speed != 0 )
			setTimeout( function ( ) { _ref._process( ); }, 20 );
		
	}


	$.fn.rotation = function( method ) {
    
		// method calling logic
		if ( methods[method] ) {
			return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.rotation' );
		}

	};


})( jQuery );

