Element.addMethods({
  makeStickyContainer: function(el) {
    new StickyContainer(el);
  }
});

var StickyContainer = Class.create({
  initialize: function(el) {
    this.scrolledEl = el;
    this.offset = 10;
    this.topBounds = el.cumulativeOffset().top;
    //this is the number that represents when the bottom of the container hits the bottom
    //of its parent and where it stops
    this.bottomBounds = el.up().getHeight() + this.topBounds - el.getHeight();
    
    this.fixed = false;
    this.widthSwitch = $('shell').getWidth() < document.viewport.getWidth();
    
    this.ie6 = window.XMLHttpRequest ? false : true;
    if (!this.ie6 && this.widthSwitch) {
      Event.observe(window, 'scroll', this.testPosition.bind(this));
      //if user got to an area on the page via anchor check where they are
      this.testPosition();
    }
    else {
      document.observe('scroll:stopped', this.fixToViewportIE6.bind(this));
      this.fixToViewportIE6();
    }
    
    var quickLinks = $('sticky-quick-links');
    if (quickLinks) quickLinks.observe('click', this.updatePosition.bind(this));
    this.viewportOffset = 0;
  },
  
  testPosition: function(e) {
    this.viewportOffset = document.viewport.getScrollOffsets().top;
    this.containerOffset = $('primary').viewportOffset().top;
    if (this.containerOffset < 0 && !this.fixed && !(this.viewportOffset > this.bottomBounds)) this.fixToViewport();
    else if (this.containerOffset > 0) this.makeStatic();
    else if (this.viewportOffset > this.bottomBounds) this.stopAtBottom();      
  },
  
  testPositionIE6: function(e) {
    this.viewportOffset = document.viewport.getScrollOffsets().top;
    this.containerOffset = $('primary').viewportOffset().top;
    
    if (this.containerOffset < 0 && !this.fixed && !(this.viewportOffset > this.bottomBounds)) this.fixed = true;
    //make element fixed in ie6
    if (this.fixed) this.fixToViewportIE6();
    //hit the top
    if (this.containerOffset > 0) this.makeStaticMorph();
    //hit the bottom
    else if (this.viewportOffset > this.bottomBounds) this.stopAtBottom();
  },
  
  //attachs the el to the viewport
  fixToViewport: function() {
    this.scrolledEl.setStyle({'position': 'fixed', 'top': 0, 'bottom': ''});
    this.fixed = true;
  },
  //attach the el to the viewport in ie6
  fixToViewportIE6: function() {
    //'top': (this.viewportOffset - this.topBounds) + 'px'
    this.viewportOffset = document.viewport.getScrollOffsets().top;
    var top = '';
    if (this.viewportOffset < this.topBounds) top = 0;
    else if (this.viewportOffset < this.bottomBounds) top = this.viewportOffset - this.topBounds;
    else{
      top = this.scrolledEl.up().getHeight() - this.scrolledEl.getHeight() }
    this.scrolledEl.setStyle({'position': 'absolute'});
    this.scrolledEl.morph({'top': top + 'px'});
    this.fixed = true; 
  },
  
  //returns scrolling element to normal document position
  makeStatic: function() {
    this.scrolledEl.setStyle({'position': 'static'});
    this.fixed = false;
  },
  
  //returns element to top bounds in ie6 and when viewport is to small
  makeStaticMorph: function() {
    this.scrolledEl.setStyle({'position': 'static'});
    this.fixed = false;
  },
  
  //element has hit the bottom of container 
  stopAtBottom: function() {
    this.scrolledEl.setStyle({'position': 'absolute', 'bottom': 0, 'top': ''})
    this.fixed = false;
  },
  
  updatePosition: function(e) {
    if (!$(e.target).match('a')) return;
    this.fixed = true;
    if (!this.ie6) this.testPosition();
    else this.testPositionIE6();
  }
});



var __debug = false;
/*


Main SlideShow Class:

EXAMPLE USAGE:

  new SlideShow('container', { slideDuration: 2 });

*/
/*
  TODO add an option so that the first slide starts visible, instead of fading in
*/
/*
  TODO pauseOnMouseover doesn't respect the slideDuration
*/
/*
  TODO add a way to delete or remove a slideShow
*/
var SlideShow = Class.create({
  
  initialize: function(element, options){
    this.element = element;
    this.options = options;
    this.defaultOptions = $H({
      autoPlay: true,
      slideDuration: 5,
      transitionDuration: 1,
      loop: true,
      crossFade: false,
      pauseOnMouseover: true,
      slidesSelector: '> *',
      startHidden: true,
      events: { init: 'dom:loaded', play: 'window:loaded' },
      beforeStart: Prototype.emptyFunction, afterFinish: Prototype.emptyFunction
    });
    
    // assigning the options to internal variables
    this.defaultOptions.merge(this.options).each(function(option){
      this[option[0]] = option[1];
    }.bind(this));
    
    this.events = $H(this.defaultOptions.get('events')).merge(this.events).toObject();
    
    if (this.autoPlay) {
      this.initEventFunction = function(){
        this.init();
        // only allow the slideShow to observe one 'dom:loaded' event
        if (this.events.init == 'dom:loaded')
          document.stopObserving(this.events.init, this.initEventFunction);
      }.bind(this);
      document.observe(this.events.init, this.initEventFunction);
    }
  },
  
  init: function(){
    if (!$(this.element)) return;
    this.root = $(this.element);
    this.id = this.root.identify();
    this.fireEvent('initializing', { slideShow: this });
    this.slides = $$('#' + String(this.id) + ' ' + String(this.slidesSelector));
    this.loopCount = 0;
    this.slideCount = 0;
    this.slideIndex = 0;
    this.paused = false;
    this.started = false;
    
    this.prep();
    
    this.playEventFunction = function(){
      this.beforeStart();
      this.play();
      if (this.pauseOnMouseover)
        this.root.observe('mouseover', this.pause.bind(this)).observe('mouseout', this.play.bind(this));
      
      // only let window:loaded start the slideShow once
      if (this.events.play == 'window:loaded')
        document.stopObserving(this.events.play, this.playEventFunction);
    }.bind(this);
    document.observe(this.events.play, this.playEventFunction);
    
    this.fireEvent('initialized', { slideShow: this });
  },
  
  prep: function(){
    this.root.makePositioned();
    
    for (var i=0; i < this.slides.length; i++) {
      this.slides[i].setStyle({ position: 'absolute', zIndex: i });
      if (this.startHidden || (!this.startHidden && i != 0))
        this.prepSlide(this.slides[i]);
    };
    this.fireEvent('prepped', { slideShow: this });
  },
  
  prepSlide: function(slide){
    return slide.setStyle({ display: 'none', opacity: 0 });
  },
  
  play: function(e){
    // test 1: autoPlay true,  pauseOnMouseover true
    // test 2: autoPlay false, pauseOnMouseover true,  (manually starting)
    // test 3: autoPlay true,  pauseOnMouseover false
    // test 4: autoPlay false, pauseOnMouseover false, (manually starting)
    
    // prevent mousing out from causing the slideShow to start
    if (e && !this.autoPlay && this.pauseOnMouseover && this.loopCount == 0) return;
    
    // if (this.paused) return;
    // prevent against internal mouse movements from triggering a transition
    if (e && this.mouseIsWithinSlideArea(e)) return;
    
    this.started = true;
    this.paused = false;
    this.fireEvent('started', { slideShow: this });
    this.transition();
  },
  
  pause: function(e){
    // if it's not started playing, or if it's already paused, or if the mouse isn't within the slide area return
    if (!this.started || this.paused || (e && !this.mouseIsWithinSlideArea(e))) return;
    
    this.paused = true;
    this.abortNextTransition();
    
    // queue paused test
    // this.setupPausedTest();
    
    this.fireEvent('paused', { slideShow: this });
  },
  
  transition: function(){
    if (this.paused) return;
    if (this.nextTransition) this.nextTransition.stop();
    
    this.coming = this.slides[this.slideIndex];
    this.going = this.coming.previous() || this.slides.last();
    
    var coming = this.coming; var going = this.going;
    
    if (this.slideCount > 0 && this.coming == this.slides.first() && this.going == this.slides.last()) {
      this.fireEvent('looped', { slideShow: this });
      this.loopCount++;
      this.afterFinish();
      if (!this.loop) return;
    }
    
    this.slideCount++;
    this.slideIndex++;
    if (this.slideIndex >= this.slides.length) this.slideIndex = 0;
    
    // if not fresh start, fade
    if (going != coming) {
      // if crossfade
      if (this.crossFade) {
        new Effect.Parallel(
          [new Effect.Appear(coming), new Effect.Fade(going)],
          {
            duration: this.transitionDuration,
            afterFinish: function(){
              this.prepSlide(going);
              this.afterTransitionEffect();
            }.bind(this)
          }
        );
      } else {
        going.fade({
          duration: this.transitionDuration / 2,
          afterFinish: function(){
            this.prepSlide(going);
            coming.appear({
              duration: this.transitionDuration / 2,
              afterFinish: this.afterTransitionEffect.bind(this)
            });
          }.bind(this)
        });
      }
    }
    // fade in the first time
    else {
      coming.appear({
        duration: this.transitionDuration / 2,
        afterFinish: this.afterTransitionEffect.bind(this)
      });
    }
    this.fireEvent('transitioned', { slideShow: this, coming: coming, going: going, loopCount: this.loopCount });
  },
  
  afterTransitionEffect: function(){
    this.scheduleNextTransition();
  },
  
  scheduleNextTransition: function(){
    if (this.slideDuration <= 0) return;
    this.nextTransition = new PeriodicalExecuter(function(nextTransition){
      if (this.paused) return;
      this.transition();
    }.bind(this), this.slideDuration);
  },
  
  abortNextTransition: function(){
    if (this.nextTransition) {
      this.nextTransition.stop();
    }
  },
  
  fireEvent: function(name, memo){
    cl('SlideShow_' + this.root.id + ':' + name);
    this.root.fire('SlideShow_' + this.root.id + ':' + name, memo);
  },
  
  mouseIsWithinSlideArea: function(e){
    var maxX = this.root.cumulativeOffset().left + this.root.getWidth();
    var minX = this.root.cumulativeOffset().left;
    var maxY = this.root.cumulativeOffset().top + this.root.getHeight();
    var minY = this.root.cumulativeOffset().top;
    
    // for whatever reason the minX and the maxY need to be checked like this
    if (minX == e.pointerX() || maxY == e.pointerY()) return false;
    if ($R(minX, maxX).include(e.pointerX()) && $R(minY, maxY).include(e.pointerY())) {
      return true; } else { return false; }
  },
  
  end: function(){
    this.pause();
    document.stopObserving(this.events.play, this.playEventFunction);
    document.stopObserving(this.events.init, this.initEventFunction);
  },
  
  remove: function(){
    this.end();
    this.root.remove();
    this.fireEvent('removed');
    // window['slideShow'] = null;
  }
  /*,
  setupPausedTest: function(){
    this.schedulePausedTest = function(ev){
      this.pausedTest = new PeriodicalExecuter(function(pe){
        cl('pausedTest: { paused(' + this.paused + '), !this.mouseIsWithinSlideArea(ev): ' + !this.mouseIsWithinSlideArea(ev));
        if (this.paused && !this.mouseIsWithinSlideArea(ev)) {
          cl('force play');
          this.play();
        }
        this.pausedTest.stop();
        this.root.stopObserving('mousemove', this.schedulePausedTest);
      }.bind(this), .2);
    }.bind(this);
    this.root.observe('mousemove', this.schedulePausedTest);
  }
  */
});


/*
  
  controls
    previous, next, play, pause, toggle
    numericals?
    keyboard shortcuts?

*/
/*
var SlideShowWithControls = Class.create(SlideShow, {
  initialize: function($super, element, controls, options){
    this.$super = $super;
    this.element = element;
    this.controls = controls;
    this.options = options;
    this.$super(this.element, this.options);
    // cl($super);
  }
});
*/


Event.observe(window, 'load', function(e){
  document.fire('window:loaded');
});

// utility function
function cl (str) {
  if (__debug)
    Try.these(function(){ console.log(str); });
}

/* Utility Functions */
// function cl(str){
//   if(_debugMode && console) t(
//     function(){
//       console.log(str);
//     }
//   );
// }

function t(f) {
  Try.these(f);
}

/*
from: http://mislav.caboo.se/js/when-available-in-prototype/
When object is available, do function fn.
*/
function when(obj, fn) {
  if (Object.isString(obj)) obj = /^[\w-]+$/.test(obj) ? $(obj) : $(document.body).down(obj)
  if (Object.isArray(obj) && !obj.length) return
  if (obj) fn(obj)
}


Element.addMethods({
  /*


  Makes 32 bit PNG's transparency work in Internet Explorer 6

    * Dependent on "Prototype JavaScript framework (1.6.0)":http://www.prototypejs.org/2007/8/15/prototype-1-6-0-release-candidate
    * Works on *img elements* and on *background images of elements*
    * PNG's can be used as backgrounds. However, *image tiling will not work*
    * *Safe to use* - You don't have to make an exception or write separate code for IE6
    * Background PNG's used in :hover's might need another application of the method


  Example Usages:

   $('yourPNG').pngHack();
   $$('div#fixMe', 'img#andMe', 'img.andUsTo').invoke('pngHack');

  */
  pngHack: function(el){
    var el = $(el);
    if (!Prototype.Browser.IE) return el;
    var gif = '/images/s.gif';
    if ((el.match('img')) && (el.src.include('png'))){
      var alphaImgSrc  = el.src;
      var sizingMethod = 'scale';
      el.src = gif;
    } else if (el.getStyle('backgroundImage').include('png')){
      var bgc = el.getStyle('backgroundColor') || '';
      var alphaImgSrc = el.getStyle('backgroundImage').gsub(/url\(|\)|'|"/, '');
      var sizingMethod = 'crop';
      el.setStyle({ background: [bgc, ' url(', gif, ') no-repeat'].join('') });
    } else {
      return el;
    }
    el.runtimeStyle.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="#{al}",sizingMethod="#{sz}")'.interpolate({ al: alphaImgSrc, sz: sizingMethod });
    return el;
  },
  /*
    
    centers the element vertically and horizontally within it's parent element
      requires positioning of the element, and dimensions
  */
  center: function(el){
    var el = $(el);
    var parent = el.up();
    return el.setStyle({
      left: (parent.getWidth()  - el.getWidth())  / 2 + 'px',
      top:  (parent.getHeight() - el.getHeight()) / 2 + 'px'
    });
  },
  fakeMinHeight: function(el){
    var el = $(el);
    if (!Prototype.Browser.IE) return el;
    if (Number(el.getStyle('height').gsub('px', '')) < Number(el.getStyle('minHeight').gsub('px', ''))) {
      el.setStyle({ height: el.getStyle('minHeight') });
    }
    return el;
  }
});

/*


Equalize the heights of columns
*/
Object.extend(Array.prototype, {
  equalizeHeights: function(){
    //equalize the heights of columns
    if (this.any(function(el){
      return Object.isElement(el)
    }))
      var maxHeight = this.map(function(el){
          return el.getHeight();
      }).max();
      this.each(function(el) {
        if (el.getHeight() != maxHeight) {
          el.setStyle({height: 
            (maxHeight - Number(el.getStyle('padding-top').sub('px', '')) -
            Number(el.getStyle('padding-bottom').sub('px', ''))) + 'px'
          });
        }
      });
    return this;
  }
});


/*


Adds the ability to random a number
*/
Object.extend(Number.prototype, {
  rand: function(){
    return Math.ceil(this * Math.random());
  }
});
