/****************
 *
 * Views browsable flickr images and youtube videos in a "dia projector" overlay.
 *
 * Usage: $('a.dia-projector').({
 * });
 *
 * @author teemu@debyte.fi
 *
 ****************/

// Wrapper to ensure $ is jQuery.
(function($) {

	/**
	 * Makes clicks on matched elements open the dia projector and collects usable element data for show.
	 * @param options the options for the dia projector
	 */
	$.fn.diaProjector = function(options)
	{
		// Construct new plugin or use existing.
		var plugin = $(window).data('DiaProjectorPlugin');
		if (plugin == undefined)
		{
			plugin = new $.DiaProjectorPlugin(options);
			$(window).data('DiaProjectorPlugin', plugin);
		}

		// Prepare and add possible media through options.
		plugin.prepareMedia(options);

		// Attach elements to the plugin.
		return this.each(function()
		{
			plugin.attachElement(this);
		});
	}

	/**
	 * Plugin constructor.
	 * @param options the options for the dia projector
	 */
	$.DiaProjectorPlugin = function(options)
	{
		var plugin = this;

		var defaults = {
			'serie': 'default',
			'addMedia': [],
			'cssFile': 'lib/DiaProjector.css',
			'zIndex': 1000,
			'openDuration': 600,
			'closeDuration': 300,
			'toolbarOpenDelay': 300,
			'toolbarOpenDuration': 300,
			'mediaSwitchDuration': 50,
			'mediaOffDuration': 300,
			'mediaMargin': 40
		};

		/**
		 * Initializes the plugin.
		 * @param options the options for the dia projector
		 */
		plugin.init = function(options)
		{
			// Store configuration for the projector.
			plugin.config = $.extend({}, defaults, options);

			// Load CSS.
			$('head').append(
				$('<link />')
					.attr({
						'rel': 'stylesheet',
						'type': 'text/css',
						'href': plugin.config.cssFile
					})
				);

			// Create media info view.
			plugin.infoView = $('<div id="dia-projector-info" />');

			// Create buttons.
			plugin.hrefButton = $('<a id="dia-projector-href" class="button" href="#" title="Open external page"><span>Open</span></a>');
			var previousButton = $('<a id="dia-projector-previous" class="button" href="#" title="Previous"><span>Previous</span></a>')
				.click(plugin.previousMedia);
			var nextButton = $('<a id="dia-projector-next" class="button" href="#" title="Next"><span>Next</span></a>')
				.click(plugin.nextMedia);
			var closeButton = $('<a id="dia-projector-close" class="button" href="#" title="Close projector"><span>Close projector</span></a>')
				.click(plugin.hideProjector);

			// Create holders.
			plugin.toolbar = $('<div class="toolbar" />')
				.append(plugin.infoView)
				.append(plugin.hrefButton)
				.append(previousButton)
				.append(nextButton)
				.append(closeButton);
			plugin.surface = $('<div class="surface" />')
				.append(plugin.toolbar);
			plugin.projector = $('<div id="dia-projector" />')
				.css({
					'display': 'none',
					'position': 'absolute',
					'z-index': plugin.config.zIndex
				})
				.append(plugin.surface)
				.append($('<div class="edge" />'));
			$('body').append(plugin.projector);

			// Global event listeners.
			$(document).keyup(plugin._hotkeys);
			$(window).resize(plugin._resize);
			plugin.active = false;
		}

		/**
		 * Prepares media.
		 * @param options the options for listed media
		 */
		plugin.prepareMedia = function(options)
		{
			// Store media configuration for the matched elements.
			plugin.mediaConfig = $.extend({}, defaults, options);

			// Prepare media holder.
			if (!plugin.media)
			{
				plugin.media = {};
			}
			if (!plugin.media[plugin.mediaConfig.serie])
			{
				plugin.media[plugin.mediaConfig.serie] = [];
			}

			// Add possible listed media.
			if (plugin.mediaConfig.addMedia)
			{
				for (var i = 0; i < plugin.config.addMedia.length; i++)
				{
					plugin.media[plugin.mediaConfig.serie].push(plugin.mediaConfig.addMedia[i]);
				}
			}

			// Set first media current.
			plugin.current = {'serie': plugin.mediaConfig.serie, 'index': 0};
		}

		/**
		 * Attaches an element to the plugin.
		 * @param element the element
		 */
		plugin.attachElement = function(element)
		{
			$element = $(element);

			// Parse and store possible element media.
			if ($element.attr('data-type'))
			{
				var media = {
					'type': $element.attr('data-type'),
					'source': $element.attr('data-source'),
					'width': $element.attr('data-width'),
					'height': $element.attr('data-height'),
					'name': $element.attr('data-name'),
					'description': $element.attr('data-description'),
					'date': $element.attr('data-date'),
					'href': $element.attr('href')
				};
				var length = plugin.media[plugin.mediaConfig.serie].push(media);
				$element.data('DiaProjectorTarget', {'serie': plugin.mediaConfig.serie, 'index': length - 1});
			}

			// Listen for clicks.
			$element.click(plugin.clickElement);
		}

		/**
		 * Handles a click on an attached element.
		 * @param event the click event
		 */
		plugin.clickElement = function(event)
		{
			event.preventDefault();
			plugin.showProjector($(this).data('DiaProjectorTarget'));
		}

		/**
		 * Implements hotkeys.
		 * @param event a keyup event
		 */
		plugin._hotkeys = function(event)
		{
			if (!plugin.active)
			{
				return;
			}
			switch (event.which)
			{
				case 27: // esc
					event.preventDefault();
					plugin.hideProjector();
					break;
				case 32: // space
				case 39: // right
					event.preventDefault();
					plugin.nextMedia();
					break;
				case 37: // left
					event.preventDefault();
					plugin.previousMedia();
			}
		}

		/**
		 * Resizes the dia projector.
		 * @param event the resize event if any
		 */
		plugin._resize = function(event)
		{
			var w = $(window).width();
			var h = $(window).height();
			var scroll = plugin._getScrollPosition();
			plugin.projector
				.stop()
				.css({
					'left': scroll.x + 'px',
					'top': scroll.y + 'px',
					'width': w + 'px',
					'height': (h + 100) + 'px'
				});
			plugin.surface.css('height', h);
			plugin.toolbar.css('display', 'block');
			if (plugin.display)
			{
				plugin._resizeMediaDisplay(plugin.display);
			}
			if (plugin.nextDisplay)
			{
				plugin._resizeMediaDisplay(plugin.nextDisplay, true);
			}
		}

		/**
		 * Shows the dia projector.
		 * @param target an optional target media to view
		 */
		plugin.showProjector = function(target)
		{
			plugin.active = true;
			if (target)
			{
				plugin.current = target;
			}

			// Animate projector in.
			$('body').css('overflow', 'hidden');
			plugin._resize();
			plugin.toolbar.css('display', 'none');
			var scroll = plugin._getScrollPosition();
			plugin.projector
				.css({
					'display': 'block',
					'top': (scroll.y - plugin.projector.height()) + 'px'
				})
				.animate({'top': scroll.y}, plugin.config.openDuration, 'swing', function()
					{
						plugin._switchMedia();
						plugin.toolbar
							.delay(plugin.config.toolbarOpenDelay)
							.css({
								'display': 'block',
								'bottom': -plugin.toolbar.height()
							})
							.animate({'bottom': 0}, plugin.config.toolbarOpenDuration, 'swing');
					});
		}

		/**
		 * Hides the dia projector.
		 * @param event the cause if any
		 */
		plugin.hideProjector = function(event)
		{
			plugin.active = false;
			if (event)
			{
				event.preventDefault();
			}
			plugin.toolbar.find('#dia-projector-close').addClass('down');
			if (plugin.nextDisplay)
			{
				plugin.nextDisplay.remove();
				plugin.nextDisplay = null;
			}
			if (plugin.display)
			{
				plugin.display.fadeOut(plugin.config.mediaOffDuration, 'swing', plugin._hideProjectorFinish);
			}
			else
			{
				plugin._hideProjectorFinish();
			}
		}

		/**
		 * Finishes hiding the dia projector.
		 */
		plugin._hideProjectorFinish = function()
		{
			if (plugin.display)
			{
				plugin.display.remove();
				plugin.display = null;
			}
			var scroll = plugin._getScrollPosition();
			plugin.projector.animate(
				{'top': scroll.y - plugin.projector.height()},
				plugin.config.closeDuration,
				'swing',
				function()
				{
					plugin.toolbar.find('#dia-projector-close').removeClass('down');
					plugin.projector.css('display', 'none');
					$('body').css('overflow', 'auto');
				});
		}

		/**
		 * Switches to the previous media.
		 */
		plugin.previousMedia = function(event)
		{
			if (event)
			{
				event.preventDefault();
			}
			plugin.toolbar.find('#dia-projector-previous').addClass('down');
			plugin.current.index--;
			if (plugin.current.index < 0)
			{
				plugin.current.index = plugin.media[plugin.current.serie].length - 1;
			}
			plugin._switchMedia();
		}

		/**
		 * Switches to the next media.
		 */
		plugin.nextMedia = function(event)
		{
			if (event)
			{
				event.preventDefault();
			}
			plugin.toolbar.find('#dia-projector-next').addClass('down');
			plugin.current.index++;
			if (plugin.current.index >= plugin.media[plugin.current.serie].length)
			{
				plugin.current.index = 0;
			}
			plugin._switchMedia();
		}

		/**
		 * Switches to the current set media.
		 */
		plugin._switchMedia = function()
		{
			plugin.infoView.empty();
			if (plugin.display)
			{
				plugin.display.animate(
						{'left': plugin.surface.width()},
						plugin.mediaSwitchDuration,
						'swing',
						plugin._switchMediaIn
					);
			}
			else
			{
				plugin._switchMediaIn();
			}
		}

		/**
		 * Creates and switches in the current media set.
		 */
		plugin._switchMediaIn = function()
		{
			// Remove old media display.
			if (plugin.display)
			{
				plugin.display.remove();
				plugin.display = null;
			}

			// Check if next display is correct.
			var media = null;
			if (plugin.nextDisplay)
			{
				media = plugin.nextDisplay.data('DiaProjectorMedia');
				if (media.index == plugin.current.index)
				{
					plugin.display = plugin.nextDisplay;
				}
				else
				{
					plugin.nextDisplay.remove();
				}
				plugin.nextDisplay = null;
			}

			// Create new media display.
			if (!plugin.display)
			{
				plugin.display = plugin._createMediaDisplay(plugin.current.index);
				plugin.surface.append(plugin.display);
			}

			// Make visible.
			plugin.toolbar.find('a.button').removeClass('down');
			plugin.display.animate(
					{'left': Math.round((plugin.surface.width() - plugin.display.width()) / 2)},
					plugin.mediaSwitchDuration,
					'swing',
					plugin._switchMediaFinish
				);

			// Create info.
			media = plugin.display.data('DiaProjectorMedia');
			plugin.infoView.append('<h1>' + media.name + '</h1><p class="date">' + media.date + '</p><p>' + media.description + '</p>');
			plugin.hrefButton.removeClass().addClass('button media-' + media.type).attr('href', media.href);
		}

		/**
		 * Finishes the media switch.
		 */
		plugin._switchMediaFinish = function()
		{
			// Create next display for preloading.
			var i = plugin.current.index + 1;
			if (i >= plugin.media[plugin.current.serie].length)
			{
				i = 0;
			}
			plugin.nextDisplay = plugin._createMediaDisplay(i);
			plugin.surface.append(plugin.nextDisplay);
		}

		/**
		 * Creates a media display.
		 * @return a jquery element displaying the media
		 */
		plugin._createMediaDisplay = function(index)
		{
			// Get media.
			var media = plugin.media[plugin.current.serie][index];
			media.index = index;
			var dim = plugin._calculateMediaDimensions(media);

			// Create display element.
			$display = null;
			switch (media.type)
			{
				case 'flickr':
					$display = $('<img class="dia-projector-media" />')
						.attr({
							'src': media.source + '?rel=0&amp;hd=1',
							'width': dim.width,
							'height': dim.height,
							'alt': 'flickr photo'
						});
					break;
				case 'youtube':
					$display = $('<iframe class="dia-projector-media" />')
						.attr({
							'class': 'youtube-player',
							'type': 'text/html',
							'width': dim.width,
							'height': dim.height,
							'src': media.source,
							'frameborder': 0
						});
					break;
			}
			if ($display)
			{
				$display
					.css({
						'display': 'block',
						'position': 'absolute',
						'left': plugin.surface.width() + 'px',
						'top': Math.round((plugin.surface.height() - plugin.toolbar.height() - dim.height) / 2) + 'px'
					})
					.data('DiaProjectorMedia', media);
			}
			return $display;
		}

		/**
		 * Resizes a media display.
		 * @param $display a media jquery element
		 * @param isNextDisplay true to place off screeen
		 */
		plugin._resizeMediaDisplay = function($display, isNextDisplay)
		{
			var media = $display.data('DiaProjectorMedia');
			var dim = plugin._calculateMediaDimensions(media);
			switch (media.type)
			{
				case 'flickr':
				case 'youtube':
					$display
						.stop()
						.attr({
							'width': dim.width,
							'height': dim.height
						});
					break;
			}
			if (isNextDisplay)
			{
				$display.css({
						'left': plugin.surface.width() + 'px',
						'top': Math.round((plugin.surface.height() - plugin.toolbar.height() - dim.height) / 2) + 'px'
					});
			}
			else
			{
				$display.css({
						'left': Math.round((plugin.surface.width() - dim.width) / 2) + 'px',
						'top': Math.round((plugin.surface.height() - plugin.toolbar.height() - dim.height) / 2) + 'px'
					});
			}
		}

		/**
		 * Calculates media dimensions.
		 * @param media a media definition
		 * @return the dimensions
		 */
		plugin._calculateMediaDimensions = function(media)
		{
			var mw = plugin.surface.width() - plugin.config.mediaMargin;
			var mh = plugin.surface.height() - plugin.toolbar.height() - plugin.config.mediaMargin;
			var w = mw;
			var h = mh;
			if (media.width && media.height)
			{
				var th = Math.round((mw * media.height) / media.width);
				if (th <= h)
				{
					h = th;
				}
				else
				{
					w = Math.round((mh * media.width) / media.height);
				}
			}
			return {'width': w, 'height': h};
		}

		/**
		 * Gets current scroll position.
		 */
		plugin._getScrollPosition = function()
		{
			if (typeof pageXOffset != 'undefined' && typeof pageYOffset != 'undefined')
			{
				return { 'x': pageXOffset, 'y': pageYOffset };
			}
			var element = document.documentElement;
			if (!element.clientHeight)
			{
				element = document.body;
			}
			return { 'x': element.scrollLeft(), 'y': element.scrollTop() };
		}

		// Finally initialize the plugin.
		plugin.init(options);
	}

})(jQuery);

