/*
  Script: itemScroller.js
  Author: Regina Luk
  adapted for Prototype by Chris Sanborn
  
  ** note: wrap ends is not implemented in this version  **
  ** note: horizontal scrolling is not implemented in this version  **
  ** TODO: implement wrapping and horizontal scrolling **
*/

/*
  Class: itemScroller
    Used to scroll items into view when there is more than one row of items.  Assumes unit is 'px' and that each item is the same size.

    When wrapEnds is true, we scroll left (next) by changing an item's margin-left from 0 to -(width of the item), then inject it after the last item in the list, and then changing the margin back to 0.  To scroll right (prev), we move the last item in the list to the beginning of the list, and change the margin of the item from -(width of the item) to 0.

    When wrapEnds is false (default), we scroll left and right by changing the margin-left of the itemsContainer instead of the item from 0 to negative and back to 0 depending on the direction we're scrolling.
    
    
    

  Arguments:
    element - html element containing all of the scrolled items and controls
    options - scroll options

  Options:
    containerSelector -
      css selector for html element containing scrolled items.
      Default: 'ul'
    itemSelector -
      css selector for scrolled items.
      Default: 'li'
    viewPortSelector -
      css selector for div with dimensional limits on what's viewable.
      Default: '.scroll_window'
    scrollerSelector -
      css selector for scroll controls.
      Grabs only first instance of this element from within the container element.
      Default: '.item_scroller'
    nextSelector -
      css selector for scroll next controls.
      Assumes control for on is an html anchor 'a', and off state is an html 'span'.
      ** note: This version makes no assumptions about the elements; it finds the first two occurences of the 
         specified css selector inside of our target object.
      Default: '.next'
    prevSelector -
      css selector for scroll prev controls.
      Assumes control for on is an html anchor 'a', and off state is an html 'span'.
      ** note: This version makes no assumptions about the elements; it finds the first two occurences of the 
         specified css selector inside of our target object.
      Default: '.prev'
    itemsPerScroll -
      number of items to move per scroll.
      Normalizes to 1 if passed a number that is less than 1 or this.options.wrapEnds is true.
      Used to control height of viewPort if mode is set to vertical.
      Normalizes to the largest possible number of items we can scroll when itemsPerScroll causes the scroll size to be larger than the viewable width in horizontal mode.
      Default: 1
    wrapEnds -
      Boolean used to check if we want to wrap items around when we move pass ends.
      Default: false
    mode -
      Set it to vertical or horizontal.
      Default: vertical
  
  Example:
    (start code)
    <style type="text/css">
      #vid_list_holder .scroll_window {overflow:hidden; height:450px;}
    </style>

    <div id="vid_list_holder">
      <!-- item_scroller needs to be inside our containing element -->
      <div class="item_scroller">
        <!-- 
          each anchor has a corresponding span for when wrapEnds is false
          that is used to turn off the link when you have reached one of the ends
        -->
        <a href="#next" class="next scroll">scroll up</a>
        <span class="next cap top"></span>
        <a href="#prev" class="scroll prev">scroll down</a>
        <span class="prev cap bottom"></span>
      </div>
      <div class="scroll_window">
        <ul>
          <li>scroll item</li>
          <li>scroll item</li>
          <li>scroll item</li>
        </ul>
      </div>
    </div>
*/

var itemScroller = Class.create({

  initialize: function(el,options) {
    this.options = Object.extend({
      containerSelector: 'ul',
      itemSelector: 'li',
      viewPortSelector: '.scroll_window',
      scrollerSelector: '.item_scroller',
      nextSelector: '.next',
      prevSelector: '.prev',
      itemsPerScroll: 4,
      wrapEnds: false,
      mode: 'vertical'
      },
      options || {}
      ); 
  

    this.el = {};
    this.el.mainModule = el;
    this.el.controls = {container: el.down(this.options.scrollerSelector)};
    this.el.controls.next = {
      on: this.el.controls.container.down(this.options.nextSelector),  // anchor
      off: this.el.controls.container.down(this.options.nextSelector, 1) // span
    };

    this.el.controls.prev = {
      on: this.el.controls.container.down(this.options.prevSelector),  // anchor
      off: this.el.controls.container.down(this.options.prevSelector, 1) // span
    };
    
    
    this.el.itemsContainer = this.el.mainModule.down(this.options.containerSelector); 
    while (this.el.itemsContainer.hasClassName('hide')) { // find the item that isn't hidden
      this.el.itemsContainer = this.el.itemsContainer.next(this.options.containerSelector);
    }
    this.el.items = this.el.itemsContainer.select(this.options.itemSelector);
		
    if (this.el.items.length == 0) { // list has no elements. Hide scroll btns and return.
      this.el.controls.prev.off.setStyle({display:'block'});
      this.el.controls.next.off.setStyle({display:'block'});
      return;  
		}
    // find out how tall and wide each item is and add that to any extra margins to find final height and width of each item
    this.itemSize = this.el.items[0].getDimensions();


    // are we scrolling horizontally or vertically?
    if (this.options.mode == 'horizontal')
    {
      // make sure items per scroll is not more than number of items visible or less than 1.
      this.viewableSize = this.el.mainModule.getSize().size.x - this.normalizeUnit(this.el.mainModule.getStyle('padding-left')) - this.normalizeUnit(this.el.mainModule.getStyle('padding-right'));
      if (this.options.wrapEnds || this.options.itemsPerScroll < 1) {
        this.options.itemsPerScroll = 1;
      } else {
        if (this.options.itemsPerScroll * this.itemSize.x > this.viewableSize) this.options.itemsPerScroll = (this.viewableSize/this.itemSize.x).toInt();
      }
      this.scrollData = {'style':'margin-left','current':0,'distance':this.options.itemsPerScroll*this.itemSize.x,'totalDistance':this.itemSize.x * this.el.items.length};
      this.el.itemsContainer.setStyles({
        'height':this.itemSize.y + 'px',
        'width':(this.itemSize.x * this.el.items.length) + 'px',
        'overflow':'hidden',
        'margin-left':'0'
      });
      //if(this.el.viewPort.isUndefined) {
        // if viewPort does not have width specified, set it
        //if (this.normalizeUnit(this.el.viewPort.getSize().size.x) == this.el.itemsContainer.getStyle('width').toInt()) {
          //this.el.viewPort.setStyle('width',this.viewableSize+'px');
        //}
      //}
    } else { // vertical
      if (this.options.itemsPerScroll < 1) this.options.itemsPerScroll = 1;

      // set viewableSize here so that it doesn't always have to be equal to the height of an item when wrapping ends
      this.viewableSize = this.options.itemsPerScroll * this.itemSize.height;
      if (this.options.wrapEnds) this.options.itemsPerScroll = 1;
      
      this.scrollData = {'moveX':0, 'current':0, 'style':'top', 'moveY':this.options.itemsPerScroll*this.itemSize.height,'totalDistance':this.itemSize.height * this.el.items.length};
      this.el.itemsContainer.setStyle({
        'height':(this.itemSize.height * this.el.items.length) + 'px',
        //'width':this.itemSize.x + 'px',
        //'overflow':'hidden',
        'top':0
      });
      // if viewPort does not have height specified, set it
      //if (this.normalizeUnit(this.el.viewPort.getSize().size.y) == this.el.itemsContainer.getStyle('height').toInt()) {
        //this.el.viewPort.setStyle('height',this.viewableSize+'px');
      //}
    }

    this.initControls();    
  },
  
  initControls: function() {
    // reset all controls to display:none
    this.el.controls.prev.on.setStyle({display:'none'});
    this.el.controls.prev.off.setStyle({display:'none'});
    this.el.controls.next.on.setStyle({display:'none'});
    this.el.controls.next.off.setStyle({display:'none'});

    if (this.options.itemsPerScroll+1 < this.el.items.length) { 
    // In our case, we're showing 5 items in the scroll window but only scrolling 4 at a time. 
    // Adding 1 to the test condition keeps a 5 item list from being scrollable.
    
      // show scroll controls if there are items to scroll
      this.el.controls.prev.on.observe('click', function(e) {
        //e = new Event(e);
        this.scroll.bind(this)('prev');
        e.stop();
      }.bind(this));
      this.el.controls.next.on.observe('click', function(e) {
        //e = new Event(e);
        this.scroll.bind(this)('next');
        e.stop();
      }.bind(this));

      this.el.controls.prev.on.setStyle({display:'block'});
      if (this.options.wrapEnds) {
        this.el.controls.next.on.setStyle({display:'block'});
      } else {
        this.el.controls.next.off.setStyle({display:'block'});
      }
      
      this.el.controls.container.setStyle({display:'block'});
      this.currentlyScrolling = false;
    } else { // nothing to scroll so show end caps
      this.el.controls.prev.off.setStyle({display:'block'});
      this.el.controls.next.off.setStyle({display:'block'});
    }
      
      
    
  },
  
  /*
    Property: normalizeUnit
      parses a string to an integer.
    
    Returns:
      0 if the string is not a number, otherwise, the string as an integer.
  */
  normalizeUnit: function(value) {
    return ( isNaN(parseInt(value)) || (typeof(parseInt(value)) != 'number') ) ? 0 : parseInt(value);
  },

  scroll: function(dir) {
    var scrollEffect, scrollEnd;

    // don't do anything if we're currently scrolling already or we might end up scrolling into the middle of something else
    if (!this.currentlyScrolling) {
      this.currentlyScrolling = true;
      // when this.options.wrapEnds is true, only scroll one item at a time
      // because we don't have anything to handle wrapping for more than one item yet.
      if (this.options.wrapEnds) {
        if (dir == 'prev') {
          // dir is prev
          this.el.itemsContainer.getLast().injectBefore(this.el.itemsContainer.getFirst());
          this.el.itemsContainer.getFirst().setStyle(this.scrollData.style,(this.scrollData.distance * (-1)) + 'px');
          scrollEffect = new Fx.Style(this.el.itemsContainer.getFirst(), this.scrollData.style, {
            onComplete: function() {
              this.currentlyScrolling = false;
            }.bind(this)
          });
          scrollEffect.start(0);
        } else {
          // dir is next
          scrollEffect = new Fx.Style(this.el.itemsContainer.getFirst(), this.scrollData.style, {
            onComplete: function() {
              this.el.itemsContainer.getFirst().injectAfter(this.el.itemsContainer.getLast());
              this.el.itemsContainer.getLast().setStyle(this.scrollData.style,'0');
              this.currentlyScrolling = false;
            }.bind(this)
          });
          // make sure we are not starting with auto value
          scrollEffect.start(this.normalizeUnit(this.el.itemsContainer.getFirst().getStyle(this.scrollData.style)),this.scrollData.distance * (-1));
        }
      } else { // not wrapping ends
        // when this.options.wrapEnds is not true, we can scroll one or more items


        if (dir == 'prev' && this.scrollData.moveY > 0 ) {
          this.scrollData.moveY = -(this.scrollData.moveY); //scroll up
        }  
        if (dir == 'next' && this.scrollData.moveY < 0) {
          this.scrollData.moveY = -(this.scrollData.moveY); //scroll down
        }


        scrollEffect = new Effect.Move(this.el.itemsContainer, {
          x: this.scrollData.moveX,          
          y: this.scrollData.moveY,
          afterFinish: function() {
            // the next line is needed when there are multiple lists loaded in the nav, 
            // since they all scroll, whether hidden or not. 
            if(this.el.itemsContainer.hasClassName('hide')) return; 
            
            this.scrollData.current = this.normalizeUnit(this.el.itemsContainer.getStyle(this.scrollData.style));
            
            if (this.scrollData.current < 0) {
              
              this.el.controls.next.on.setStyle({display:'block'});
              this.el.controls.next.off.setStyle({display:'none'});
              if (this.viewableSize >= (this.scrollData.totalDistance + this.scrollData.current)) {
                // if there's only one page left, turn off next
                this.el.controls.prev.on.setStyle({display:'none'});
                this.el.controls.prev.off.setStyle({display:'block'});
              } else if (this.el.controls.prev.on.getStyle('display') == 'none') {
                // if there are still more pages and next is turned off, turn it back on
                this.el.controls.prev.on.setStyle({display:'block'});
                this.el.controls.prev.off.setStyle({display:'none'});
              }
            } else {
              // if we're back at 0, turn off prev and turn on next
              this.el.controls.next.on.setStyle({display:'none'});
              this.el.controls.next.off.setStyle({display:'block'});
              this.el.controls.prev.on.setStyle({display:'block'});
              this.el.controls.prev.off.setStyle({display:'none'});
            }
            this.currentlyScrolling = false;
            
          }.bind(this)
        });
        
          scrollEnd = this.scrollData.current + (this.scrollData.distance);

        // don't let users scroll into nothingness
        if ((scrollEnd <= 0) && (Math.abs(scrollEnd) < this.scrollData.totalDistance)) {
          //scrollEffect.start(this.scrollData.current,scrollEnd);
          scrollEffect.from(this.scrollData.current);
          scrollEffect.to(scrollEnd);
        }
      } // end wrapEnds
    } // end currentlyScrolling
  }

});
