var Pagination = Class.create();
Object.extend(Pagination.prototype, {
	initialize: function(elementSelector, elementsPerPage, status){
		this.elCount = elementsPerPage;
		this.elements = $$(elementSelector);
		this.status = status ? $(status) : false;
		this.pageCount = Math.ceil(this.elements.length / this.elCount);
		this.moveQueue = [];
		this.curPage = 1;
		this.elements.invoke('hide');
		this.elements.invoke('setOpacity', 100);
		this.getElements(0, this.elCount).invoke('show');
		this.updateStatus();
	},
	getElements: function(startindex, endindex) {
		var range = $R(startindex, endindex - 1);
		return this.elements.findAll(function(element, index){
			return range.include(index);
		});
	},
	getVisibleElements: function(){
		return this.elements.findAll(function(element){
			return element.visible();
		});
	},
	getCurrentElements: function(){
		var startindex = (this.curPage - 1) * this.elCount;
		return this.getElements(startindex, startindex + this.elCount);
	},
	lock: function(){
		this.locked = true;
	},
	unlock: function(){
		this.locked = false;
		if(this.moveQueue.length) (this.moveQueue.shift())();
	},
	updateStatus: function() {
		if(this.status){
			this.status.update("Page " + this.curPage + " of " + this.pageCount);
		}
	},
	setPage: function(pageIndex) {
		if(this.locked) return;
		this.lock();
		var dirForward = pageIndex >= this.curPage;
		this.curPage = Math.min(this.pageCount, Math.max(1, pageIndex));
		this.updateStatus();
		if(dirForward)
			this.swapStep1(this.getVisibleElements(), this.getCurrentElements());
		else
			this.swapStep1(this.getVisibleElements().reverse(), this.getCurrentElements().reverse());
	},
	nextPage: function(){
		if(this.locked){
			this.moveQueue.push(this.nextPage.bind(this));
			return;
		}
		if(this.curPage < this.pageCount) this.setPage(this.curPage + 1);
		else if(this.moveQueue.length) this.moveQueue = [];
	},
	prevPage: function(){
		if(this.locked){
			this.moveQueue.push(this.prevPage.bind(this));
			return;
		}
		if(this.curPage > 1) this.setPage(this.curPage - 1);
		else if(this.moveQueue.length) this.moveQueue = [];
	},
	swapStep1: function(oldComponents, newComponents){
		new Effect.DelayedChain('Opacity', oldComponents.toArray(), {
			from: 1.0,
			to: 0.0001,
			duration: 0.5,
			afterFinish: this.swapStep2.bind(this, oldComponents, newComponents)
		});
	},
	swapStep2: function(oldComponents, newComponents){
		oldComponents.invoke('hide');
		newComponents.invoke('show');
		newComponents.invoke('setOpacity', 0.01);
		new Effect.DelayedChain('Opacity', newComponents.toArray(), {
			from: 0.0001,
			to: 1.0,
			duration: 0.5,
			afterFinish: this.unlock.bind(this)
		});
	}
});

Effect.DelayedChain = Class.create();
Object.extend(Effect.DelayedChain.prototype, {
	initialize: function(effect, elements, options, timeout){
		this.elements = elements;
		this.effect = effect;
		this.timeout = timeout || 100;
		this.options = Object.extend({}, options || {});
		this.afterFinish = this.options.afterFinish || Prototype.emptyFunction;
		this.options.afterFinish = Prototype.emptyFunction;
		setTimeout(this.action.bind(this),1);
	},
	action: function() {
		if(this.elements.length){ 
			new Effect[this.effect](this.elements.shift(), this.options);
			setTimeout(this.action.bind(this), this.timeout);
		} else {
			if(this.afterFinish) this.afterFinish();
		}
	}
});

Effect.Chain = Class.create();
Object.extend(Effect.Chain.prototype, {
	initialize: function(effect, elements, options){
		this.elements = elements || [];
		this.effect = effect;
		this.options = options || {};
		this.afterFinish = this.options.afterFinish || Prototype.emptyFunction;
		this.options.afterFinish = this.nextEffect.bind(this);
		setTimeout(this.nextEffect.bind(this), 1);
	},
	nextEffect: function(){
		if(this.elements.length)
			new Effect[this.effect](this.elements.shift(), this.options);
		else
			this.afterFinish();
	}
});