/*
 *  La Boite v.1.1.0
 *  Copyright 2009, Gilles Cochez
 *  Released under the MIT, BSD, and GPL Licenses.
 *  More information: http://laboite.codeserenity.com/
 */
 
 /*
	Tested on: Firefox 2+, IE6+, Safari 3+, Chrome, Opera 9+
	Tested with: jQuery 1.3.2 and jQuery 1.4
*/

// closure
;(function($)
{
	var		
		scope = 'laboite', // plugin scope name
		ie = !$.support.opacity, // detect ie
		ie6 = ie && !window.XMLHttpRequest, // detect ie6
		v = {}, // hold current viewer data and options
		e = {}, // hold cached elements as jQuery objects
		x = {}, // hold the plugin's methods
		t; // interval holder for the slide show
		
	// plugin declaration
	x = $.fn.laboite = function(o)
	{
		// overwrite defaults with passed options
		o = $.extend({}, $.fn.laboite.defaults, o);
		
		// sort out our set of elements (search if not <a>s)
		if (this.nodeName && 'A' === this.nodeName.toUpperCase()) var set = $(this);
		else var set = $(this).find('a');
		
		// cache the element set and it's size
		$.extend(o, {
			set: set,
			size: set.size(),
			parent: $(this)
		});
		
		// sort options out for each trigger
		set.each(function() {
			$(this).data(scope, $(this).data(scope) ? $.extend({}, $(this).data(scope), o) : o);
		}).click(function(event) { 
			event.preventDefault();
			x.init($(this).blur());
		});
		
		// autostart?
		if (o.autostart) {
			setTimeout(function() {
				x.init(set.eq(0));
			}, o.autostart_delay);
		};
		
		// return self to allow chaining
		return this;
	};
	
	// extend our methods object
	$.extend(x,
	{
		// initialize the interface
		init: function(a) 
		{
			// get the viewer data and options
			v = a.data(scope);
			
			// if no cache get the set of elements and its size
			if (!v.cache) {
				v.set = v.parent.find('a').size() ? v.parent.find('a') : v.parent;
				v.size = v.set.size();				
			};
			
			// extend our viewer data holder
			$.extend(v, {				
				key: v.set.index(a), // cache key
				controls: (v.size > 1 && v.controls) ? true : false, // we check size and disable the controls if appropriate
				slideshow: (v.size > 1 && v.slideshow) ? true : false // we check the size and disable the slideshow if appropriate	
			});
			
			// if control make sure
			if (v.controls) {
				v.next = true;
				v.prev = true;
			} else {
				v.next = false;
				v.prev = false;
			};
			
			// build the markup
			x.markup();
			
			// extend so more with helper
			x.pcn(a);
			
			// handle window resizing
			x.win();
			
			// on init callback
			v.on_init(e.viewer);
			
			// load the content
			x.load();
		},
		
		// loading content function
		load: function()
		{
			var 
				url = v.cur.attr('href'), // extract url
				captxt = v.cur.attr('title') || v.cur.find('img').eq(0).attr('alt') || '', // extract caption text (if any)
				ow = e.content.css('overflow','hidden').innerWidth(), oh = e.content.innerHeight();
			
			// show loader
			x.fade(x.center(e.loader.show()), 1);
				
			// image?
			if (x.isimg(url)) {
				$(new Image()).load(function() {
					e.content.html($(this).load(function(){}).css('marginBottom', -3)).show();
					x.onload($(this), captxt, ow, oh);
				}).attr('src', url);
			}
			// id selector
			else if (x.isid(url)) {
				e.content.append($(url).clone());
				x.onload(e.content.children().show(), captxt, ow, oh);
			}
			// url in an iframe
			else if (v.iframe) {
				$('<iframe>').hide().css({width: v.iframe_width, height: v.iframe_height}).load(function() {
					x.onload($(this).show(), captxt, ow, oh);
				}).attr('src', url).appendTo(e.content);
			}
			// basic url
			else {
				$('<div>').load(url, function() {
					e.content.append(this);					
					x.onload(e.content.children(), captxt, ow, oh);
				});
			};
		},
		
		// on content load
		onload: function($e, captxt, ow, oh) 
		{
			// hide loader
			e.loader.hide();
			
			// update the caption element
			if (v.caption) e.caption.text(captxt).show();
			
			// update the stats element
			if (v.stats) e.stats.text(v.stats_pattern.replace(':current', v.key+1).replace(':total', v.size)).show();
			
			// collect dimension
			var nw = $e.outerWidth(), nh = $e.outerHeight();
			
			// display (after resizing if needed)
			if ((nw != ow) || (nh != oh)) x.resize(nw, nh, x.postload);
			else x.fade(e.content, 1, x.postload);
		},
		
		// executed after the content has fully loaded (inc the fading)
		postload: function() {
			e.viewer.find('*[id^="'+v.prefix+'"]').each(function() { 
				var id = $(this).attr('id').replace(v.prefix, '');
				if (v[id] && (!e[id].is(':visible') || e[id].css('opacity') < 1)) x.fade(e[id], 1);
			});
			x.bindkeys();
			v.on_load(e.viewer);
		},
		
		// bind keyboard keys
		bindkeys: function() {
			if (v.bind_keys)
			{
				$(document).bind('keyup keypress', function(oe) { // have to bind both to avoid browser issues
					if (!oe) var oe = window.event; // damn ie
					var code = oe.keyCode || oe.which,
						str = String.fromCharCode(code);
						
					// escape key?
					if (v.bind_escape && code == 27) x.close();
					
					// arrow keys?
					if (v.bind_arrows) {
						if (code == 37 && v.prev) x.prev();
						else if (code == 39 && v.next) x.next();
					};
						
					// Switch through result (todo: find shorter way to do it!)
					switch (str)
					{
						case v.close_key: x.close(); break;
						case v.next_key: if (v.next) x.next(); break;
						case v.prev_key: if (v.prev) x.prev(); break;
						case v.slide_key: if (v.slide) x.slide(); break;
						case v.reset_key: if (v.reset) x.reset(); break;
					};
				});
			};
		},
		
		// unbind keyboard keys
		unbindkeys: function() {
			$(document).unbind('keyup keypress');
		},
		
		// resize the content container
		resize: function(w, h, fn) {
			e.content.animate({width:w, height:h}, {
				easing: (v.easing && $.easing.def) ? v.easing : 'linear',
				step: function() {
					x.center(e.viewer);
				},
				complete: function() {
					x.fade(e.content, 1, fn);
					x.center(e.viewer);
					v.on_resize(e.viewer);
				}
			}, v.resize_speed);
		},

		// build the plugin's markup
		markup: function() 
		{
			// create and bind dim element (if needed)
			x.$('dim').click(v.bind_dim ? x.close : false);
			
			// create, fill and center our viewer element
			x.$('viewer').append(
				x.$('stats'),
				x.$('caption'),
				x.$('next', x.next),
				x.$('prev', x.prev),
				x.$('close', x.close),
				x.$('reset', x.reset),
				x.$('slide', x.slide),
				x.$('loader'),
				x.$('content').show()
			);

			// add to the document
			$('body').append(e.dim, e.viewer);
			
			// if ie6 set width and height for the dimmer element
			if (ie) e.viewer.addClass(v.prefix+'ie');
			if (ie6) x.ie6fix();
			
			// center the viewer
			x.center(e.viewer);
			
			// fade the dim in
			x.fade(e.dim, v.dim_alpha);
			
			// fade the viewer in
			x.fade(e.viewer, v.viewer_alpha);			
		},
		
		// set previous/current/next element
		pcn: function($e) {
			v.key = v.set.index($e);
			$.extend(v, {
				cur: v.set.eq(v.key), // set current element
				next: v.key+1 < v.size ? v.set.eq(v.key+1) : false, // next element
				prev: v.key-1 >= 0 ? v.set.eq(v.key-1) : false // previous element
			});
			if (!v.continuous && !v.next && t) x.stop();
			if (v.continuous && !v.next) v.next = v.set.eq(0); // if continuous set next
			if (v.continuous && !v.prev) v.prev = v.set.eq(v.size-1); // if continuous set prev
		},
		
		// hide what need to be hidden and load
		change: function() {
			/*
				need to do a smarter way here to reduce the footprint and hassle of updating
				if more elements are added etc...
			*/
			x.unbindkeys();
			if (v.next || e.next.is(':visible')) x.fade(e.next, 0, function(){ e.next.hide(); });
			if (v.prev || e.prev.is(':visible')) x.fade(e.prev, 0, function(){ e.prev.hide(); });
			if (v.caption) x.fade(e.caption, 0);
			if (v.close) x.fade(e.close, 0);
			if (v.slide) x.fade(e.slide, 0);
			if (v.reset) x.fade(e.reset, 0);
			if (v.stats) x.fade(e.stats, 0);
			x.fade(e.content, 0, function() {
				e.content.empty(); // empty content element
				x.load(v.cur);
			});
		},
		
		// next content
		next: function() {
			x.pcn(v.next);
			x.change();
			v.on_next(e.viewer);
		},
		
		// previous content
		prev: function() {
			x.pcn(v.prev);
			x.change();
			v.on_prev(e.viewer);
		},
		
		// return to the first record in the set
		reset: function() {
			x.pcn(v.set.eq(0));
			x.change();
		},
		
		// start/stop the slideshow
		slide: function() {
			if (!t) x.play();
			else x.stop();
		},
		
		// start slideshow
		play: function() {
			t = setInterval(x.next, v.slide_delay);
			e.slide.text(v.slide_stop).addClass(v.prefix+'stop');
		},
		
		// stop slideshow
		stop: function() {
			clearInterval(t);
			e.slide.text(v.slide_html).removeClass(v.prefix+'stop');
			t = false;
		},
		
		// close the lot
		close: function() {
			x.fade(e.dim, 0, function(){ $(this).remove(); });
			x.fade(e.viewer, 0, function(){ $(this).remove(); });
			$(window).unbind('resize scroll');
			v.on_close();
		},
		
		// take care of centering the loader and viewer
		center: function($e) 
		{
			// css options
			var opt = { position:'absolute' };
			
			// viewer element?
			if (e.viewer === $e) {
				if (v.viewer_x) opt.top = (($(window).height()-$e.outerHeight()) / 2) + $(document).scrollTop();
				if (v.viewer_y) opt.left = (($(window).width()-$e.outerWidth()) / 2) + $(document).scrollLeft();
			} else {
				if (v.loader_x) opt.top = (e.viewer.innerHeight()-$e.outerHeight()) / 2;
				if (v.loader_y) opt.left = (e.viewer.innerWidth()-$e.outerWidth()) / 2;
			};
			
			// set css and return element
			return $e.css(opt);
		},
		
		// handle windows resizing
		win: function() {
			$(window).bind('resize scroll', function() {
				if (ie6) x.ie6fix();
				x.center(e.viewer);
				x.center(e.loader);
			});
		},
		
		// fade function (might be temporary... or not...)
		fade: function($e, alpha, fn) {
			if (alpha == 0) return $e.fadeTo(v.fade_speed, 0, fn);
			else return $e.show().fadeTo(v.fade_speed, alpha, fn);
		},
		
		// dimmer element IE6 CSS fix
		ie6fix: function() {
			e.dim.css({
				position:'absolute',
				width: $(window).width(), 
				height: $(window).height(), 
				top: $(window).scrollTop(), 
				left: $(window).scrollLeft()
			});
		},
		
		// check if the str is an image
		isimg: function(str) {
			return v.image || str.match(/([^\s]+(\.(jpg|png|gif|bmp))$)/i);
		},
		
		// check for id selector
		isid: function(str) {
			return '#' === str.substring(0, 1);
		},
		
		// Create an element, set its id, hide it, cache it, fill it, click bind, hover bind and return as a jQuery element (speed up dev time and reduce footprint nicely)
		// as the id matches the settings to enable/disable elements we check them and act accordingly (no need to write elements that wont be used)
		$: function(id, clk) {
			if ('undefined' === typeof v[id] || v[id]) 
				return e[id] = $(document.createElement('div')).attr('id', v.prefix+id)
					.css({opacity:0, display:'none'}).html(v[id+'_html'] || '')
						.click('function' == typeof clk ? clk : function(){})
							.hover(!v.hover_class ? function(){} : function(){ $(this).addClass(v.prefix+'hover'); }, !v.hover_class ? function(){} : function(){ $(this).removeClass(v.prefix+'hover'); });
		}
	});
	
	// defaults settings
	$.fn.laboite.defaults = 
	{
		prefix: 'laboite-', // CSS prefix used to generate the id for the element created
		cache: true, // Cache the item list on first call (Disable if your list is dynamic!)
		continuous: false, // Enable or Disable continuous browsing
		autostart: false, // Enable or Disable autostart functionality
		autostart_delay: 0, // Autostart delay in milliseconds
		image: false, // set to true to force the plugin loading an url as an images (for dynamically generated images)
		slide: false, // enable|disable slideshow option
		slide_delay: 3500, // time to show a slide for (in slideshow mode)
		slide_html: '', // play label for slideshow button
		slide_stop: '', // stop label for the slideshow button (rever to original when stopped)
		slide_key: 's', // slideshow action hot key
		fade_speed: 300, // Fade in and out speed of elements
		resize_speed: 600, // Resizing animation speed
		dim_alpha: 0.75, // Dimmer element opacity level
		viewer_alpha: 1, // Opacity of the viewer element
		viewer_html: '', // Insert extra html elements into the viewer elements (elements to do rounded corner for example)
		viewer_x: true, // Center the viewer inside the browser on the x axis
		viewer_y: true, // Center the viewer inside the browser on the y axis
		loader_html: '', // Content to insert in the loader element
		loader_x: true, // Center the viewer inside the viewer elements on the x axis
		loader_y: true, // Center the viewer inside the viewer elements on the y axis
		stats: true, // Enable or Disable the stats displaying 
		stats_pattern: 'Item :current of :total', // Pattern used to build the stats string
		controls: true, // Enable or Disable the next and previous element
		next_html: '', // Content to insert in the next element
		next_key: 'n', // Next action hot key
		prev_html: '', // Content to insert in the previous element
		prev_key: 'p', // Previous action hot key
		reset:false, // Enable or Disable the reset element
		reset_html: '', // Content to insert in the reset element
		reset_key: 'r', // Reset action hot key
		close: true, // Enable or Disable the close button
		close_html: '', // Content to insert in the close element		
		close_key: 'c', // Close action hot key
		caption: true, // Enable or Disable caption message
		iframe: false, // Enable or Disable the use of iframe
		iframe_width: '700px', // Iframe width
		iframe_height: '500px', // Iframe height
		bind_dim: false, // Enable or Disable close function on the dimmer element
		bind_keys: true, // Enable or Disable hot keys binding (inc. escape and arrows too)
		bind_escape: true, // Bind the escape key to close the viewer
		bind_arrows: true, // Bind arrows for next and previous browsing
		hover_class: false, // Enable or Disable addition of the hover class on the viewer elements
		on_init: function(viewer) {}, // run after initialization is complete (the viewer element is passed)
		on_load: function(viewer) {}, // run after each content is loaded (the viewer element is passed)
		on_resize: function(viewer) {}, // run after the viewer has resized (the viewer element is passed)
		on_prev: function(viewer) {}, // run when the previous button is pressed (the viewer element is passed)
		on_next: function(viewer) {}, // run when the next button is pressed (the viewer element is passed)
		on_close: function() {}, // run after the viewer is closed
		easing: '' // jquery.easing settings (require the jquery easing plugin)
		//@end
	};
})(jQuery);
