/*
* Crossfade
* Version 4.2 BETA January 2009
*
* Copyright (c) 2007 Millstream Web Software http://www.millstream.com.au
*
* 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 conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* *
*/

var Crossfade = Class.create();

Crossfade.prototype = {
  initialize : function(elm, options) {
    this.elm = $(elm);

    if (!this.elm || !this.elm.id)
    {
      throw "Undefined element / element's identifier not found!";
    }

    var me = this;

    // options
    var t_opt = {};
    for (t in Crossfade.Transition) {
      var trans = Crossfade.Transition[t];
      if (trans.className && this.elm.hasClassName(trans.className)) {
        t_opt = {transition:trans};
        break;
      }
    }
    this.options = Object.extend(Object.clone(Crossfade.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);
      });
    }

    // initiate prev & next  buttons
    this.nextButton = $(this.elm.id + '-next');
    if (this.nextButton) {
      Event.observe(this.nextButton, 'click', this.next.bind(this));
    }

    this.prevButton = $(this.elm.id + '-previous');
    if (this.prevButton) {
      Event.observe(this.prevButton, 'click', this.previous.bind(this));
    }

    // initiate links and highlights
    this.links = $$('a.' + this.elm.id + '-link');

    this.links.each(function(link) {
      Event.observe(link,'click', me.anchor.bindAsEventListener(me),false);
    });

    this.highlights = $$('a.' + this.elm.id + '-highlight');
    // if highlights are not found, use links
    if ((this.highlights.length == 0) && (this.links.length > 0))
    {
      this.highlights = this.links;
    }

    // estimate maximum height for all slides and normalize them
    var maxHeight = 0;
    for (var i = 0; i < this.slides.length; i++)
    {
      if (this.slides[i].getHeight() > maxHeight)
      {
        maxHeight = this.slides[i].getHeight();
      }
    }
    for (var i = 0; i < this.slides.length; i++)
    {
      this.slides[i].setStyle({height: maxHeight + 'px'});
    }

    // get initial slide index
    this.anchorRegEx = new RegExp('^#(.+)' + this.options.locationSuffix + '$');
    var idx = this.getInitialSlide(options.initialSlideId); //check for URI target

    // initial slide is always 0 as slides were reordered!
    this.counter = idx;
    var initialSlide = this.slides[idx];
    var nextSlideIdx = (idx + 1) % this.slides.length;

    this.updateLinksControls(this.getLinkElementForSlide(initialSlide));
    this.updatePrevNext();

    this.loadSlide(initialSlide, function() {
      me.options.transition.prepare(me, idx);
    });
    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);

    if (this.options.wrapping || ((this.counter + 1) < this.slides.length))
    {
      this.stop();
      this.cycle();
    }
  },
  previous : function(event) {
    Event.stop(event);

    if (this.options.wrapping || ((this.counter - 1) >= 0))
    {
      this.stop();
      this.cycle(-1);
    }
  },
  anchor : function(ev) {
    var element = Event.findElement(ev, "a");
    Event.stop(ev);
    if (element && element.hash.match(this.anchorRegEx)) {
      var loc = RegExp.$1;
      this.stopTimer();
      // update url fragment
      document.location.hash = element.hash;
      
      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 prevSlide = this.slides[this.counter];
    this.counter = this.loopCount(this.counter + dir);
    var nextSlide = this.slides[this.counter];
    this.doLoad(prevSlide, nextSlide, this, dir, this.getLinkElementForSlide(nextSlide));
  },
  cycleTo : function(slide, activeLink) {
    if (!this.ready) {
      return;
    }

    slide = $(slide);
    if (!this.slides.include(slide)) {
      return;
    }

    var oldCounter = this.counter;
    var prevSlide = this.slides[this.counter];
    var newCounter = this.slides.indexOf(slide);
    if (oldCounter != newCounter)
    {
      this.options.transition.cancel(this);
      this.counter = newCounter;
      this.ready = false;
      var nextSlide = slide;
      var dir = (oldCounter < newCounter)? 1 : -1;
      this.doLoad(prevSlide, nextSlide, this, 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();
      }
    }));

    // TODO not necessary?
    //this.loadSlide(this.slides[this.loopCount(this.counter+1)]);
  },
  getLinkElementForSlide: function(slide) {
    var found = null;
    var me = this;
    if (slide.id)
    {
      found = this.links.find(function(link, idx) {
        var loc = (link.hash.match(me.anchorRegEx))? RegExp.$1 : null;
        return (loc && (loc == slide.id));
      });
    }
    return found;
  },
  updateLinksControls: function(anchor) {
    this.highlights.each(function(node) {
      if (node.hash == anchor.hash)
      {
        node.addClassName("active");
      }
      else
      {
        node.removeClassName("active");
      }
    });
  },
  loadSlide : function(slide, onload){
    var loaders = [], me = this;
    onload = typeof onload === 'function' ? onload : function() {};
    var onloadFunction = function() {
        onload();
        me.ready = true;
    };
    slide = $(slide);
    loaders = Selector.findChildElements(slide, [this.options.imageLoadSelector]);
    if (loaders.length && loaders[0].href !== '') {
      var img = document.createElement('img');
      img.className = 'loadimage';
      img.onload = onloadFunction;
      img.src = loaders[0].href;
      loaders[0].parentNode.replaceChild(img,loaders[0]);
    }
    else {
      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(initialSlideId) {
    var i = 0;
    var locId = null;

    if (initialSlideId) {
      locId = initialSlideId;
    }
    else if (document.location.hash) {
      if (document.location.hash.match(this.anchorRegEx)) {
        locId = RegExp.$1;
      }
    }
    
    if (locId) {
      var initialSlide = this.slides.find(function(slide, idx) {
        i = idx;
        return slide.id == locId;
      });

      i = initialSlide ? i : 0;
    }
    
    return i;
  },
  loopCount : function(index) {
    if (index >= this.slides.length) {
      index = 0;
    }
    else if (index < 0) {
      index = this.slides.length - 1;
    }
    return index;
  },
  rndm : function(min, max){
    return Math.floor(Math.random() * (max - min + 1) + min);
  },
  timer : null,effect : null,ready : false
};
Crossfade.Transition = {};
Crossfade.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')});
    });
  }
};
Crossfade.Transition.Crossfade = {
  className : 'transition-crossfade',
  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 : 'Crossfade', 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'});
    });
  }
};
Crossfade.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 === idx ? '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
});
Crossfade.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'});
    });
  }
};

Crossfade.Transition.SlideHorizontal = {
  // Height and width of parent slideshow element need to be set via CSS
  className : 'transition-slide-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+50)+'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+50)+'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+50)+'px'),
        zIndex: (i === idx ? 1 : 0),
        visibility:'visible'
      });
    });
    show.elm.setStyle({overflow: 'hidden'});
  }
};

Crossfade.Transition.SmoothHorizontalScroll = {
  // Height and width of parent slideshow element need to be set via CSS
  className : 'transition-smooth-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+50)+'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+50), 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+50)+'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+50)+'px'),
        zIndex: (i === idx ? 1 : 0),
        visibility:'visible'
      });
      // do not change it to 'i == idx' - it won't work! first slide must be relative!
      if (i == 0)
      {
        $(s).setStyle({
          position:'relative'
        });
      }
    });
    show.elm.setStyle({overflow: 'hidden'});
  }
};
Crossfade.defaults = {
  autoLoad : true,
  autoStart : true,
  random : false,
  wrapping : true,
  randomClassName : 'random',
  selectors : ['.crossfade'],
  imageLoadSelector : 'a.loadimage',
  ajaxLoadSelector : 'a.load',
  interval : 5,
  duration : 2,
  locationSuffix: 'Section',
  transition : Crossfade.Transition.Crossfade,
  controlsUpdate: 'onChange' //also: 'none', 'beforeChange', 'afterChange'
};
Crossfade.setup = function(options) {
  Object.extend(Crossfade.defaults,options);
};
Crossfade.load = function() {
  if(Crossfade.defaults.autoLoad) {
    Crossfade.defaults.selectors.each(function(s){
      $$(s).each(function(c){
        return new Crossfade(c);
      });
    });
  }
};

if(window.FastInit) {
  FastInit.addOnLoad(Crossfade.load);
} else {
  Event.observe(window, 'load', Crossfade.load);
}

function fireEvent(event)
{
  if (typeof event === 'function')
  {
    event();
  }
}

