Element.addMethods({
  /* gets the margins of an element:
   * returns either the measured side, or the amount set all sides
   */
  getMargin: function(element) {
    var margin = (element.getStyle('margin')||"").sub('px','')*1.0;
    
    var margin_left = element.getStyle('margin-left')||"";
    var margin_right = element.getStyle('margin-right')||"";
    var margin_top = element.getStyle('margin-top')||"";
    var margin_bottom = element.getStyle('margin-bottom')||"";
    
    margin_left = margin_left.blank() ? margin : margin_left.sub('px','')*1.0;
    margin_right = margin_right.blank() ? margin : margin_right.sub('px','')*1.0;
    margin_top = margin_top.blank() ? margin : margin_top.sub('px','')*1.0;
    margin_bottom = margin_bottom.blank() ? margin : margin_bottom.sub('px','')*1.0;
    
    var result = [ margin_top, margin_right, margin_bottom, margin_left ];
    
    result.left = margin_left;
    result.right = margin_right;
    result.top = margin_top;
    result.bottom = margin_bottom;
    
    return result;
  }
});

/* A slideshow handler pass it this structure as element:
 
<div>
  <ol class="focus">
    <li>Slide content</li>
  </ol>
  <div class="dots_wrapper">
    <ol class="dots">
      <li>Whatever a dot looks like</li>
    </ol>
    <div class="slider"></div>
  </div>
  <div class="thumbs_wrapper">
    <ol class="thumbs">
      <li>Whatever a thumb looks like</li>
    </ol>
  </div>
</div>

  The elements can be in any order so long as this structure is maintained.
  Any div's may be replaced by other tag.

  The slide show consists of three parts:

  1) the focus frame, a slide comes in from the bottom and takes up the frame
  2) the dots, these present a line of dots for the user to scroll through
  3) the thumbs, this is a filmstrip of smaller versions of the slides the
     user can click through.

  The user may click either the dots or the thumbs to navigate back and forth
  (when using the thumbs, only the right-most and left-most displayed thumb
  will cause the thumbs to scroll).  Also clicking on the thumb will select
  a new slide (displayed in the focus).

  The only option to pass is how much offset to apply to the slider's edge.
*/
Slideshow = Class.create({
  initialize: function( element, options ) {
    this.element = $(element);
    this.options = options;
    this.options.slider_adjust = this.options.slider_adjust || { top: 0, left: 0 };
    this.options.width = this.options.width || 4;

    this.left = this.element.down('.left');
    this.right = this.element.down('.right');
    /* when setting opacity, it seems to oveflow sometimes causing flicker, we
     * get around that by setting the max opacity to 0.99
     */
    this.left_effect = new Effect.Style( this.left, 'opacity', { unit: '', start: 0.0, end: 0.99 } );
    this.right_effect = new Effect.Style( this.right, 'opacity', { unit: '', start: 0.0, end: 0.99 } );

    this.thumbs = this.element.down('.thumbs');
    var thumbs_margin = this.thumbs.down('li').getMargin();
    this._thumbs_width = this.thumbs.down('li').getWidth() + thumbs_margin.left + thumbs_margin.right;
    this.thumb_effect = new Effect.Style( this.thumbs, 'left', {
      start: 0,
      end: 0,
      beforeStart: this._before_thumbs_effect.bind(this),
      afterFinish: this._after_thumbs_effect.bind(this) } );

    this.thumbs.observe('click', this._thumb_select.bindAsEventListener(this) );

    this.focus = this.element.down('.focus');
    if( this.focus ) {
      this.current_focus = this.focus.down('li.selected');
      this.focus_top = (this.current_focus.getStyle('top')||"").sub('px','');
      this.focus_top = this.focus_top.blank() ? 0 : this.focus_top*1.0;
      this.offset_index = this.current_focus.previousSiblings().length;
    } else {
      this.offset_index = 0;
    }
    this.selected_index = this.offset_index;
    
    this.dots = this.element.down('.dots');
    this._dots_offset = this.dots.down('li').positionedOffset();
    var dots_margin = this.dots.down('li').getMargin();
    this._dots_offset_adjust = {
      top: -dots_margin.top + this.options.slider_adjust.top,
      left: -dots_margin.left + this.options.slider_adjust.left };
    this._dots_width = this.dots.down('li').getWidth() + dots_margin.left + dots_margin.right;
    this.slider = this.element.down('.slider');
    this._drag_offset = this._dots_width * this.offset_index;
    this._dragging = false;
    this._render_slider();
    this.dots_wrapper = this.element.down('.dots_wrapper');
    this.dots_wrapper.observe('click', this._dots_do.bindAsEventListener(this) );
    this.dots_wrapper.observe('mousedown', this._dots_start.bindAsEventListener(this) );
    this.dots_wrapper.observe('mouseup', this._dots_end.bindAsEventListener(this) );
    this.dots_wrapper.observe('mousemove', this._dots_update.bindAsEventListener(this) );
  },

  /* renders the slider's new position */
  _render_slider: function() {
    this.slider.setStyle({
      display: 'block',
      //top: 280+'px',
			//ask adam about this... dynamic top value:
			top: this._dots_offset.top+this._dots_offset_adjust.top+'px',
      left: (this._dots_offset.left+this._dots_offset_adjust.left+this._drag_offset)+'px'});
  },

  /* move the slider to the event's position and drop it, snap to position */
  _dots_do: function(e) {
    e.stop();
    this._dragging = true;
    this._dots_update(e);
    this._dragging = false;
    this._dots_snap();
  },

  /* pick up the slider and render it at the current position */
  _dots_start: function(e) {
    e.stop();
    this._dragging = true;
    this._dots_update(e);
  },

  /* drop the slider, snap to position */
  _dots_end: function(e) {
    e.stop();
    this._dots_update(e);
    this._dragging = false;
    this._dots_snap();
  },

  /* dots update figures out where the slider should be based on the mouse
   * position, it renders the new position at the end of call,
   * it refuses to update if we are not dragging */
  _dots_update: function(e) {
    e.stop();
    
    if( !this._dragging )
      return;

    var x_min = this.dots.select('li:first-child')[0].cumulativeOffset().left;
    var x_max = this.dots.select('li:nth-last-child('+this.options.width+')')[0].cumulativeOffset().left;
    var x = e.pointerX() - this._dots_width*this.options.width/2.0;
    this._drag_offset = Math.min( x_max, Math.max( x_min, x ) ) - x_min;
    this.select( this._nearest_index(), { slider: false, focus: false } );
    
    this._render_slider();
  },

  /* what is the closest index to slider is at currently? */
  _nearest_index: function() {
    var offset = this._drag_offset;
    var dist = offset % this._dots_width;
    offset -= dist;
    if( dist > this._dots_width / 2 )
      offset += this._dots_width;
    return offset / this._dots_width;
  },

  /* snap the slider to the closest dot, and render */
  _dots_snap: function( index ) {
    var nearest_index = index || index == 0 ? index : this._nearest_index();
    this._drag_offset = nearest_index * this._dots_width;
    this._render_slider();
  },

  /* where is the thumbnail strip right now, in pixels? */
  _thumbs_offset: function() {
    var pos = (this.thumbs.getStyle('left')||"").sub('px','');
    return pos.blank() ? 0 : pos * 1.0;
  },

  /* select a new position this always updates the thumbstrip
   * to the correct location.
   * it can optionally also focus the selected item. (option.focus == true)
   * it can optionally also update the slider by snapping it in place.
   * (option.slider == true)
   *
   * Usually the thumb strip is set to put the selected index to the
   * left-most position it can obtain.
   *
   * However, if selecting a new focus, this does not happen.
   *
   * Instead, with respect to visibility, if the left-most or further element
   * is selected, it is moved to be as right-most as possible in the
   * thumbstrip.  And visa versa for the right.
   * If the middle two elements are selected, then the thumbstrip does not
   * move.
   * */

  select: function( select, options ) {
    if( typeof(select) == 'number' ) {
      var new_index = select;
      var new_thumb = this.thumbs.select('li:nth-child('+(new_index+1)+')')[0];
    } else {
      var new_thumb = select;
      var new_index = new_thumb.previousSiblings().length;
    }
    
    var old_thumb = this.thumbs.select('li:nth-child('+(this.selected_index+1)+')')[0];
    var old_offset_index = this.offset_index;

    if( options.focus ) {
      if( new_index - old_offset_index == 0 ) {
        // page left
        this.offset_index = Math.max( 0, this.offset_index - this.options.width + 1 );
      } else if( new_index - old_offset_index == this.options.width - 1 ) {
        // page right
        this.offset_index = Math.min( Math.max( 0, this.thumbs.childElements().length - this.options.width ), this.offset_index + this.options.width - 1 );
      } else if ( new_index - old_offset_index < 0 ) {
        // jump left
        this.offset_index = Math.max( 0, new_index - this.options.width + 1 );
      } else if ( new_index - old_offset_index > this.options.width - 1 ) {
        // jump right
        this.offset_index = Math.min( Math.max( 0, this.thumbs.childElement().length - this.options.width ), new_index + this.options.width - 1 );
      }

      if( this.selected_index != new_index ) {
        this.selected_index = new_index;
        this.selected = new_thumb;

        old_thumb.removeClassName('selected');
        new_thumb.addClassName('selected');

				if( this.focus ) {
	        if( this.next_focus ) {
	          if( this.focus_effect )
	            this.focus_effect.finish_effect();
	          this.next_focus.setStyle({top: this.focus_top+'px'});
	          this.current_focus.removeClassName('selected');
	          this.next_focus.setStyle({zIndex: ''});
	          this.current_focus = this.next_focus;
	        }
	        this.next_focus = this.focus.select('li:nth-child('+(this.selected_index+1)+')')[0];
	        this.next_focus.setStyle({top: this.focus.getHeight()+'px', zIndex: 1});
	        this.next_focus.addClassName('selected');
	        this.focus_effect = new Effect.Style( this.next_focus, 'top', {
	          start: this.focus.getHeight(),
	          end: this.focus_top });
	        this.focus_effect.start_effect();
				}
      }
    } else {
      this.offset_index = Math.min( new_index, Math.max( 0, this.thumbs.childElements().length - this.options.width ) );
    }
    
    if( options.slider ) {
      this._dots_snap( this.offset_index );
    }

    if( old_offset_index != this.offset_index ) {
      this.thumb_effect.begin_effect();
      this.thumb_effect.style_options.start = this._thumbs_offset();
      this.thumb_effect.style_options.end = -this.offset_index * this._thumbs_width;
      this.thumb_effect.start_effect();
    }
  },

  /* select a thumb to be the new focus */
  _thumb_select: function(e) {
    var thumb = e.element();
    
    /* Don't select if we clicked on a link */
    if( thumb.match('a') || thumb.up('a') )
      return;
    
    if( !thumb.match('.thumbs > li') )
      thumb = thumb.up('.thumbs > li');

    if( !thumb )
      return;

    e.stop();

    this.select( thumb, { slider: true, focus: true } );
  },

  /* make the left and right bar appear */
  _before_thumbs_effect: function() {
    this.left_effect.start_effect();
    this.right_effect.start_effect();
  },

  /* disappear the left and right bar */
  _after_thumbs_effect: function() {
    this.left_effect.rewind_effect();
    this.right_effect.rewind_effect();
  }
});




/*===== This was in the document... put into this JS file by Matt ====*/

// In this example we just have to say that our slider should be pushed up
// 5px from where the top of the box's layout is and 1 px left of where the
// boxes left edge starts (margins are taken into account for both).
//	Event.observe(window, 'load', function() {
//	  $('slideshow').slideshow = new Slideshow( $('slideshow'), {
//			width: 3, //number of thumb-items visible in a set for slideshow on this page
//	    slider_adjust: { top: 0, left: 0 }
//	  });
//	});
/*===== put back into the document ====*/
