//////////////////////////// INFINITE SCROLL
function InfinateScroller(args){ 
    jQuery.extend( this, args || {} );
	this.rebuild_index();
	this.dbg_draw( "init" );
}
InfinateScroller.prototype = {
    on_shuffle: function(){}, 
    create_item: function(){},
    center: 1,
    visible: 3,
    peripheral: 2,
    items:[],
    index: [],
	DIR_LEFT: -1,
	DIR_RIGHT: 1,
    clone_items: function( times ){
		var len = this.items.length;
        for(var i=0; i< times ; i++){
			for(var j=0; j < len; j++){
				this.items.push($.extend({}, this.items[j]) );
				this.index.push(this.index[j]);
			}
        }
    },

    move: function move( dir ){
        this.dbg_draw( "before move dir: "+dir+ " >" );
        this.center += dir;
		
        //need to ensure the center is within the bounds
        if(this.center < this.center_min ){
            var dist= Math.abs(this.center_min - this.center);
            this.prepend_el( dist );
            this.center = this.center_min;
        }
        if(this.center > this.center_max){
            var dist= Math.abs(this.center_max - this.center);
            this.append_el( dist );
            this.center = this.center_max;
        }
        this.dbg_draw( "after move dir: "+dir+ " >" );
    },
    prepend_el: function( amount ){
        amount = amount || 1;
        for(var i=0; i < amount; i++){
            this.items.unshift(this.items.pop());
            this.index.unshift(this.index.pop());
        }
		this.dbg_draw("    prepend_el: "+amount+ "  <<  ");
     }, 
    append_el: function( amount ){
        amount = amount || 1;
        
        for(var i=0; i < amount; i++){
            this.items.push(this.items.shift());
            this.index.push(this.index.shift());
        }
		this.dbg_draw("    append_el: "+amount+'   >>   ');
    },
	
	rebuild_index: function(){
	
		this.min_items = this.visible + (this.peripheral*2);
		var item_len = this.items.length;

		// build item index
		this.index = [];
		for(var i=0; i < item_len; i++){
			this.index[i] = i;
		}

		// need to check if we have enough images to scroll
		if(this.items.length > 0 && this.items.length < this.min_items){
			// number of times to duplicate the input,
			// cant just add a few items, must duplicate whole series
			// otherwise when we shift the order would get messed up
			var deficit = Math.ceil( this.min_items / this.items.length)-1;
			this.clone_items( deficit );
		}
		
			// the min index for the center, if its less than these we need to shift
			this.center_min = Math.ceil((this.visible/2)) + this.peripheral;
			this.center_max = this.items.length - this.center_min;

		this.log(JSON.stringify({
			v:this.visible, 
			p:this.peripheral, 
			mi: this.min_items, 
			sil: item_len, 
			il: this.items.length, 
			cmin: this.center_min,
			cmax: this.center_max
		}));
	},

    log:function(){ 
		if(this.debug){
			console.log.apply(console, arguments); 
		}
	},
    debug: false,
    dbg_draw: function( info ){
        var tmp = [].concat(this.index), cx = this.center;
       
 
       if(cx-1 > -1)
           tmp[cx-1] = '[' + tmp[cx-1];
       if(cx+1 < tmp.length)
           tmp[cx+1] = tmp[cx+1]+']';
       this.log((info || "") + ' '+ tmp.join('-'));
    },
	test: function(){		
		var scroller = new InfinateScroller( {
			items: [1,2,3,4,5],
			peripheral: 1, 
			debug: true
		});
		
		for(var i=0; i < 10;i++)
			scroller.move(1);
		for(var i=0; i < 10;i++)
			scroller.move(-1);
		
		scroller = new InfinateScroller( {
			items: [1],
			visible: 5,
			peripheral: 1, 
			debug: true
		});
		
		scroller.move(7);
		scroller.move(-14);
	}
}




function InfinateGallery(container, config){
	
	this.items=[];
	for(var i =0; i < config.images.length; i++)
			this.items[i] = {url: config.images[i].url, item:config.images[i]};
	this.images = this.items;
	
	delete config.images;
		
    InfinateScroller.call(this,config);
	
	//container.css({ overflow:'hidden' });
	this.ct = container;
	this.drag_ct = $('<div class="gallery-drag-ct" ></div>').appendTo(container);
	this.el =  $('<div class="gallery-drag-el"></div>').appendTo(this.drag_ct);
	
	this.drag_ct.scroll(function(e){
		
		e.preventDefault();
		return false;
	});
	this.ct.scroll(function(e){
		
		e.preventDefault();
		return false;
	});
	
	var userAgent = navigator.userAgent.toString().toLowerCase();
	if ((userAgent.indexOf('safari') != -1) && !(userAgent.indexOf('chrome') != -1)) {
		/*body.css({
			'position':'relative',
			'height':'100%',
			'overflow-x':'hidden'
		});*/
	}
	
	var _this = this;
	this.nav_left = $( '<div id="gal_left">&lt;</div> ').appendTo(container).click(function(){ _this.move(_this.DIR_RIGHT); });
	this.nav_right = $( '<div id="gal_right">&gt;</div> ').appendTo(container).click(function(){ _this.move(_this.DIR_LEFT); });

	this.els = $().add( this.drag_ct) .add(this.nav_left).add(this.nav_right );
	this.els.hide();
	this.initDrag();
	this.preload();
	
}
    

InfinateGallery.prototype = $.extend({},InfinateScroller.prototype,{
	thumb:{ width: 600, height:480},
	anim:{ duration:1500, name:'easeInExpo' },
	offset: 0.5 ,
	dynamic_sizing:true,
	gutter: 25,
	
	//////////////////////////////////////////////
	////// COORDS
	
    x: function ( x ){  
		if(x){
			this.el.css('left', x+'px'); 
		}else return parseInt(this.el.css('left').replace('px',''));
	},
	height: function(){
		return this.dynamic_sizing ? this.ct.height() : this.thumb.height;
	},

	img_offset: function(index){
		if(!this.dynamic_sizing){
			var width = (this.thumb.width + this.gutter);
			var inx = index-1; // index is zero based;
			
			var x = ((width * inx) + (width*this.offset)) * -1; 
			return x;
		}else{
			var off = 0;
			for(var i=0; i <= index; i++){
				off += this.items[i]._img.width() + this.gutter;
			}
			off -= (this.ct.width() / 2 ) +( (this.items[index]._img.width() ) /2) ;  // center img
			return off*-1;
		}
	},
	
	/////////////////////////
	/// EVENTS
	change: function(){},
	before_change: function(){},
	/// default empty handler, should be overriden by config
	click: function(item){},
	
	///////////////////////////////////////////
	//// DOM MANIP
	move: function(amount, noanim){
		try{
			this.before_change(this.items[this.center]);
		}catch(e){
			this.log("before_change error: "+e);
		}
		InfinateScroller.prototype.move.apply(this,arguments);
		if(this._preload_complete){
			if(noanim){
				this.x(  this.img_offset( this.center ) );
			}else{			
				this.el.draggable({disabled: true});
				var _this = this;
				
				this.el.stop().animate({ 
					left:  this.img_offset( this.center ) + 'px' },
					this.anim.duration,
					this.anim.name, 
					function(){ 
						_this.el.draggable({disabled:false});
						_this.change( _this.items[_this.center].item );
					}
				);
			}
		}
	},
	append_el: function( amount ){
		for(var i=0; i< amount; i++){
			var img = this.items[i]._img; 
			if(!img)continue;
			var _this = this;
			img.appendTo(_this.el);
			_this.x( _this.x() + img.width() + _this.gutter );
		}
		InfinateScroller.prototype.append_el.apply(this,arguments);
	},
	prepend_el: function( amount ){
		var total = 0;
		for(var i=0; i< amount; i++){
			var img = this.items[ this.items.length - i - 1]._img; //  len - i == go backwards, - 1 coz index is zero based
			if(!img)continue;
			img.prependTo(this.el);
			total += img.width() + this.gutter;
		}
		var bf = this.x();
		this.x( this.x() - total );
		InfinateScroller.prototype.prepend_el.apply(this,arguments);		
	},
	
	//////////////////////////////////////////////////////////////
	//////// PRELOADING
	
	_preload_complete:false,
	preload: function(){
		var _this = this;
		this.hide();
		if(this.items.length == 0) return;
		this.move(0, true);
		for(var cfg,j=0;  cfg = this.items[j]; j++){
			this.items[j]._img = $('<img style="display:none" />')
				.appendTo( this.el )
				.css('margin-left', this.gutter+'px')
				.load(this.imgLoaded)
				.data('infinate_config', this.items[j])
				.data('infinate_gallery', this)
				.attr('data-infinate-index', this.index[j])
				// make images unselectable: http://stackoverflow.com/questions/2700000/how-to-disable-text-selection-using-jquery
				.attr('unselectable', 'on')
               .css({
                   '-moz-user-select':'none',
                   '-webkit-user-select':'none',
                   'user-select':'none',
				   'cursor':'pointer'
               }).each(function() {this.onselectstart = function() { return false; };})
			   .click( this.click_handler() )
			   .bind('dragstart', function(event) { event.preventDefault(); })
			   
			   // reload img, this forces the load event to fire (if it was cached, the image might have 
				// fire load event before we added the event listener above )
				.attr('src', this.items[j].url)
				.attr('src','')
				.attr('src', this.items[j].url);
							
		}
	},
	
	_img_loaded:0,
	imgLoaded: function(){
		
		var img = $(this),
			gal = img.data('infinate_gallery'),
			height = gal.height();
			img.attr('data-original-height',img.height());
			img.attr('data-original-width',img.width());	

		if(img.data('infinate_config').loaded) return;
			
		if(gal.dynamic_sizing){
			gal.resizeImage( img );
		}else{
			img.height( gal.thumb.height ).width(gal.thumb.width);
		}
		gal.drag_ct.width( gal.drag_ct.width()  + img.width() + (gal.gutter*2.1) ); // gutter * 2 for ie double margin bug, * .1 for some extra space to be safe
		img.fadeIn()
			.data('infinate_config').loaded = true;
		gal._img_loaded++;
		gal.is_preload_complete();
	},
	
	is_preload_complete: function(){
		if(!this._preload_complete && this.items.length == this._img_loaded){
			this._preload_complete = true; 
			this.move(0, true);
			if(!this._visible){
				this.show();
			}
		}
	},
	
	/////////////////////////////////////////////
	////// VISIBILITY
	
	hide: function( cb ){
		
		this.els.hide();
		this._visible = false;
		if(cb) return cb();
	},
	show: function( cb ){
		this.els.show();
		
		this._visible = true;
		if(cb) return cb();
	},
	_visible: false,
	
	////////////////////////////////////////////////////////////////////////
	//// CLICK HANDLER
	
	/// click flag, it defaults to true, drag sets it to false
	is_click: true,
	/// image click handler
	click_handler: function(){
		var _this = this;
		return (function(e){ 
			e.preventDefault(); 
			if(_this.is_click){
				_this.click($(this).data('infinate_config').item);
			}else{
				// drag set this to false, so reset it so that next time 
				//the user clicks it will fire, if they drag it will be disabled again.
				_this.is_click = true;
			}
		});
	},
	
	////////////////////////////////////////////////////
	///// DRAG
	min_swipe_dist: 100, // min drag dist that will trigger swipe (px)
	max_swipe_dist: 300, // max dist before swipe will automatically occur (drag event is cancel)
	min_click_dist: 20, // if the user drags less than this dist it will be a click
	
	initDrag : function(){
		var start, _this = this,revert = true;
		this.el.draggable({ 
			axis: 'x',  // limit to x only scrolling
			disabled: false,  
			start: function(event, ui){ 
				_this.el.stop();
				start = _this.x(); 
				revert = true;
				_this.is_click=false;
			},
			drag:function(event, ui){
				var end = _this.x(),
					dist = Math.abs(Math.abs( Math.max(start,end) ) - Math.abs( Math.min(start,end) ));

				if(dist >= _this.max_swipe_dist){ // its a swipe
						var dir =  (start < end) ?   _this.DIR_LEFT : _this.DIR_RIGHT; 
						setTimeout(function(){ _this.move(dir); },30);
						revert = false;
						start = end; // so stop hadler doesnt move
						return false; // false to cancel drag
				}
			}
			,stop: function(event, ui) {  
				_this._drag=false;
				var end = _this.x(),
					dist = Math.abs(Math.abs( Math.max(start,end) ) - Math.abs( Math.min(start,end) ));
				if(dist >= _this.min_swipe_dist){ // its a swipe
					var dir =  (start < end) ?   _this.DIR_LEFT : _this.DIR_RIGHT; 
					_this.move(dir);
				}else if(revert){ // revert to original pos ( center 0 )
					if(dist < _this.min_click_dist){
						_this.is_click = true;// its a click, this gets reset by click_handler
					}
					_this.move(0); // revert
				}
			}
		});
	},
	
	///////////////////////////////////////////////////
	//// OPTIONS
	
	option: function( name, val ){
	
		if(this[name]) return this[name](val);
		
		name = "opt_"+name;
		
		if(this[name]) return this[name](val);
		
		return "gallery - unknown option: "+name;
	},
	
	load: function( images ){
		var _this = this;
		this.hide( function(){ 
			_this.x(0);
			_this.center = 0;
			_this._img_loaded = 0;
			_this._preload_complete = false;
			_this.el.empty(); 
			_this.items = [];
			for(var i =0; i < images.length; i++)
				_this.items[i] = {url: images[i].url, item:images[i]};
			_this.images = this.items;
			_this.rebuild_index();
			_this.preload(); 
		} );
	},
	
	resize: function(){
		if(this.dynamic_sizing){
			for(var i=0; i< this.items.length; i++){
				this.resizeImage(this.items[i]._img);
			}
		}
	},
	
	resizeImage: function(img){
		// hack for wierd bug in webkit where sometimes img.height() is returned as 0
		var h = Math.max(img[0].naturalHeight,img[0].height, img.height());
		if(h ==0) h =1;
		var scale =  this.height() / h;
		img.resizeImage({scale:scale});	
	},
	select: function( fn ){
		var _this = this,
			dir = 0, 
			min_dist = this.items.length, 
			center = this.center;
			
		$.each( this.items, function(index, val){
			if(fn(val)){
				// dist from center
				var dist =  index - center;
				
				if(dist == 0){ // center item is selected 
					dir = 0; // cancel move
					return true;// exit loop
				}
				
				if(Math.abs(dist) < min_dist){ 
					min_dist = Math.abs(dist);
					dir =  index - center;
				}
			}
		});
		if(dir !== 0)
			this.move(dir);
	}
	
});

InfinateGallery.generateDebugImages =function( w,h,amount, extra){
	var canvas= document.createElement("canvas"), ctx = canvas.getContext('2d');
	canvas.width = w;
	canvas.height = h;
	$(canvas).appendTo("body").hide();
	var images = [];
	for(var i =0; i < amount;i++){
		ctx.fillStyle = "#efefef";  
		ctx.fillRect(0,0,w,h);
		ctx.font = "100px Times New Roman";  
		ctx.fillStyle = "Black";  
		var dim = ctx.measureText(i.toString());
		ctx.fillText(
			i.toString(),  
			Math.round((w/2)-(dim.width/2)), 
			 Math.round( (h/2)-((dim.height||20)/2) )
		); 
		images.push($.extend({},extra || {}, { url: canvas.toDataURL(), name:i.toString() }));
	}
	return images;
}



jQuery.fn.gallery = function( config ){
	var args = arguments;
	return $(this).each(function(){
		var $el = $( this );
		if(!$el.data( 'gallery' )){
			$el.data('gallery', new InfinateGallery( $el, config ));
		}else{
			$el.data('gallery').option.apply($el.data('gallery'),args);
		}
	});
}
