/**
 * jQuery.pxEtalage - automated image slider for jQuery
 * written by Michiel Sikma <michiel@wedemandhtml.com>
 * Copyright (C) 2010, PixelDeluxe <http://pixeldeluxe.nl/>
 * All rights reserved.
 * Date: 2011/06/10
 *
 * @author Michiel Sikma
 * @version 0.6
 *
 **/

(function($)
{
	$.fn.pxEtalage = function(options)
	{
		var settings = {
			/* Which effect to use (fade, scroll). */
			effect: "fade",
			/* How long the effect takes to complete. */
			effect_duration: 500,
			
			/* Whether to go wrap the slides around (i.e. go back to 0 after clicking past the end). */
			slide_wrap: false,
			/* Whether to create an "infinitely long" slider. Only makes sense for "scroll" effect. slide_wrap must also be true. */
			slide_infinite: false,
			/* Whether to allow automatic sliding or to restrict to user interaction. */
			slide_automation: true,
			/* How long a slide will be seen before moving on to the next. */
			slide_duration: 4000,
			
			/* Which event to listen to for the menu links. */
			menu_event: "mouseenter",
			/* Which event to listen to for the arrows. */
			arrow_event: "click",
			
			/* Actions to perform on animation start. */
			callback_start: null,
			/* Actions to perform after the animation finishes. */
			callback_end: null
		};
		
		/* Perform the plugin code on all elements, then return the jQuery object for chainability. */
		return this.each(function()
		{
			/**
			 * Note that this plugin only works with the proper HTML structure. Here is an example:
			 *
			 * <div class="main">
			 *     <ul class="menu">
			 *         <li><a href="page_a.html">Page A</a></li>
			 *         <li><a href="page_b.html">Page B</a></li>
			 *         <li><a href="page_c.html">Page C</a></li>
			 *         <li><a href="page_d.html">Featured page</a></li>
			 *     </ul>
			 *     <div class="image_wrap">
			 *         <img src="page_a_preview.png" alt="Page A preview" class="slide" />
			 *         <img src="page_b_preview.png" alt="Page B preview" class="slide" />
			 *         <img src="page_c_preview.png" alt="Page C preview" class="slide" />
			 *         <img src="page_d_preview.png" alt="Page D preview" class="slide default" />
			 *     </div>
			 * </div>
			 *
			 * When using arrows instead of a menu of links, the following structure applies:
			 *
			 * <div class="main">
			 *     <div class="arrows">
			 *         <a href="#" class="arrow prev">Previous</a>
			 *         <a href="#" class="arrow next">Next</a>
			 *     </div>
			 *     <div class="image_wrap">
			 *         <img src="page_a_preview.png" alt="Page A preview" class="slide" />
			 *         <img src="page_b_preview.png" alt="Page B preview" class="slide" />
			 *         <img src="page_c_preview.png" alt="Page C preview" class="slide" />
			 *         <img src="page_d_preview.png" alt="Page D preview" class="slide default" />
			 *     </div>
			 * </div>
			 *
			 * The .main div here represents the element on which the $.fn.pxEtalage() method was called.
			 * Its class is arbitrary. .menu and .image_wrap are required, however. The images with
			 * .slide might as well be <div> elements.
			 *
			 **/
			
			/* $main refers to the HTML element on which the $.fn.pxEtalage() method was called. */
			var $main = $(this);
			var a, z;
			
			if (settings) {
				/* Override default settings. */
				$.extend(settings, options);
				if (!/^fade|scroll$/.test(settings.effect)) {
					window.console && console.warn("The requested effect (%o) cannot be found.", settings.effect);
				}
			}
			
			/* Index all the images and determine their default. */
			var $image_wrap = $(".image_wrap", $main);
			var $images = $(".slide", $image_wrap);
			if ($images.length == 0) {
				/* If there are no images, bail out. */
				return;
			}
			var $image;
			
			/* In case we're creating an infinite slider, add two extra items to the list now. */
			var inf = settings.slide_infinite && settings.slide_wrap && settings.effect == "scroll";
			if (inf) {
				/* Add two copies. */
				var $prependClone = $($images[$images.length - 1])
					.clone()
					.addClass("z_copy");
				var $appendClone = $($images[0])
					.clone()
					.addClass("a_copy");
				$prependClone.css({marginLeft: "-"+(parseInt($prependClone.attr("width"), 10))+"px"});
				$image_wrap.prepend($prependClone);
				$image_wrap.append($appendClone);
			}
			var $all_images = $.extend({}, $images);
			if (inf) {
				$all_images.push($prependClone[0]);
				$all_images.push($appendClone[0]);
			}
			for (a = 0, z = $all_images.length; a < z; ++a) {
				/* Number all of the images. */
				$.data($all_images[a], "n", a);
				if (settings.effect == "scroll") {
					$image = $($all_images[a]);
					$.data($all_images[a], {pos: $image.position(), margins: {top: parseInt($image.css("marginTop"), 10), left: parseInt($image.css("marginLeft"), 10)}});
				}
			}
			
			var max_z = 5;
			var $default_image = $(".slide.default", $image_wrap);
			if ($default_image.length == 0) {
				$default_image = $($images[0]);
			}
			var default_n = $.data($default_image[0], "n");
			var hide_default = true;
			max_z = Number($default_image.css("zIndex")) + 1;
			var $previous_image = $default_image;
			
			var $menu = $(".menu", $main);
			var $arrows = $(".arrows", $main);
			var uses_menu = $menu.length > 0;
			var uses_arrows = $arrows.length > 0;
			if (settings.effect == "fade") {
				$images.fadeTo(0, 0);
				$default_image.fadeTo(0, 1);
				$default_image.css({zIndex: max_z});
			}
			if (settings.effect == "scroll") {
			}
			
			/* Function used to move to the next or previous slide. */
			function adjecent_slide(offset)
			{
				show_slide(get_adjecent_slide_n(offset));
			}
			
			/* Information about the current slider. */
			var info = {a: 0};
			
			/* Function used to show a particular slide. */
			var prev_n;
			var curr_n;
			var has_wrapped = false;
			var $wrap_target;
			function callback_end_wrapper(a)
			{
				settings.callback_end != null && settings.callback_end.apply(a, [self]);
			}
			var on_prepend = false;
			var on_append = false;
			function show_slide(n)
			{
				if (uses_menu) {
					$menu_links.removeClass("active");
					$($menu_links[n]).addClass("active");
				}
				var $to_image;
				var $image = $to_image = $($images[n]);
				prev_n = $.data($previous_image[0], "n");
				curr_n = $.data($image[0], "n");
				var animate_to_clone;
				
				var direction_forward = curr_n > prev_n;
				var wrap_last_to_first = prev_n == $images.length - 1 && curr_n == 0;
				var wrap_first_to_last = prev_n == 0 && curr_n == $images.length - 1;
				var allow_wrap = true;
				if (inf && ((wrap_last_to_first && on_prepend) || (wrap_first_to_last && on_append))) {
					/* If going forward and on the prependClone, or going back and on the appendClone, don't wrap. */
					has_wrapped = false;
					on_prepend = false;
					on_append = false;
					allow_wrap = false;
				}
				
				
				if (wrap_first_to_last && allow_wrap && inf) {
					/* Animate the "infinite slider" from the first to the last item. */
					on_prepend = true;
					$image = $prependClone;
					$wrap_target = $to_image;
					animate_to_clone = true;
				}
				if (wrap_last_to_first && allow_wrap && inf) {
					/* Animate the "infinite slider" from the last to the first item. */
					on_append = true;
					$image = $appendClone;
					$wrap_target = $to_image;
					animate_to_clone = true;
				}
				on_prepend = wrap_first_to_last ? on_prepend : false;
				on_append = wrap_last_to_first ? on_append : false;
				if (prev_n == curr_n) {
					return;
				}
				
				var callback_info = {curr: curr_n, prev: prev_n, amount: $images.length, settings: settings, is_clone: animate_to_clone == true ? true : false};
				
				switch (settings.effect) {
					case "fade":
						max_z += 1;
						$image.css({zIndex: max_z});
						settings.callback_start != null && settings.callback_start.apply($image[0], [callback_info]);
						$image.animate({opacity: 1}, {duration: settings.effect_duration, queue: false, complete: function()
						{
							settings.callback_start != null && settings.callback_start.apply($image[0], [callback_info]);
						}});
						if ($previous_image && $previous_image[0] != $image[0]) {
							$previous_image.animate({opacity: 0}, {duration: settings.effect_duration, queue: false});
						}
						if (hide_default && $default_image[0] != $image[0]) {
							/* Hide the default image if it hasn't been hidden already. */
							hide_default = false;
							$default_image.animate({opacity: 0}, {duration: settings.effect_duration, queue: false});
						}
						break;
					case "scroll":
						var pos, margins;
						settings.callback_start != null && settings.callback_start.apply($image[0], [callback_info]);
						if (has_wrapped) {
							pos = $.data($wrap_target[0], "pos");
							margins = $.data($wrap_target[0], "margins");
							$image_wrap.css({marginLeft: (-pos.left) - margins.left, marginTop: (-pos.top) - margins.top});
							has_wrapped = false;
						}
						pos = $.data($image[0], "pos");
						margins = $.data($image[0], "margins");
						$image_wrap.animate({marginLeft: (-pos.left) - margins.left, marginTop: (-pos.top) - margins.top}, {duration: settings.effect_duration, queue: false, complete: function()
						{
							settings.callback_start != null && settings.callback_start.apply($image[0], [callback_info]);
						}});
						break;
				}
				if (animate_to_clone) {
					has_wrapped = true;
				}
				$previous_image = $to_image;
			}
			
			/* Function used to get the next image in line. */
			function get_adjecent_slide_n(offset)
			{
				if (offset == null) {
					offset = 1;
				}
				var next_n = $.data($previous_image[0], "n") + offset;
				if (next_n > $images.length - 1 && settings.slide_wrap) {
					next_n = 0;
				}
				if (next_n < 0 && settings.slide_wrap) {
					next_n = $images.length - 1;
				}
				if (next_n > $images.length - 1 && !settings.slide_wrap) {
					next_n = $images.length - 1;
				}
				if (next_n < 0 && !settings.slide_wrap) {
					next_n = 0;
				}
				return next_n;
			}
			
			if (uses_menu) {
				/* Link the hover command of the menu items to their corresponding slides. */
				var $menu_links = $("a", $menu);
				var $link;
				for (a = 0, z = $menu_links.length; a < z; ++a) {
					$link = $($menu_links[a]);
					$.data($menu_links[a], "n", a);
					$link[settings.menu_event](function()
					{
						show_slide($.data(this, "n"));
						return false;
					});
					if (a == default_n) {
						$link.addClass("active");
					}
				}
			}
			if (uses_arrows) {
				/* Link the arrows to the next/prev actions. */
				var $arrow_links = $(".arrow", $arrows);
				var $arrow_link;
				for (a = 0, z = $arrow_links.length; a < z; ++a) {
					$arrow_link = $($arrow_links[a]);
					if ($arrow_link.hasClass("prev")) {
						$.data($arrow_links[a], "offset", -1);
					}
					if ($arrow_link.hasClass("next")) {
						$.data($arrow_links[a], "offset", 1);
					}
					$arrow_link[settings.arrow_event](function()
					{
						adjecent_slide($.data(this, "offset"));
						return false;
					});
				}
			}
			
			/* Set up automatic sliding if it is enabled. */
			if (settings.slide_automation) {
				if (jQuery.timer) {
					function start_timer()
					{
						$main.everyTime(settings.slide_duration, "slide_automation", function()
						{
							show_slide(get_adjecent_slide_n());
						});
					}
					start_timer();
					/* Make sure sliding is paused when the user hovers over the menu. */
					$menu.mouseenter(function()
					{
						$main.stopTime("slide_automation");
					});
					$menu.mouseleave(function()
					{
						start_timer();
					});
				} else {
					window.console && console.warn("jQuery Timers plugin doesn't seem to be present. It's required for automatic sliding. Download it at http://plugins.jquery.com/project/timers. To hide this warning, set slide_automation to %o.", false);
				}
			}
			
			/* Last check to ensure integrity. */
			if (uses_menu && $menu_links.length > $images.length) {
				window.console && console.warn("Amount of menu links (%o) and images (%o) is not the same.", $menu_links.length, $images.length);
			}
		});
	}
})(jQuery);
