var Magisticsfade = Class.create();
Magisticsfade.prototype = {
    loaded : false,
    initialize : function(elm, options) {
	var me = this, next, prev;
	this.elm = $(elm);
	var t_opt = {};
	for(t in Magisticsfade.Transition) {
	    var trans = Magisticsfade.Transition[t];
	    if(trans.className && this.elm.hasClassName(trans.className)) {
		t_opt = {transition:trans};
		break;
	    }
	}
	this.options = Object.extend(Object.clone(Magisticsfade.defaults),Object.extend(options || {},t_opt));
	this.options.interval = Math.max(2,this.options.interval);
	this.elm.makePositioned();
	this.slides = this.elm.childElements();
	if(this.options.random || this.elm.hasClassName(this.options.randomClassName)){
	    this.slides.sort(function(a,b){
		return me.rndm(-1,1);
	    });
	}
	if(this.elm.id) {
	    this.nextButton = $(this.elm.id + '-next');
	    this.prevButton = $(this.elm.id + '-previous');
	    this.stopButton = $(this.elm.id + '-stop');
	    if(this.nextButton) { Event.observe(this.nextButton, 'click', this.next.bind(this)); }
	    if(this.prevButton) { Event.observe(this.prevButton, 'click', this.previous.bind(this)); }
	    if(this.stopButton) { Event.observe(this.stopButton, 'click', this.stop.bind(this)); }
	    $$('a.' + this.elm.id + '-link').each(function(link, idx) {
		Event.observe(link,'click', me.anchor.bindAsEventListener(me),false);
	    });
	}
	var idx = this.getInitialSlide(); //check for URI target
	if(idx != 0) {
	    var neworder = [];
	    for(var x = idx,l = this.slides.length; x < l; x++) {
		neworder.push(this.slides[x]);
	    }
	    for(var x = 0; x < idx; x++) {
		neworder.push(this.slides[x]);
	    }
		this.slides = neworder;
	}

	var initialSlideIdx = 0;

	// Find index of initial slide and next one
	if (options.initialSlideId) {
	    for (var i = 0; i < this.slides.length; i++) {
	        if (this.slides[i].identify().match(options.initialSlideId)) {
		    initialSlideIdx = i;
		    break;
		}
	    }
	}
		
	this.counter = initialSlideIdx;
	var initialSlide = this.slides[initialSlideIdx];
	var nextSlideIdx = (initialSlideIdx + 1) % this.slides.length;
		
	this.updateLinksControls(this.getLinkElementForSlide(initialSlide));
	this.updatePrevNext();
		
	this.loadSlide(initialSlide, function() {
	    me.options.transition.prepare(me, initialSlideIdx);
	});
	this.loadSlide(this.slides[nextSlideIdx]);
	if(this.options.autoStart) { 
	    setTimeout(this.start.bind(this),this.rndm((this.options.interval-1)*1000,(this.options.interval+1)*1000));
	}
    },
    start : function() {
	if (!this.timerStopped) {
	    this.ready = true;
  	    this.timerDir = 1;
  	    this.timerCycle();
  	    this.timer = new PeriodicalExecuter(this.timerCycle.bind(this), this.options.interval);
	}
	return this.timer;
    },
    stop : function() {
	this.options.transition.cancel(this);
	this.stopTimer();
    },
    stopTimer : function() {
	if (this.timer) {
	    this.timer.stop();
	}
	this.timerStopped = true;
    },
    next : function(event) {
	Event.stop(event);
	var allowCycle = true;
	if (!this.options.wrapping && ((this.counter + 1) >= this.slides.length)) {
	    allowCycle = false;
	}
	if (allowCycle) {
	    // this.stop();
  	    this.cycle();
	}
    },
    previous : function(event) {
    	Event.stop(event);
	var allowCycle = true;
	if (!this.options.wrapping && ((this.counter - 1) < 0)) {
	    allowCycle = false;
	}
	if (allowCycle) {
    	    // this.stop();
	    this.cycle(-1);
	}
    },
    anchor : function(ev) {
	var element = Event.findElement(ev, "a");
	Event.stop(ev);
	if(element.href.match(/#(\w.+)/)) {
	    var loc = RegExp.$1;
	    // this.stopTimer();
	    this.cycleTo(loc, element);
	}
    },
    timerCycle : function() {
	if (!this.options.wrapping) {
	    if (((this.timerDir > 0) && ((this.counter + 1) >= this.slides.length)) || ((this.timerDir <= 0) && ((this.counter - 1) < 0))) {
		this.timerDir = -this.timerDir;
	    }
	}
	if (this.timerDir > 0) {
	    this.cycle();
	} else {
	    this.cycle(-1);
	}
    },
    cycle : function(dir) {
	if(!this.ready) { return; }
	    this.ready = false;
	    dir = (dir === -1) ? dir : 1;
	    var me = this, prevSlide, nextSlide, opt, fade;
	    prevSlide = this.slides[this.counter];
	    this.counter = this.loopCount(this.counter + dir);
	    if(this.counter == 0){
		this.loaded = true;
	    }
	nextSlide = this.slides[this.counter];
	this.doLoad(prevSlide, nextSlide, me, dir, this.getLinkElementForSlide(nextSlide));
    },
    cycleTo : function(slide, activeLink) {
	var me = this, prevSlide, nextSlide, opt, fade;
	if(!this.ready) { return; }
	    slide = $(slide);
	    var slideIds = this.slides.map(function(s,i){
		return s.id;
	    });
	    if(!this.slides.include(slide)) { return; }
	    var oldCounter = this.counter;
	    prevSlide = this.slides[this.counter];
	    var newCounter = this.slides.indexOf(slide);//slideIds.indexOf(slide.id);
	    if (oldCounter != newCounter) {
	        this.options.transition.cancel(this);
  	        this.counter = newCounter;
  	        this.ready = false;
  	        nextSlide = slide;
  	        var dir = (oldCounter < newCounter)? 1 : -1;
  	        this.doLoad(prevSlide, nextSlide, me, dir, activeLink);
	    }
	},
    updatePrevNext: function() {
	if (!this.options.wrapping) {
    	    if (this.nextButton) {
    		if (this.counter == (this.slides.length - 1)) {
		    this.nextButton.setStyle({
        		visibility: 'hidden'
        	    });
    		} else {
    		    this.nextButton.setStyle({
        		visibility: 'visible'
        	    });
    		}
    	    }
	    if (this.prevButton) {
    		if (this.counter == 0) {
    		    this.prevButton.setStyle({
        		visibility: 'hidden'
        	    });
    		} else {
		    this.prevButton.setStyle({
        		visibility: 'visible'
    		    });
    		}
    	    }
	}
    },
    doLoad : function(prevSlide, nextSlide, me, dir, activeLink) {
	// TODO add support for afterChange + better support for onChange
	if (this.options.controlsUpdate == 'beforeChange') {
	    this.updateLinksControls(activeLink);
	    this.updatePrevNext();
	}
	this.loadSlide(nextSlide, me.options.transition.cycle(prevSlide, nextSlide, me, dir, function() {
    	    if (me.options.controlsUpdate == 'onChange') {
		me.updateLinksControls(activeLink);
    		me.updatePrevNext();
    	    }
	}));
	if(!this.loaded) {
	    this.loadSlide(this.slides[this.loopCount(this.counter+1)]);
	}
    },
    getLinkElementForSlide: function(slide) {
	var found = null;
	if (slide.id && this.elm.id) {
	    found = $$('a.' + this.elm.id + '-link').find(function(link, idx) {
    		var loc = (link.href.match(/#(\w.+)/))? RegExp.$1 : null;
    		return (loc && (loc == slide.id));
	    });
	}
	return found;
    },
    updateLinksControls: function(activeControl) {
	// Add 'active' class to current link and remove it from others
	if(this.elm.id) {
	    $$('.' + this.elm.id + '-link').each(function(link) {
    		if (link == activeControl) {
    		    link.addClassName("active");
    		} else {
    		    link.removeClassName("active");
    		}
	    });
	}
    },
    loadSlide : function(slide, onload){
	var loaders = [], me = this, img, pnode, onloadFunction;
	onload = typeof onload === 'function' ? onload : function(){};
	onloadFunction = function() {
	    onload();
	    me.ready = true;
	};
	slide = $(slide);
	loaders = Selector.findChildElements(slide,[this.options.imageLoadSelector]);
	if(loaders.length && loaders[0].href !== ''){
	    img = document.createElement('img');
	    img.className = 'loadimage';
	    img.onload = onloadFunction;
	    img.src = loaders[0].href;
	    loaders[0].parentNode.replaceChild(img,loaders[0]);
	} else {
	    loaders = [];
	    loaders = Selector.findChildElements(slide, [this.options.ajaxLoadSelector]);
	    if(loaders.length && loaders[0].href !== ''){
		new Ajax.Updater(slide, loaders[0].href, {method:'get',onComplete:onloadFunction});
	    } else {
		onloadFunction();
	    }
	}
    },
    getInitialSlide : function() {
	var i = 0;
	if(document.location.href.match(/#(\w.+)/)) {
	    var loc = RegExp.$1.split('/');
	    var slide1 = this.slides.find(
	    function(slide, idx) {
		i = idx;
		return $(slide).match('#'+loc[1]);
	    });
		i = slide1 ? i : 0;
	}
	return i;
    },
    loopCount : function(c){
	if(c >= this.slides.length){
	    c = 0;
	} else if (c < 0) {
	    c = this.slides.length - 1;
	}
	    return c;
    },
    rndm : function(min, max){
	return Math.floor(Math.random() * (max - min + 1) + min);
    },
    timer : null,effect : null,ready : false
};

Magisticsfade.Transition = {};
Magisticsfade.Transition.Switch = {
	className : 'transition-switch',
	cycle : function(prev, next, show, dir, onSlideChange) {
		show.slides.without(next).each(function(s){
			$(s).hide();
		});
		$(next).show();
		fireEvent(onSlideChange);
	},
	cancel : function(show){},
	prepare : function(show, idx){
		show.slides.each(function(s,i){
			$(s).setStyle({display:(i === idx ? 'block' : 'none')});
		});	
	}
};
Magisticsfade.Transition.Magisticsfade = {
	className : 'transition-magisticsfade',
	cycle : function(prev, next, show, dir, onSlideChange) {
		var opt = show.options;
		show.effect = new Effect.Parallel([new Effect.Fade(prev ,{sync:true}),
			new Effect.Appear(next,{sync:true})],
			{duration: opt.duration, queue : 'Magisticsfade', afterFinish:function(){
				show.slides.without(next).each(function(s){
					$(s).setStyle({opacity:0});
				});
			}}
		);
		fireEvent(onSlideChange);
	},
	cancel : function(show){
		if(show.effect) { show.effect.cancel(); }
	},
	prepare : function(show, idx){
		show.slides.each(function(s,i){
			$(s).setStyle({opacity:(i === idx ? 1 : 0),visibility:'visible'});
		});	
	}
};
Magisticsfade.Transition.FadeOutFadeIn = {
    className : 'transition-fadeoutfadein',
    cycle : function(prev, next, show, dir, onSlideChange) {
	var opt = show.options;
	show.effect = new Effect.Fade(prev ,{ duration: opt.duration/2, afterFinish: function(){fireEvent(onSlideChange);
	show.effect = new Effect.Appear(next,{ duration: opt.duration/2 });
	    show.slides.without(next).each(function(s, i){
	        $(s).setStyle({opacity:0, display: 'none'});
	    });
	}
	});
    },
    cancel : function(show){
	if(show.effect) { show.effect.cancel(); }
    },
    prepare : function(show, idx){
	show.slides.each(function(s,i){
	    $(s).setStyle({opacity:(i === idx ? 1 : 0),visibility:'visible', display:(i === 0 ? 'block' : 'none')});
	});	
    }
};

Effect.DoNothing = Class.create();
Object.extend(Object.extend(Effect.DoNothing.prototype, Effect.Base.prototype), {
	initialize: function() {
		this.start({duration: 0});
	},
	update: Prototype.emptyFunction
});
Magisticsfade.Transition.FadeOutResizeFadeIn = {
    className : 'transition-fadeoutresizefadein',
    cycle : function(prev, next, show, dir, onSlideChange) {
	var opt = show.options;
	show.effect = new Effect.Fade(prev ,{
	    duration: (opt.duration-1)/2,
	    afterFinish: function(){
		show.slides.without(next).each(function(s){
		    $(s).setStyle({opacity:0});
		});
		fireEvent(onSlideChange);
		var slideDims = [next.getWidth(),next.getHeight()];
		var loadimg = Selector.findChildElements(next,['img.loadimage']);
		if(loadimg.length && loadimg[0].offsetWidth && loadimg[0].offsetHeight){
		    slideDims[0] += slideDims[0] < loadimg[0].offsetWidth ? loadimg[0].offsetWidth : 0;
		    slideDims[1] += slideDims[1] < loadimg[0].offsetHeight ? loadimg[0].offsetHeight : 0;
		}
		var showDims = [show.elm.getWidth(),show.elm.getHeight()];
		var scale = [(showDims[0] > 0 && slideDims[0] > 0 ? slideDims[0]/showDims[0] : 1)*100,(showDims[1] > 0 && slideDims[1] > 0 ? slideDims[1]/showDims[1] : 1)*100];
		show.effect = new Effect.Parallel([
			(scale[0] === 100 ? new Effect.DoNothing() : new Effect.Scale(show.elm,scale[0],{sync:true,scaleY:false,scaleContent:false})),
			(scale[1] === 100 ? new Effect.DoNothing() : new Effect.Scale(show.elm,scale[1],{sync:true,scaleX:false,scaleContent:false}))
		    ],
		    {
			duration: 1,
		        queue : 'FadeOutResizeFadeIn',
			afterFinish: function(){
			    show.effect = new Effect.Appear(next,{duration: (opt.duration-1)/2});
			}
		    }
		);
			}
	});
    },
    cancel : function(show){
	if(show.effect) { show.effect.cancel(); }
    },
    prepare : function(show, idx){
	var slideDims = [$(show.slides[0]).getWidth(),$(show.slides[0]).getHeight()];
	show.elm.setStyle({width:slideDims[0]+'px', height:slideDims[1]+'px'});
	show.slides.each(function(s,i){
	    $(s).setStyle({opacity:(i === idx ? 1 : 0),visibility:'visible'});
	});	
    }
};

Magisticsfade.Transition.SlideHorizontal = {
    // Height and width of parent slideshow element need to be set via CSS
    className : 'transition-slide',
    cycle : function(prev, next, show, dir, onSlideChange) {
	var opt = show.options;
	var dim = show.elm.getDimensions();
        show.slides.each(function(s){
    	    $(s).setStyle({zIndex:(next === s) ? 1 : 0});
	});
	$(next).setStyle({left: dir*(dim.width+0)+'px'});
	show.effect = new Effect.Move (next ,{
	    duration: opt.duration/2,
	    x: 0,
	    y: 0,
	    mode: 'absolute',
	    afterFinish: function(){
    		show.slides.without(next).each(function(s){
    		    $(s).setStyle({left: (dim.width+0)+'px'});
    		});
	    }
	});
	fireEvent(onSlideChange);
    },
    cancel : function(show){
	if(show.effect) { show.effect.cancel(); }
    },
    prepare : function(show, idx){
	var dim = show.elm.getDimensions();
	show.slides.each(function(s,i){
	    $(s).setStyle({
    		position:'absolute',
    		top:0,
    		left: (i === idx ? 0 : (dim.width+0)+'px'),
    		zIndex: (i === idx ? 1 : 0),
    		visibility:'visible'
	    });
    });
    show.elm.setStyle({overflow: 'hidden'});
    }
};

Magisticsfade.Transition.SmoothHorizontalScroll = {
    // Height and width of parent slideshow element need to be set via CSS
    className : 'transition-horizontal',
    cycle : function(prev, next, show, dir, onSlideChange) {
	var opt = show.options;
	var dim = show.elm.getDimensions();
	show.slides.each(function(s){
	    $(s).setStyle({zIndex:(next === s) ? 1 : 0});
	});
	$(next).setStyle({left: dir*(dim.width+0)+'px'});
	show.effect = new Effect.Parallel([
	    new Effect.Move(next, {duration: opt.duration/2, x: 0, y: 0, mode: 'absolute', sync: true}),
	    new Effect.Move(prev, {duration: opt.duration/2, x: -dir*(dim.width+0), y: 0, mode: 'absolute', sync: true})],
	    {duration: opt.duration, queue : 'SlideHorizontal', afterFinish:function(){
  		show.slides.without(next).each(function(s){
    		    $(s).setStyle({left: (dim.width+0)+'px'});
    		});
	    }}
	);
	fireEvent(onSlideChange);
    },
    cancel : function(show){
	if(show.effect) { show.effect.cancel(); }
    },
    prepare : function(show, idx){
	var dim = show.elm.getDimensions();
	show.slides.each(function(s,i){
	    $(s).setStyle({
		position:'absolute',
		top:0,
		left: (i === idx ? 0 : (dim.width+0)+'px'),
		zIndex: (i === idx ? 1 : 0),
		visibility:'visible'
	    });
	});
	show.elm.setStyle({overflow: 'hidden'});
    }
};

Magisticsfade.Transition.SmoothVerticalScroll = {
    // Height and width of parent slideshow element need to be set via CSS
    className : 'transition-vertical',
    cycle : function(prev, next, show, dir, onSlideChange) {
	var opt = show.options;
	var dim = show.elm.getDimensions();
	show.slides.each(function(s){
	    $(s).setStyle({zIndex:(next === s) ? 1 : 0});
	});
	$(next).setStyle({left: 0});
	show.effect = new Effect.Parallel([
	    new Effect.SlideUp(next, {duration: opt.duration/2, x: 0, y: 0, mode: 'absolute', sync: true, queue: 'end'}),
	    new Effect.SlideDown(prev, {duration: opt.duration/2, x: -dir*(dim.height+0), y: 0, mode: 'absolute', sync: true, queue: 'end'})],
	    {duration: opt.duration, queue : 'SlideVertical', afterFinish:function(){
  		show.slides.without(next).each(function(s){
    		    $(s).setStyle({left: 0+'px'});
    		});
	    }}
	);
	fireEvent(onSlideChange);
    },
    cancel : function(show){
	if(show.effect) { show.effect.cancel(); }
    },
    prepare : function(show, idx){
	var dim = show.elm.getDimensions();
	show.slides.each(function(s,i){
	    $(s).setStyle({
		position:'absolute',
		top:0+'px',
		left: 0,
		zIndex: (i === idx ? 1 : 0),
		visibility:'visible'
	    });
	});
	show.elm.setStyle({overflow: 'hidden'});
    }
};

Magisticsfade.defaults = {
    autoLoad : true,
    autoStart : true,
    random : false,
    wrapping : true,
    randomClassName : 'random',
    selectors : ['.magisticsfade'],
    imageLoadSelector : 'a.loadimage',
    ajaxLoadSelector : 'a.load',
    interval : 5,
    duration : 2,
    transition : Magisticsfade.Transition.Magisticsfade,
    controlsUpdate: 'onChange' //also: 'none', 'beforeChange', 'afterChange'
};
Magisticsfade.setup = function(options) {
    Object.extend(Magisticsfade.defaults,options);
};
Magisticsfade.load = function() {
    if(Magisticsfade.defaults.autoLoad) {
	Magisticsfade.defaults.selectors.each(function(s){
	    $$(s).each(function(c){
		return new Magisticsfade(c);
	    });
	});
    }
};

function fireEvent(event) {
    if (typeof event === 'function') {
	event();
    }   
}
