(function(dom) {
	
	
	
	/**
	 *
	 * Extend Function object
	 *
	 */
	
	Function.prototype.bind = function(object) {
		var b = this;
		return function() {
			return b.apply(object, arguments);
		}
	};
	
	/**
	 *
	 * Gallery
	 *
	 * Animated gallery.
	 *
	 * @param root {HTMLElement} the root HTMLElement for a gallery.
	 *
	 *
	 */
	
	var SLIDE = '<div class="slide">' +
					'<a href="#" class="prev" rel="gallery-prev">Previous</a>' +
					'<a href="#" class="next" rel="gallery-next">Next</a>' +
					'<img src="{{src}}" alt="{{alt}}" />' +
					'<p class="caption">' +
						'<span class="description">' +
							'<a href="{{link}}">{{title}}</a> {{caption}}' +
						'</span>' +
						'<span class="toggle">' +
							'{{nr}} of {{total}}' +
							'<a href="#" rel="gallery-caption" data-alt="Show caption">Hide caption</a>' +
						'</span>' +
					'</p>' +
				'</div>';
	
	var Slideshow = function(root) {
		
		// set the root
		this.root = dom(root);
		this.root.bind('click', this.notify.bind(this));
		
		// set the default speed
		this.speed = 3000;
		
		// load the active
		var slide = this.root.find('.slides .active');
		this.showSlide(slide);
		
	};
	
	Slideshow.prototype = {
		constructor: Slideshow,
		notify: function(e) {
			
			//
			var node, prefix, rel, action;
			
			node = dom(e.target).closest('a');
			prefix = /gallery-/;
			
			if (node) {
				rel = node.attr('rel');
				
				if (prefix.test(rel)) {
					
					action = rel.replace(prefix, '');
					switch (action) {
						
						case 'show' : e.preventDefault();
							this.showSlide(node);
							break;
							
						case 'next' : e.preventDefault();
							this.showNextSlide(/* uses the active node */);
							break;

						case 'prev' : e.preventDefault();
							this.showPrevSlide(/* uses the active node */);
							break;
							
						case 'play' : e.preventDefault();
							this.togglePlay(node);
							break;
							
						case 'speed' : e.preventDefault();
							this.adjustSpeed(node);
							break;
							
						case 'caption' : e.preventDefault();
							this.toggleCaption(node);
							break;
						
						default: 
							break;
							
					}
				}
			}
			
		},
		updateSlide: function(node) {
			
			var nodes, nr, total;
			
			nodes = this.root.find('.slides li a');
			total = nodes.length;
			nr = 0;
			for (var i = 0; i < total; i++) {
				if (nodes[i] === node[0]) {
					nr = i + 1;
				}
			}
			
			var slide = this.root.find('.slide').hide();
			var content = {
				caption: node.find('span').html(),
				title: node.find('em').html(),
				link: node.attr('href'),
				src: node.attr('data-source'),
				alt: node.find('img').attr('alt'),
				total: total,
				nr: nr
			};
			
			// set the image
			slide.find('img').attr('src', content.src);
			slide.find('img').attr('alt', content.alt);
			
			// set the caption
			slide.find('.description a').html(content.title);
			slide.find('.description a').attr('href', content.link);
			slide.find('.description em').html(content.caption);
			
			// set the caption toggle
			slide.find('.toggle em').html(content.total);
			slide.find('.toggle var').html(content.nr);
			
			slide.fadeIn();
			
		},
		showSlide: function(node) {
			
			this.reset();
			
			this.root.find('.slides li a').removeClass('active');
			node.addClass('active');
			
			this.updateSlide(node);
			
		},
		showNextSlide: function() {
			
			// get the current slide and show one higher
			var nodes = this.root.find('.slides li a');
			var n, l = nodes.length;
			for (var i = 0; i < l; i++) {
				if (nodes.eq(i).hasClass('active')) {
					n = (i === l - 1) ? 0 : i + 1;
					break;
				}
			}

			// get the node to show
			var node = nodes.eq(n);
			var slides = this.root.find('.slides ol');
			
			// if slides.left - node.left > fits in the gallery then move slides to fit into view
			var La = node.closest('li').position().left;
			var Lb = slides.position().left;
			var W = 500;
			
			if (l > 7) {
				if (n === 0) {
					slides.stop().animate({ left : 20 - La });
				} else if (Lb + La > W) {
					slides.stop().animate({ left : W - La });
				}
			}
			
			// show the appropriate slide
			this.showSlide(node);
			
		},
		showPrevSlide: function() {
			
			// get the current slide and show one higher
			var nodes = this.root.find('.slides li a');
			var n, l = nodes.length;
			for (var i = 0; i < l; i++) {
				if (nodes.eq(i).hasClass('active')) {
					n = (i === 0) ? l - 1 : i - 1;
					break;
				}
			}
			
			//
			var node = nodes.eq(n);
			var slides = this.root.find('.slides ol');
			
			// if slides.left - node.left > fits in the gallery then move slides to fit into view
			var La = node.closest('li').position().left;
			var Lb = slides.position().left;
			var W = 500;
			
			// if it's the first one, show the last slide on the right
			if (l > 7) {
				if (n === l - 1) {
					slides.stop().animate({ left : W - La });
				} else if (Lb + La < 0) {
					slides.stop().animate({ left : 20 - La });
				}
			}
			
			// show the appropriate slide
			this.showSlide(node);
			
		},
		toggleCaption: function(node) {
			
			// toggle the caption
			var caption = node.closest('.caption');
			var description = caption.find('.description');
			
			if (description.is(':visible')) {
				description.hide();
				caption.removeClass('lock');
				caption.css({ width: 100 });
			} else {
				description.show();
				caption.addClass('lock');
				caption.css({ width: 'auto' });
			}
			
			// switch the label
			var alt = node.attr('data-alt');
			var html = node.html();
			
			node.html(alt).attr('data-alt', html);
			
		},
		togglePlay: function(node) {
			
			var alt = node.attr('data-alt');
			var html = node.html();
			
			if (!this.running) {
				node.addClass('pause');
				this.start();
			} else {
				node.removeClass('pause');
				this.stop();
			}
			
			node.html(alt).attr('data-alt', html);
			
		},
		updateSpeed: function(node) {
			
			var nodes = this.root.find('.controls ol li').removeClass('active');
			for (var i=0; i < nodes.length; i++) {
				nodes.eq(i).addClass('active');
				if (nodes.get(i) === node.closest('li').get(0)) {
					break;
				}
			}
			
		},
		adjustSpeed: function(node) {
			this.speed = node.attr('data-speed');
			this.updateSpeed(node);
			this.reset();
		},
		reset: function() {
			if (this.running) {
				this.stop();
				this.start();
			}
		},
		start: function() {
			this.running = setTimeout(this.showNextSlide.bind(this), this.speed);
		},
		stop: function() {
			clearInterval(this.running);
			delete this.running;
		}
	};
	
	
	
	/**
	 *
	 * Initialise the slideshow
	 *
	 *
	 *
	 */
	
	dom(function() {
		
		// instantiate any galleries on the page
		var nodes = dom('.gallery-slideshow');
		if (nodes.length) {
			for (var i=0; i < nodes.length; i++) {
				new Slideshow(nodes.get(i));
			}
		}
		
		dom('[rel="slideshow"]').bind('click', function(e) { e.preventDefault();
			
			// get the underlying page
			$.get(this.getAttribute('href'), function(response) {
				
				// remove existing dialogs
				dom('.gallery-dialog').remove();
				dom('#overlay').remove();
				
				// create a new DOM node
				var dialog =  dom('<div class="gallery-dialog"><a href="#" class="close">Close</a>' + response + '</div>');
				var overlay = dom('<div id="overlay"></div>');
				
				// append the dialog and overlay
				dom('#content').append(dialog);
				dom('body').append(overlay);
				
				// bind close behaviour
				dialog.find('a.close').bind('click', function(e) { e.preventDefault();
					overlay.fadeOut();
					dialog.fadeOut();
					slideshow.stop();
				});
				
				overlay.bind('click', function() { 
					overlay.fadeOut();
					dialog.fadeOut();
					slideshow.stop();
				});
				
				
				// position the node
				var top = dom(window).scrollTop() + 100;
				var left = '50%';
				var margin = '0 0 0 -310px';
				
				// position the dialog
				dialog.css({ top: top, left: left, margin: margin });
				overlay.css({ height: dom(document).height() });
				
				// create an instance of slideshow
				var slideshow = new Slideshow(dialog);
				
			});
		});
		
		
	});
	
	
})(jQuery);
