/**
 * Flipbook animator.
 * Animates a sprite by changing the background image.
 */
 
//global timer (should not be but was made global for transitions)
var timer = 0;
//the current frame that is animating
var currentAnimation = 0;
//global settings store
var settings = [];
//the interval between animations
var interval = 1000;
//the index of the current image
var index = -1;
//the total number of images
var totalImages = 0;
//frame counts
var frameCounts = [];

(function( $ ){
	//is the device a mobile site
	var isMobile = function() {
		var ua = navigator.userAgent.toLowerCase();
		var isApple = ua.match(/(iphone|ipod|ipad)/);
		var isAndroid = ua.indexOf("android") > -1;
		
		return isApple || isAndroid;
	};	

	var Flipbook = function(el, options, callback) {
		/*** session variables ***/
		//save the current context
		this.context = $(el);
		//the id of the current animation
		this.animationId = 0;
		//the current frame the animation is on
		this.currentFrame = 0;
		this.mobileImages = [];
		this.reverse = false;
		//the frame count for the current image
		this.frameCount = 0;
		//callback function
		this.callback = callback;
		
		/*** static variables ***/
		this.active = 'flipbook-active';
	
		//initialize the animation
		this.init = function()
		{
			//length of the interval between each frame in ms
			interval = 1000 / settings.fps;
			
			//set the total number of images
			totalImages = settings.images.length;
				
			if (isMobile())
				this.initMobile();
			else {
				this.initStandard();
			}
		};	
		
		/***** MOBILE *****/
		
		//initialize the animation
		this.initMobile = function()
		{					
			//populate the frame count array
			for (var key in settings.images) {
				frameCounts.push(settings.images[key].count);
			}
			
			//initiate the first animation
			this.nextMobile();
		};
		
		//cache all the images for the animation
		this.cacheImages = function(count) {
			var obj = this;
			var cachedImage = new Image(100,100);
			//get the url
			var src = this.getUrl(count);
			
			//once the image is finished loading load the next one (should refactor to be asynchronous)
			$(cachedImage).load(function(){
				delete cachedImage;
				obj.mobileImages.push(src);
				count = obj.stepMobile(count);
				//if there is another image in the sequence then load it
				if (count > 0 && count <= obj.frameCount)
					obj.cacheImages(count);
				//else start the animation
				else
					obj.animateMobile();
			});
			
			cachedImage.src = src;
		};
		
		//get the url for the given index in the animation sequence
		//format name_##.xyz where ## is padded with 0s to the max size
		this.getUrl = function(num) {
			var baseUrl = settings.images[index].url;
			var url = baseUrl.substring(0, baseUrl.lastIndexOf('.'));
			
			//padd with zeros
			var length = ('' + this.frameCount).length;
			var numStr = '' + num;
			while (numStr.length < length) {
				numStr = '0' + numStr;
			}
			
			url += '_' + numStr + baseUrl.substring(baseUrl.lastIndexOf('.'), baseUrl.length);
			return url;			
		};
		
		//reverse the current animation
		this.reverseMobile = function() {
			this.reverse = true;
			
			//clear the interval
			window.clearInterval(timer);
			
			//get the frame count
			this.frameCount = frameCounts[index];
			
			//queue up the images
			this.mobileImages = [];
			for (var i = 1; i <= this.frameCount; i++) {
				this.mobileImages.push(this.getUrl(i));
			}
			
			//start the animation
			this.animateMobile();		
		};
		
		//go to the next animation
		this.nextMobile = function() {	
			this.reverse = false;
			
			//clear the interval
			window.clearInterval(timer);
			
			//increment the index
			index++;
			if (index >= totalImages)
				index = 0;
				
			//get the frame count
			this.frameCount = frameCounts[index];
				
			//add the image if one does not exist
			if ($('img', this.context).length == 0)
				$(this.context).append("<img src='" + this.getUrl(1) + "' />");
				
			//preload all of the images
			this.mobileImages = [];
			this.cacheImages(this.frameCount);	
		};
		
		//animate the images for a mobile browser
		this.animateMobile = function() {
			if (this.mobileImages.length > 0) {
				var obj = this;
				var src = this.mobileImages.pop();
				
				//display the next frame
				$('img', this.context).attr('src', src);
								
				//if at the end of the animation
				if (this.mobileImages.length == 0) {
					//clear the interval
					window.clearTimeout(timer);
					
					//fire the callback
					if(typeof callback == 'function'){
						callback.call(this.context);
					}
				//else, queue up the next call
				} else {
					timer = window.setTimeout(function() {
						obj.animateMobile();
					}, interval);
				}
			}
		};
		
		//get the next image in the sequence
		this.stepMobile = function(num) {
			if (this.reverse)
				return num + settings.step;
			else
				return num - settings.step;
		};
		
		
		/***** STANDARD *****/
		
		//init for non-mobile browsers
		this.initStandard = function() {
			var obj = this;
			
			index = totalImages;
						
			//cache the images on the screen
			for(var i = (totalImages - 1); i > -1; i--) {
				var value = settings.images[i];
				var cachedImage = new Image(100,100);
				//after the image is cached then create
				$(cachedImage).load(function(url, count){	
					//create a div with the url as the background image
					var div = $('<div></div>');
					div.css('background', 'url("' + url + '") no-repeat 0px 0px');
					div.css('width', '100%');
					div.css('height', '100%');
					div.css('position', 'absolute');
					obj.context.append(div);
					
					//push the frame count
					frameCounts.push(count);
					
					//if this is the first slide then start the animation
					if (i == 0) {
						//start the animations
						obj.nextStandard();
					}
					
					//delete the image object used for the cache
					delete cachedImage;
				}(value.url, value.count));
				//load the image in first to make sure it is cached
				cachedImage.src = value.url;
			}
		};
		
		//reverse the current animation
		this.reverseStandard = function() {
			this.reverse = true;
			
			//set the current animation
			this.animationId = ++currentAnimation;
					
			//get the current image
			var image = $('.' + this.active);
			
			//set the frame count
			this.frameCount = frameCounts[index];
			
			//number of frames minus the current frame
			var frames = this.frameCount - 1;
		
			var position = (frames * settings.width * -1) + 'px ' + (frames * settings.height * -1) + 'px';
			
			//set the position
			$(image).css('background-position', position);
			
			//start the animations
			this.animate();		
		};
		
		//go to the next animation
		this.nextStandard = function() {	
			this.reverse = false;
			
			//set the current animation
			this.animationId = ++currentAnimation;
			
			//set the new index
			index--;
			if (index < 0)
				index = totalImages - 1;
			
			//set the frame count
			this.frameCount = frameCounts[index];
		
			//get the old image
			var oldImage = $('.' + this.active);
			
			//get the new image
			var newImage = $('div:eq(' + index + ')', this.context);
			
			//set the starting position of the animation
			var position = '0px 0px';
			
			//set the position
			$(newImage).css('background-position', position);
			
			//show the new image
			$(newImage).show(0);	
			
			//hide the old image
			$(oldImage).hide(0);
				
			//add the active class to the new image
			$(newImage).addClass(this.active);
			
			//remove the active class from the old image
			$(oldImage).removeClass(this.active);
						
			//start the animations
			this.animate();		
		};
		
		//Animate one step of the flipbook
		this.animate = function() {		
			if (this.animationId == currentAnimation) {			
				var obj = this;
				
				//get the current image
				var image = $('.' + this.active);
								
				//get the current x/y position of the background in the format "Xpx Ypx"
				var pos = $(image).css('background-position');
				
				//old versions of IE use non-standard css so we have to use this special hack to get the x/y position
				if (pos == 'undefined' || pos == null) {
					pos = [$(image).css("background-position-x"), $(image).css("background-position-y")];
				//else, split the background position
				} else {
					pos = pos.split(" ");
				}
				
				//get the X position
				var x = parseFloat(pos[0]);
				//get the X unit
				//var xUnit = pos[0].replace(/[0-9-.]/g, "");
				var xUnit = 'px';
				
				//get the Y position
				var y = parseFloat(pos[1]);
				//get the Y unit
				//var yUnit = pos[1].replace(/[0-9-.]/g, "");
				var yUnit = 'px';
				
				//format the new position
				var newPosition = this.step(x, settings.width) + xUnit + ' ' + this.step(y, settings.height) + yUnit;
				
				//set the new position
				$(image).css('background-position', newPosition);
				
				//increment the current frame
				this.currentFrame++;
				
				//if at the end of the animation then fire the callback
				if (this.currentFrame == (this.frameCount - 1)) {
					//clear timer
					window.clearTimeout(timer);
					
					//call callback function
					if(typeof this.callback == 'function'){
						this.callback.call(this.context);
					}
				//else, queue up the next call
				} else {
					timer = window.setTimeout(function() {
						obj.animate();
					}, interval);
				}
			}
		};
		
		//shift the position the given step size based on the animation direction
		this.step = function(position, stepSize) {
			if (this.reverse)
				return position + stepSize;
			else
				return position - stepSize;
		};
	};
	
	//create the jquery plugin
	$.fn.flipbook = function (action, options, callback) {
		//override the default settings
		if (settings.length == 0)
			settings = $.fn.flipbook.defaults;
        var opts = $.extend(settings, options);
		//create a new instance of the object for each element the selector brings back
        return this.each(function () {
            var instance = new Flipbook($(this), opts, callback);
			
			//handle the action
			switch(action) {
				case 'init':
					instance.init();
					break;		
				case 'next':
					if (isMobile())
						instance.nextMobile();
					else 
						instance.nextStandard();
					break;	
				case 'reverse':
					if (isMobile())
						instance.reverseMobile();
					else 
						instance.reverseStandard();
					break;
			}
        });
    }
	
	//default settings for flipbook
	$.fn.flipbook.defaults = {
		images		: [],			//list of image urls
		height		: 0,			//image height to change on each step
		width		: 0,			//image width to change on each step
		count		: 0,			//number of frames
		fps			: 0,			//frames per second
		step		: 1,			//number of frames to step over on each step
		reverse		: false			//should the animation run in reverse?
    };
})( jQuery );
