/**
 * Slideshow.js: Slideshow class for performing content transitions
 *
 * @fileoverview
 * @author Garth Henson
 * @version 1.0
 */

/**
 * Slideshow class constructor method
 *
 * @version 1.0
 * @constructor
 * @param {String} slideshowId The HTML ID of the element which is to become the slideshow container
 * @param {Object} config Configuration object to control the behavior of the slideshow. 
 *	Options:
 *		String transition fade|slide|shutter - Defines the transition type between slides
 *		int timeout - Defines the time (in milliseconds) between transitions
 * @return Slideshow object
 * @type Slideshow
 */

Syn.Slideshow = Class.extend(
{
	init: function(slideshow_id, config)
	{
		this.listeners = {
			beforetransition : [],
			ontransition     : [],
			beforeplay       : [],
			onplay           : [],
			beforestop       : [],
			onstop           : [],
			beforeprev       : [],
			onprev           : [],
			beforenext       : [],
			onnext           : [],
			beforeplaypause  : [],
			onplaypause      : []
		};

		// Constructor variable assignment
		this.id     = slideshow_id;

		var defaults = {
			transition : 0,
			timeout : 5000,
			hover_stop : true,
			direction : 'n',
			play : true,
			fade_speed : 'slow'
		};
		this.config = $.extend(defaults, config);
		
		// Object variables referencing display settings
		this.container = null;
		this.controls  = null;
		this.slides    = [];
		this.current   = 0;
		
		// Object variables to control rotation
		this.setTransitionType(this.config.transition);
		this.timeout    = this.config.timeout;
		this.timeout_id = null;
		this.hover_stop = this.config.hover_stop;
		
		// Slide settings
		this.slide_direction = this.config.direction;
		this.z_index         = 999;
		
		var ele = $('#' + this.id);
		this.container = ele.find('div.ss-container');
		this.controls  = ele.find('div.ss-controls');
		
		this.width  = this.container.width();
		this.height = this.container.height();

		//Set animation and trigger values
		this.running = false;
		this.transitioning = false;

		// Now for the actual setup of the configuration variables
		var show = this;

		// Find and assign all slides to their holders
		this.container.find('div.slide').each(function(i)
		{
			var i = $(this);
			var h_offset = parseInt(i.css('padding-top'), 10) + parseInt(i.css('padding-bottom'), 10);
			var w_offset = parseInt(i.css('padding-left'), 10) + parseInt(i.css('padding-right'), 10);
			
			i.hide();
			i.css({
				width: show.width - h_offset + 'px',
				height: show.height - w_offset + 'px',
				overflow: 'hidden'
			});
			
			show.slides.push(i);
		});

		// Set up the controls, if any
		this.setControls();
		
		// Prime the first slide to be displayed
		if (this.slides.length > 0)
		{
			this.slides[0].css('left', 'auto');
			this.slides[0].show();
		}

		// Assign actions to hover, if specified
		if (this.hover_stop == true)
		{
			this.container.hover(
				function()
				{
					show.stop();
				},
				function()
				{
					show.play();
				}
			);
		}

		if (this.config.play || false)
		{
			this.play();
		}

		// Add this Slideshow to the registry
		Syn.Slideshow.registry.push(this);
	},
	
	setControls: function()
	{
		if (this.controls)
		{
			if (this.slides.length < 2)
			{
				this.controls.hide();
			}
			else
			{
				this.controls.find('a.ss-previous').connect('click', this, 'showPrevious');
				this.controls.find('a.ss-next').connect('click', this, 'showNext');
				this.controls.find('a.ss-pause').connect('click', this, 'stop');
				this.controls.find('a.ss-stop').connect('click', this, 'stop');
				this.controls.find('a.ss-pause-play').connect('click', this, 'pausePlay');
			}
		}
	},
	
	/**
	 * Toggles the action of the current slide show
	 * @member Syn.Slideshow
	 * @type void
	 */
	pausePlay: function()
	{
		this.trigger('beforeplaypause');
		if (this.timeout_id != null)
		{
			this.stop();
		}
		else
		{
			this.play();
		}
		this.trigger('onplaypause');
	},
	
	/**
	 * Shows the next slide in the series. If we are on the last slide, it loops back to the first.
	 * @member Syn.Slideshow
	 * @type void
	 */
	showNext: function()
	{
		this.trigger('beforenext');

		var x = this.current + 1;
		if (x == this.slides.length)
		{
			x = 0;
		}
		
		var o = this;
		this.change(x, function()
		{
			o.trigger('onnext');
		});
	},
	
	/**
	 * Shows the previous slide in the series. If we are on the first slide, it loops to the last.
	 * @member Syn.Slideshow
	 * @type void
	 */
	showPrevious: function()
	{
		this.trigger('beforeprev');

		var x = this.current - 1;
		if (x < 0)
		{
			x = this.slides.length - 1;
		}
		
		var o = this;
		this.change(x, function()
		{
			o.trigger('onprev');
		});
	},
	
	/**
	 * Processes the transition from the requested slide to the slide at the requested index
	 * @member Syn.Slideshow
	 * @param {int} index The index of the loaded slide which is to be shown
	 * @param {function} callback An optional callback to execute upon completion
	 * @type void
	 */
	change: function(index, callback)
	{
		if (this.slides.length > 1 && !this.transitioning)
		{
			// Kill Timeout, since we don't know if it was manually triggered
			clearTimeout(this.timeout_id);
			this.timeout_id = null;


			var o = this;
			this.transitioning = true;

			var fn = function()
			{
				// Transition is completed, so reset the timeout
				if (o.running || false)
				{
					o.timeout_id = setTimeout(function(){ o.showNext(); }, o.timeout);
				}

				if (callback || false)
				{
					callback();
				}

				// Transition finished
				o.transitioning = false;
				o.trigger('ontransition');
			}

			this.trigger('beforetransition');
			switch (this.transition)
			{
				case Syn.Slideshow.TRANSITION_TYPE_FADE:
					this.fade(index, fn);
					break;
	
				case Syn.Slideshow.TRANSITION_TYPE_SHUTTER:
					this.shutter(index, fn);
					break;
	
				case Syn.Slideshow.TRANSITION_TYPE_SLIDE:
					this.slide(index, fn);
					break;
			}
		}
	},
	
	/**
	 * Fades visibility from the currently visible slide to the slide at the provided index
	 * @member Syn.Slideshow
	 * @param {int} index The index of the slide to which to fade
	 * @param {function} callback Optional callback function to execute upon completion
	 * @type void
	 */
	fade: function(index, callback)
	{
		var slide1 = this.slides[this.current];
		var slide2 = this.slides[index];
		
		var show = this;
		slide1.fadeOut(show.config.fade_speed, function()
		{
			$(this).css('left', '-999em');
			show.current = index;
			if (callback || false)
			{
				callback();
			}
		});
		
		slide2.css('left', 'auto');
		slide2.fadeIn(show.config.fade_speed);
	},
	
	/**
	 * Immediately transitions from the currently visible slide to the slide at the provided index
	 * @member Syn.Slideshow
	 * @param {int} index The index of the slide to which to transition
	 * @param {function} callback Optional callback function to execute upon completion
	 * @type void
	 */
	shutter: function(index, callback)
	{
		var slide1 = this.slides[this.current];
		var slide2 = this.slides[index];

		slide1.hide();
		slide1.css('left', '-999em');
		slide2.css('left', 'auto');
		slide2.show();

		this.current = index;
		if (callback || false)
		{
			callback();
		}
	},
	
	/**
	 * True "sliding" transition. If the member variable slide_direction is set to
	 * one of the 8 compas points (n, s, e, w, ne, nw, se, sw), the transition will
	 * come from that direction. Transition defaults to coming from the east.
	 * @member Syn.Slideshow
	 * @param {int} index The index of the slide to which to transition
	 * @param {function} callback Optional callback function to execute upon completion
	 * @type void
	 */
	slide: function(index, callback)
	{
		var direction = this.slide_direction;
		if (!direction)
		{
			direction = 'e';
		}
		
		var slide1 = this.slides[this.current];
		var slide2 = this.slides[index];
		
		slide1.css('z-index', this.z_index - 10);
		slide2.css({
			zIndex: this.z_index,
			left: 'auto'
		});
		slide2.show();
		
		if (direction.charAt(0) == 'n')
		{
			slide2.css('marginTop', -(slide2.height()) + 'px');
		}
		else if (direction.charAt(0) == 's')
		{
			slide2.css('marginTop', slide2.height() + 'px');
		}
		else
		{
			slide2.css('marginTop', '0');
		}
		
		if (direction.substr(-1) == 'e')
		{
			slide2.css('marginLeft', slide2.width() + 'px');
		}
		else if (direction.substr(-1) == 'w')
		{
			slide2.css('marginLeft', -(slide2.width()) + 'px');
		}
		else
		{
			slide2.css('marginLeft', '0');
		}
		
		var o = this;
		slide2.animate({
			marginLeft: 0,
			marginTop: 0
		}, 1000, null, function()
		{
			slide1.hide();
			o.current = index;
			if (callback || false)
			{
				callback();
			}
		});
		
	},
	
	/**
	 * Sets the transition type for this object
	 * @member Syn.Slideshow
	 * @param {String} type Values: shutter|fade|slide
	 * @type void
	 */
	setTransitionType: function(type)
	{
		if (type == 'shutter')
		{
			this.transition = Syn.Slideshow.TRANSITION_TYPE_SHUTTER;
		}
		else if (type == 'fade')
		{
			this.transition = Syn.Slideshow.TRANSITION_TYPE_FADE;
		}
		else if (type == 'slide')
		{
			this.transition = Syn.Slideshow.TRANSITION_TYPE_SLIDE;
		}
	},
	
	/**
	 * Begin the slideshow animation and set the interval for rotation
	 * @member Syn.Slideshow
	 * @type void
	 */
	play: function()
	{
		this.running = true;
		if (this.timeout_id != null)
		{
			return;
		}
	
		this.trigger('beforeplay');

		var o = this;
		this.timeout_id = setTimeout(function(){ o.showNext(); }, this.timeout);

		this.trigger('onplay');
	},
	
	/**
	 * Stop slideshow animation by clearing the current object's interval
	 * @member Syn.Slideshow
	 * @type void
	 */
	stop: function()
	{
		if (this.timeout_id)
		{
			clearTimeout(this.timeout_id);
		}
		
		this.trigger('beforestop');

		this.timeout_id = null;
		this.running = false;

		this.trigger('onstop');
	},

	/**
	 * Adds a listener to the provided event
	 * @member Syn.Slideshow
	 * @param {String} e The event to which the listener is to be applied
	 * @param {function} fn The listener function
	 */
	addListener : function(e, fn)
	{
		if (this.listeners[e] || false)
		{
			this.listeners[e].push(fn);
		}
	},

	/**
	 * Removes the specified listener function from the provided event
	 * @member Syn.Slideshow
	 * @param {String} e The event from which the listener is to be removed
	 * @param {function} fn The listener to be removed
	 */
	removeListener : function(e, fn)
	{
		if (this.listeners[e] || false)
		{
			var new_listeners = [];
			for (var i = 0; i < this.listeners[e].length; i++)
			{
				if (this.listeners[e][i] != fn)
				{
					new_listeners.push(this.listeners[e][i]);
				}
			}
			this.listeners[e] = new_listeners;
		}
	},

	/**
	 * Clears all listeners from the specified event
	 * @member Syn.Slideshow
	 * @param {String} e The event
	 */
	clearListeners : function(e)
	{
		if (this.listeners[e] || false)
		{
			this.listeners[e] = [];
		}
	},

	/**
	 * Triggers the specified event, executing all listeners
	 * NOTE: All listeners are passed "this" Slideshow object as a parameter
	 * @member Syn.Slideshow
	 * @param {String} e The event
	 */
	trigger : function(e)
	{
		if (this.listeners[e] || false)
		{
			for (var i = 0; i < this.listeners[e].length; i++)
			{
				this.listeners[e][i](this);
			}
		}
	}
});

/**
 * Static member variable: Fade transition type
 */
Syn.Slideshow.TRANSITION_TYPE_FADE    = 0;
/**
 * Static member variable: Shutter transition type
 */
Syn.Slideshow.TRANSITION_TYPE_SHUTTER = 1;
/**
 * Static member variable: Slide transition type
 */
Syn.Slideshow.TRANSITION_TYPE_SLIDE   = 2;

/**
 * Static registry to retain a reference for all slideshows in this context
 */
Syn.Slideshow.registry = [];
