/*

	Copyright (c) 2009 Rob Bast

	Permission is hereby granted, free of charge, to any person
	obtaining a copy of this software and associated documentation
	files (the "Software"), to deal in the Software without
	restriction, including without limitation the rights to use,
	copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software
	is furnished to do so, subject to the following condition:

	The above copyright notice and this permission notice shall be
	included in all copies or substantial portions of the Software.


	slider.js

		Simple class for sliding elements within a container.

		Container should be as wide as the total width of slides you
		intend to show at once (overflow hidden). Slides should be wide
		enough to fill the container. Slides should have no margin or
		padding! If you need these, simply add a child element to the
		slide with desired padding or margin.

*/
var Slider = new Class({

	Implements: [Options, Events],

	options: {
		childSelector: 'div',    // used to grab sliding children of container
		endless: false,          // endless implies next() or previous() always works
		direction: 'horizontal', // direction of sliding, horizontal or vertical
		show: 1,                 // how many items to show at once
		slidesToSlide: 1,        // how many items to slide at once
		auto: false,             // delay between each auto slide action (set to false for off)
		fxOptions: {
			duration: 500          // duration of the slide effect
		},
		onStart: function(){
			this.next();
		},
		onFirst: function(){
			this.next();
		},
		onLast: function(){
			this.previous();
		},
		onNext: $empty,
		onPrevious: $empty,
		onStop: $empty,
		onPause: $empty,
		onResume: $empty
	},

	slides: {
		next: [],
		previous: []
	},
	fx: null,
	timeoutId: null,

	initialize: function(container, options){
		// grab container element
		this.container = this.parent = document.id(container);
		// check if container exists
		if(!this.container){
			console.error('You failed the internet, do not pass start and do not collect awesome fx effects.');
			return;
		}
		// process any passed options
		this.setOptions(options);
		// grab all children of our container, based on childSelector given && also apply style float:left
		var slides = this.container.getElements(this.options.childSelector).setStyle('float', 'left');
		// check if we have enough child elements to work with
		if(slides.length < this.options.show + 1){
			console.error('Failure is imminent, need more items to slide anything.');
			return;
		}
		// grab all excess children and loop through them
		slides.slice(this.options.show, slides.length).each(
			function(slide){
				// remove each element from the DOM and push them onto the 'next' slides stack
				this.slides.next.push(slide.dispose());
			// I bind 'this', because otherwise this.slides is not accessible
			}.bind(this)
		);
		// check if we want to slide multiple slides at once
		if(this.options.slidesToSlide > 1){
			// set slide duration to duration divided by amount of slides to slide
			this.options.fxOptions = $merge(this.options.fxOptions, {duration: this.options.fxOptions.duration / this.options.slidesToSlide});
		}
		// if auto slide option is on
		if(this.options.auto){
			// start sliding! durrr
			this.timeoutId = this.start.delay(this.options.auto, this);
		}
	},

	start: function(){
		this.fireEvent('start');
	},

	stop: function(){
		if(this.timeoutId){
			$clear(this.timeoutId);
			this.fireEvent('stop');
		}
	},
	
	pause: function(){
		if(this.fx){
			this.fx.pause();
			this.fireEvent('pause');
		}
	},

	resume: function(){
		if(this.fx){
			this.fx.resume();
			this.fireEvent('resume');
		}
	},
	
	previous: function(slides){
		var slides = slides || this.options.slidesToSlide;
		this.slide(-slides);
	},
	
	next: function(slides){
		var slides = slides || this.options.slidesToSlide;
		this.slide(slides);
	},
	
	slide: function(slides){
		// check if we don't have an fx instance running
		if(!this.fx && (slides > 0 || slides < 0)){
			// check if we're moving in 'next' direction and are out of slides
			if(slides > 0 && !this.slides.next.length){
				// check if we are running in endless mode
				if(this.options.endless){
					// grab the last slide from the 'previous' slides stack and push it onto the 'next' slides stack
					this.slides.next.push(this.slides.previous.pop());
				// we're not
				}else{
					// fire last event
					this.fireEvent('last');
					// stop further processing
					return;
				}
			}
			// check if we're moving in 'previous' direction and are out of slides
			if(slides < 0 && !this.slides.previous.length){
				// check if we are running in endless mode
				if(this.options.endless){
					// grab the last slide from the 'next' slides stack and push it onto the 'previous' slides stack
					this.slides.previous.push(this.slides.next.pop());
				// we're not
				}else{
					// fire first event
					this.fireEvent('first');
					// stop further processing
					return;
				}
			}
			// get the old child about to be removed
			var oldChild = (slides > 0
				? this.container.getFirst(this.options.childSelector)
				: this.container.getLast(this.options.childSelector)
			);
			// get the new child we want to slide in
			var newChild = (slides > 0
				? this.slides.next.shift()
				: this.slides.previous.shift()
			);
			// generate the options object
			var opts = {'0': {}, '1': {}};
			// figure out which direction we are sliding in
			switch(this.options.direction){
				// vertical!
				case 'vertical':
					// figure out which margin to adjust depending on 'next' or 'previous' direction
					opts['0'][(slides > 0 ? 'margin-top' : 'margin-bottom')] = [0, -oldChild.getStyle('height').toInt()];
					opts['1'][(slides > 0 ? 'margin-bottom' : 'margin-top')] = [-oldChild.getStyle('height').toInt(), 0];
					// inject element into appropriate dom location after having adjusted style
					newChild.setStyle((slides > 0 ? 'margin-bottom' : 'margin-top'), -oldChild.getStyle('height').toInt());
				break;
				// horizontal!
				case 'horizontal':
				default:
					// figure out which margin to adjust depending on 'next' or 'previous' direction
					opts['0'][(slides > 0 ? 'margin-left' : 'margin-right')] = [0, -oldChild.getStyle('width').toInt()];
					opts['1'][(slides > 0 ? 'margin-right' : 'margin-left')] = [-oldChild.getStyle('width').toInt(), 0];
					// inject element into appropriate dom location after having adjusted style
					newChild.setStyle((slides > 0 ? 'margin-right' : 'margin-left'), -oldChild.getStyle('width').toInt());
				break;
			}
			// merge some options for the fx instance
			var fxOptions = $merge(this.options.fxOptions, {
				// onComplete event
				onComplete: function(){
					// when we're done, unset fx instance
					this.fx = null;
					// dispose of the old child
					if(slides > 0){
						// push it onto the 'previous' stack if we inserted a 'next' slide
						this.slides.previous.unshift(oldChild.dispose().setStyles({'margin-top':0,'margin-bottom':0,'margin-left':0,'margin-right':0}));
					// dispose of the old child
					}else{
						// push it onto the 'next' stack if we inserted a 'previous' slide
						this.slides.next.unshift(oldChild.dispose().setStyles({'margin-top':0,'margin-bottom':0,'margin-left':0,'margin-right':0}));
					}
					// do we have more slides to slide?
					if(slides.toInt()-1 > 0){
						// call slide!
						this.slide((slides > 0
							? slides - 1
							: slides + 1
						));
						return;
					// are we in auto sliding mode?
					}else if(this.options.auto){
						// check if we were heading for 'next'
						if(slides > 0){
							// call next after a delay
							this.timeoutId = this.next.delay(this.options.auto, this);
						// we were heading for 'previous'
						}else{
							// call previous after a delay
							this.timeoutId = this.previous.delay(this.options.auto, this);
						}
					}
					// check if we were heading for 'next'
					if(slides > 0){
						// trigger 'next' event
						this.fireEvent('next', [oldChild, newChild]);
					// we were heading for 'previous'
					}else{
						// trigger 'previous' event
						this.fireEvent('previous', [oldChild, newChild]);
					}
				}.bind(this)
			});
			// insert the element
			newChild.inject(this.container, (slides > 0 ? 'bottom' : 'top'));
			// create the fx instance
			this.fx = new Fx.Elements($$(oldChild, newChild), fxOptions).start(opts);
		}
	}
});
