/**
 * SlideShow - Creates a slideshow of images & content with automatic or manual rotation
 * @param	object	options		optional;Key/value pair object to set slideshow options.
 *
 * options:
 * 	imgClass		The classname used to detect images that are part of the slideshow
 * 	contentClass	The classname used to detect content items are part of the slideshow
 * 	loop			Whether the slideshow should loop
 * 	direction		Direction of the traversal of slides. (forward or reverse)
 * 	delay			Default delay (in milliseconds) between changing slides (used when no per-image delay set)
 * 	speed			Speed of the transition animation (in milliseconds)
 * 	autoStart		Whether the slideshow should start as soon as it's loaded
 *
 * Event callbacks can be trigged by using the register method.
 * Callbacks are passed these arguments following order:
 *   SlideShow instance, current slide index, index of slide before current, event, any extra arguments 
 * 
 * 
 * events:
 *  start			Triggered when the slideshow is started
 *  stop			Triggered when the slideshow is stopped
 *  next			Triggered when the next slide in the rotation is displayed (after goto event)
 *  previous		Triggered when the previous slide in the rotation is displayed (after goto event)
 *  goto			Triggered just prior to start of transition
 *  transition		Triggered on every step of opacity changes. --Caution-- this will be intensive
 */
function SlideShow(options) {
	/* Internal use items */
	this.images = [];
	this.content = [];
	this.delays = [];
	this.current = 0;
	this.last = 0;
	this.timers = [];
	this.layers = [];
	this.rotating = false;
	this.rotationTimer = null;
	this.isIE = false;
	
	/* Settings */
	this.imgClass = 'ss_photo';
	this.contentClass = 'ss_content';
	this.loop = true;
	this.direction = SlideShow.FORWARD;
	this.delay = 10000; //milliseconds
	this.speed = 1000; //milliseconds
	this.autoStart = false;
	
	/* Event callbacks */
	this.events = {
		'start': [],
		'stop': [],
		'next': [],
		'previous': [],
		'goto': [],
		'transition': []
	};
	
	for(var k in options) {
		if(options.hasOwnProperty(k) && (k in this) && this[k] !== undefined) {
			this[k] = options[k];
		}
	}
	
	domLoader.register(function(){
		this.isIE = window.ActiveXObject ? (window.XMLHttpRequest ? 7 : 6) : false;
		
		if(this.imgClass.trim() != '') {
			var images = slice(dbc(this.imgClass));
			images.forEach(function(img,i, imgs){
				this.images.push(img);
			},this);
		}
		
		if(this.contentClass.trim() != '') {
			var content_items = slice(dbc(this.contentClass));
			content_items.forEach(function(item,i){
				item.style.zIndex = i;
				this.content.push(item);
			},this);
		}
		
		var max = Math.max(this.images.length, this.content.length)-1;
		for(max;max >= 0;max--){
			this.layers.push(max);
		}
		
		if(this.images[0]) {
			this.setOpacity(this.images[0],100);
		}
		
		if(this.content[0]) {
			this.setOpacity(this.content[0],100);
		}
		
		this.relayer();
		if(this.autoStart) {
			this.start();
		}
	},this);
}
/**
 * Constants
 */
SlideShow.FORWARD = 'forward';
SlideShow.REVERSE = 'reverse';
/**
 * Methods
 */
SlideShow.prototype = {
	get: function(name) {
		return (name in this) ? this[name] : null;
	},
	set: function(name, value) {
		if(!(name in this) || this[name] === undefined) {
			return false;
		}
		switch(name) {
			case 'imgClass':
			case 'contentClass':
				this[name] = String(value);
				break;
			case 'loop':
			case 'autoStart':
				this[name] = Boolean(value);
				break;
			case 'delay':
			case 'speed':
				this[name] = Number(value);
				break;
			case 'delays':
				if(value instanceof Array) {
					value = value.filter(function(val){ return !isNaN(Number(val)); });
					this[name] = value;
				}
				break;
			case 'direction':
				if(value == SlideShow.FORWARD || value == SlideShow.REVERSE) {
					this[name] = value;
				}
				break;
		}
		return this;
	},
	/**
	 * Register a function to be called when a valid slideshow event happens
	 * see the constructor for valid events.
	 */
	register: function(event, callback) {
		if(event in this.events && typeof callback == 'function') {
			this.events[event].push(callback);
			return true;
		}
		return false;
	},
	/**
	 * Internal usage	Calls registered callbacks for the specified event
	 */
	fireCallback: function(event, xargs) {
		if(event in this.events && this.events[event] instanceof Array) {
			var args = [this.current, this.last, event].concat((xargs instanceof Array) ? xargs: []);
			this.events[event].forEach(function(fn){
				fn.apply(this, args);
			},this);
		}
		return this;
	},
	next: function() {
		if(this.direction == SlideShow.REVERSE) {
			return this.previous();
		}
		var next = this.current+1;
		if(next >= this.layers.length && this.loop) {
			next = 0;
		} else if(next >= this.layers.length) {
			next = this.current;
		}
		if(this.goto(next)) {
			this.fireCallback('next');
			return true;
		}
		return false;
	},
	previous: function() {
		if(this.direction == SlideShow.FORWARD) {
			return this.next();
		}
		var prev = this.current-1;
		if(prev < 0 && this.loop) {
			prev = this.layers.length-1;
		} else if(prev < 0) {
			prev = this.current;
		}
		if(this.goto(prev)) {
			this.fireCallback('previous');
			return true;
		}
		return false;
	},
	goto: function(idx) {
		idx = Number(idx);
		if(isNaN(idx)) {
			return this;
		}
		if(idx >= 0 && idx < this.layers.length) {
			this.last = this.current;
			this.current = idx;
		}
		if(this.last != this.current) {
			var topLayer = this.layers.length-1;
			while(this.layers[topLayer-1] != this.current) {
				this.layers.unshift(this.layers.pop());
			}
			this.fireCallback('goto');
			this.transition();
			return true;
		}
		return false;
	},
	/**
	 * Internal usage	Creates the fading transition animation
	 */
	transition: function() {
		var imgFrom = (this.images[this.last] || null), imgTo = (this.images[this.current] || null);
		var opac = 100, opac2 = 0;
		var step = 1, speed = this.speed/100;
		var self = this;
		
		function changeOpacity(index) {
			self.timers[index] = window.setTimeout(function() {
				opac -= step;
				opac2 += step;
				if(self.images[self.last]) {
					self.setOpacity(self.images[self.last], opac);
				}
				if(self.images[self.current]) {
					self.setOpacity(self.images[self.current], opac2);
				}
				if(self.content[self.last]) {
					self.setOpacity(self.content[self.last],opac);
				}
				if(self.content[self.current]) {
					self.setOpacity(self.content[self.current],opac2);
				}
				self.fireCallback('transition',[opac2]);
				window.clearTimeout(self.timers[index]);
				self.timers[index] = null;
			},(index * speed));
		}
		
		for(var i=0;i < 100;i++) {
			changeOpacity(i);
		}
		
		this.timers[101] = window.setTimeout(function(){
			var topLayer = self.layers.length-1;
			while(self.layers[topLayer] != self.current) {
				self.layers.unshift(self.layers.pop());
			}
			self.relayer();
			window.clearTimeout(self.timers[101]);
		},(101 * speed));
	},
	/**
	 * Internal usage	Handles the rotation timer
	 */
	rotate: function() {
		if(!this.rotating) {
			return;
		}
		var delay = Number(this.delays[this.current] || this.delay)+this.speed;
		var self = this, dirfn = (this.direction == SlideShow.REVERSE ? 'previous':'next');
		this.rotationTimer = window.setTimeout(function(){
			if(self[dirfn]()) {
				self.rotate();
			} else {
				self.stop();
			}
		},delay);
	},
	start: function() {
		this.fireCallback('start');
		this.rotating = true;
		this.rotate();
		return this;
	},
	stop: function() {
		this.fireCallback('stop');
		this.rotating = false;
		window.clearTimeout(this.rotationTimer);
		this.rotationTimer = null;
		return this;
	},
	/**
	 * Internal usage	Sets the opacity of an element
	 */
	setOpacity: function(obj, value) {
		if(this.isIE) {
			try{
			if(obj.filters.length) {
				obj.filters[0].opacity = value;
			} else {
				obj.style.filter += 'progid:DXImageTransform.Microsoft.Alpha(opacity='+value+')';
			}
			}catch(e){}
		} else {
			obj.style.opacity = value/100;
		}
	},
	/**
	 * Internal usage	Sets z-index of elements so they are still usable
	 */
	relayer: function() {
		this.layers.forEach(function(id, n) {
			if(this.images[id]) {
				this.images[id].style.zIndex = n;
			}
			if(this.content[id]) {
				this.content[id].style.zIndex = n;
			}
		},this);
	},
	constructor: SlideShow
};