/* nGallery.js
 * December 16, 2009 Jon Suderman
 * requires prototype and scripty2
    
    Minimum HTML expected:
    ======================
 
    <div id="my_slideshow">
      <ul class="slides">
        <li><img src="first.jpg" /></li>
        <li><img src="second.jpg" thumb="second_small.jpg" /></li>
        <li><img src="third.jpg" /></li>
      </ul>
      <ul class="thumbs"></ul>
    </div>
    * Note: If the img element's thumb attribute is empty, the thumbnail will reuse the slide's img src.
            If you don't want thumbnail navigation, don't include the thumbs ul element.
  
  
    HTML generated by script:
    ========================
    <div id="my_slideshow" class="ngallery">
      <div class="ngallery_slides_container">
        <div class="ngallery_slides_outer">
          <div class="ngallery_slides_inner">
            <ul class="slides">
              <li class="ngallery_slide"><img src="first.jpg" /></li>
              <li class="ngallery_slide"><img src="second.jpg" thumb="second_small.jpg" /></li>
              <li class="ngallery_slide"><img src="third.jpg" /></li>
            </ul>
          </div>
        </div>
      </div>
      <div class="ngallery_thumbs_container">
        <a class="ngallery_thumbs_previous"></a>
        <div class="ngallery_thumbs_outer">
          <div class="ngallery_thumbs_pointer"></div>
          <div class="ngallery_thumbs_inner">
            <ul class="thumbs">
              <li class="ngallery_thumb">
                <span class="ngallery_thumb_image"><img src="first.jpg" /></span>
                <span class="ngallery_thumb_caption"></span>
              </li>
              <li class="ngallery_thumb">
                <span class="ngallery_thumb_image"><img src="second_small.jpg" /></span>
                <span class="ngallery_thumb_caption"></span>
              </li>
              <li class="ngallery_thumb">
                <span class="ngallery_thumb_image"><img src="third.jpg" /></span>
                <span class="ngallery_thumb_caption"></span>
              </li>
            </ul>
          </div>          
        </div>
        <a class="ngallery_thumbs_next"></a>
      </div>
    </div>
    * Note: If you want to include the wrapper divs in the source (or any other markup shown above), go ahead. 
            Just use the same classnames and the script won't generate duplicates.

    Customize with CSS (example)
    ============================
    .ngallery { width: 760px; padding: 28px; margin: 0 auto; background: transparent url(../images/layout/gallery_background.gif) left top no-repeat; visibility: hidden; }
    .ngallery .ngallery_thumbs_container { background: #222; margin-bottom: -5px; }
    .ngallery .ngallery_thumbs_container .ngallery_thumbs_outer { padding-bottom: 5px; }
    .ngallery .ngallery_thumbs_container .ngallery_thumbs_outer .ngallery_thumbs_pointer { background: transparent url(../images/layout/gallery_pointer_down.png) center center no-repeat; _background-image: none; _filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../images/layout/gallery_pointer_down.png", sizingMethod="crop"); }
    .ngallery .ngallery_thumbs_container .ngallery_thumbs_outer .ngallery_thumbs_inner { width: 625px !important; }
    .ngallery .ngallery_thumbs_container .ngallery_thumbs_outer .ngallery_thumbs_inner ul.thumbs li { background: #222; padding: 5px 5px 5px 0; }
    .ngallery .ngallery_thumbs_container .ngallery_thumbs_outer .ngallery_thumbs_inner ul.thumbs li a { width: 100px; height: 40px; }
    .ngallery .ngallery_thumbs_container .ngallery_thumbs_outer .ngallery_thumbs_inner ul.thumbs li img { width: 100px; }
    .ngallery .ngallery_thumbs_container a.ngallery_thumbs_previous { background: transparent url(../images/layout/gallery_arrow_left.gif) 90% 45% no-repeat; }
    .ngallery .ngallery_thumbs_container a.ngallery_thumbs_next { background: transparent url(../images/layout/gallery_arrow_right.gif) 10% 45% no-repeat; }
    .ngallery .ngallery_slides_container { background: #222; padding: 0 5px 5px; }

    Usage examples
    ==============
    new nGallery('#my_slideshow')
    new nGallery({ container: '#my_slideshow', delay: 8 })
    new nGallery('#my_slideshow', { delay: 4, slides_container: 'ul.slides', thumbs_container: 'ul.thumbs' })
    
    new nGallery({ 
      container           : '#my_slideshow', 
      delay               : 3,
      duration            : 1,
      transition          : 'push',           // crossfade, fade, cut, push
      thumbs_easing       : 'easeOutBounce',  // http://scripty2.com/doc/scripty2 fx/s2/fx/transitions.html
      slides_easing       : 'easeInQuad',     // bounce, bouncePast, easeFrom, easeFromTo, easeInBack, easeInCirc, easeInCubic, easeInExpo, easeInOutBack, easeInOutCirc, easeInOutCubic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, easeTo, elastic, linear, sinusoidal, spring, swingFrom, swingFromTo, swingTo
      pointer_navigation  : 'fixed'           // fixed, active
    })
 *
 *--------------------------------------------------------------------------*/
 
var nGallery = Class.create({
  options           : {},  
  current_slide     : null,
  slideshow         : null,
  looping           : true,
  ignore_input      : false,
  number_per_page   : 1,
  page_offset       : 0,
  slot_position     : {0:0},
  odd_even          : 'odd',
  
  
  initialize: function(){
    // Ensure we're not in surf-to-edit
    if ($('nterchange')) { return false }
    // -- ARGUMENTS -- 
    
    // Get arguments container and options (both optional, one required)
    var arg_container = arg_options = ''
    $A(arguments).each(function(arg){
      switch(typeof arg){
        case "string": arg_container=arg; break;
        case "object": arg_options=arg; break;
      }
    })
    // Override default options
    this.options = Object.extend({      
      container           : '',                                   // eg: #masthead
      slides_container    : 'ul.slides',
      thumbs_container    : 'ul.thumbs',
      thumbs_easing       : 'sinusoidal',                         // http://scripty2.com/doc/scripty2%20fx/s2/fx/transitions.html
      slides_easing       : 'sinusoidal',                         // bounce, bouncePast, easeFrom, easeFromTo, easeInBack, easeInCirc, easeInCubic, easeInExpo, easeInOutBack, easeInOutCirc, easeInOutCubic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, easeTo, elastic, linear, sinusoidal, spring, swingFrom, swingFromTo, swingTo
      pointer_navigation  : 'active',                             // active, fixed
      transition          : 'crossfade',                          // crossfade, fade, cut, left, right, up, down
      before_transition   : function(current_slide,next_slide){}, // Override with custom function
      after_transition    : function(current_slide,next_slide){}, // Override with custom function
      slideshow           : true,
      duration            : 1,                                    // Speed of transition
      delay               : 5,                                    // Speed of slideshow
      number_per_page_for_padding: 0                              // Normally left alone, over-rides number_per_page when padding the slideshow with slides on each end
    }, arg_options || {})
    
    // If the container was set as string instead of in the options object
    this.options.container = (arg_container) ? arg_container : this.options.container
    // Verify arguments map to proper containers
    if (!this.handle_arguments()) { return false }

    // -- BUILD/VERIFY MARKUP --
    // Wrap slides with extra divs
    this.build_slides_container()
    // Wrap thumbs with extra divs, add links and pointer
    this.build_thumbs_container()

    // -- DO MATH --
    // Determine slot_positions, number_per_page, page_offset
    this.calculate_groups(true)
    // Add blanks so all groups are equal, then repeat the first page on the last, and the last page on the first
    this.pad_with_blanks_and_copies()

    // -- BUILD SLIDES/THUMBS --
    // Prepare each slide and thumb
    var slides = $(this.slides).childElements()
    slides.each( function(slide, index){
      this.build_slide(slide, index)
      this.build_thumb(slide)
    }.bind(this))
    // Now that everything is built, add in some css
    this.apply_css()

    // -- READY TO BEGIN --
    
    // Transition to initial slide (true argument skips all easing)
    var first_original_slide = $(this.slides).down('.ngallery_slide_original').identify()
    this.transition(first_original_slide, true)    
    // Clean-up
    slides = null
    first_original_slide = null
    // Start the slideshow
    if (this.options.slideshow) {
      this.loop()
    }
    
  },

  handle_arguments: function() {
    if ($$(this.options.container).first()) {
      this.container = $$(this.options.container).invoke('addClassName','ngallery').first().identify()
    }
    // Abort if the gallery container doesn't exist
    if (!this.container) { return false }    
    // Classify the container
    $(this.container).addClassName('ngallery')   
    // Get the slides ul
    if ($(this.container).down(this.options.slides_container)) {
      this.slides = $(this.container).down(this.options.slides_container).identify()
    }
    // Abort if the slides ul doesn't exist
    if (!this.slides) { return false }
    // Grab the slides
    var slides = $(this.slides).childElements()
    // Abort if there are 1 or less slides
    if (slides.size() <= 1) { return false }
    // Classify the slides 
    slides.invoke('addClassName', 'ngallery_slide ngallery_slide_original')
    // Get the thumbs ul (if it exists)
    if ($(this.container).down(this.options.thumbs_container)) {
      this.thumbs = $(this.container).down(this.options.thumbs_container).identify()
    }
    // Clean-up
    slides = null
    return true
  },
  // Wrap slides with extra divs
  build_slides_container: function() {
    
    // div.ngallery_slides_container
    //   div.ngallery_slides_outer
    //     div.ngallery_slides_inner
    //       ul.slides
    
    // Build/verify ngallery_slides_inner
    var ngallery_slides_inner = $(this.slides).up()
    if (!ngallery_slides_inner.hasClassName('ngallery_slides_inner')) {
      ngallery_slides_inner = $(this.slides).wrap('div', { 'class' : 'ngallery_slides_inner' })      
    }
    this.ngallery_slides_inner = ngallery_slides_inner.identify()
    
    // Build/verify ngallery_slides_outer
    var ngallery_slides_outer = ngallery_slides_inner.up()
    if (!ngallery_slides_outer.hasClassName('ngallery_slides_outer')) {
      ngallery_slides_outer = ngallery_slides_inner.wrap('div', { 'class' : 'ngallery_slides_outer' })      
    }
    this.ngallery_slides_outer = ngallery_slides_outer.identify()    
    
    
    // Build/verify ngallery_slides_container
    var ngallery_slides_container = ngallery_slides_outer.up()
    if (!ngallery_slides_container.hasClassName('ngallery_slides_container')) {
      ngallery_slides_container = ngallery_slides_outer.wrap('div', { 'class' : 'ngallery_slides_container' })      
    }
    this.ngallery_slides_container = ngallery_slides_container.identify()
        
    // Clean-up
    ngallery_slides_inner = null
    ngallery_slides_outer = null
    ngallery_slides_container = null
  },
  
  
  // Wrap thumbs with extra divs, add links and pointer
  build_thumbs_container: function() {
    if (!this.thumbs) { return false }
    
    // div.ngallery_thumbs_container
    //   a.ngallery_thumbs_previous
    //   div.ngallery_thumbs_outer
    //     div.ngallery_thumbs_pointer
    //     div.ngallery_thumbs_inner
    //       ul.thumbs
    //   a.ngallery_thumbs_next
    
    // Build/verify ngallery_thumbs_inner
    var ngallery_thumbs_inner = $(this.thumbs).up()
    if (!ngallery_thumbs_inner.hasClassName('ngallery_thumbs_inner')) {
      ngallery_thumbs_inner = $(this.thumbs).wrap('div', { 'class' : 'ngallery_thumbs_inner', 'style' : 'position:relative; overflow:hidden;' }) // neccessary this early for calculate_groups() to work
    }
    this.ngallery_thumbs_inner = ngallery_thumbs_inner.identify()
    // Build/verify ngallery_thumbs_outer
    var ngallery_thumbs_outer = ngallery_thumbs_inner.up()
    if (!ngallery_thumbs_outer.hasClassName('ngallery_thumbs_outer')) {
      ngallery_thumbs_outer = ngallery_thumbs_inner.wrap('div', { 'class' : 'ngallery_thumbs_outer' })
    }
    this.ngallery_thumbs_outer = ngallery_thumbs_outer.identify()
    // Build/verify ngallery_thumbs_container
    var ngallery_thumbs_container = ngallery_thumbs_outer.up()
    if (!ngallery_thumbs_container.hasClassName('ngallery_thumbs_container')) {
      ngallery_thumbs_container = ngallery_thumbs_outer.wrap('div', { 'class' : 'ngallery_thumbs_container' })
    }
    this.ngallery_thumbs_container = ngallery_thumbs_container.identify()
    
    // Build/verify ngallery_thumbs_pointer
    var ngallery_thumbs_pointer = ngallery_thumbs_container.down('.ngallery_thumbs_pointer')
    if (!ngallery_thumbs_pointer) {
      ngallery_thumbs_outer.insert({top:'<div class="ngallery_thumbs_pointer"></div>'})
      ngallery_thumbs_pointer = ngallery_thumbs_container.down('.ngallery_thumbs_pointer')
    }
    this.ngallery_thumbs_pointer = ngallery_thumbs_pointer.identify()    
    
    // Build/verify ngallery_thumbs_previous
    var ngallery_thumbs_previous = ngallery_thumbs_container.down('.ngallery_thumbs_previous')
    if (!ngallery_thumbs_previous) {
      ngallery_thumbs_outer.insert({before:'<a class="ngallery_thumbs_previous" href="#previous"></a>'})
      ngallery_thumbs_previous = ngallery_thumbs_container.down('.ngallery_thumbs_previous')
    }
    this.ngallery_thumbs_previous = ngallery_thumbs_previous.identify()
    
    // Click event for ngallery_thumbs_previous
    ngallery_thumbs_previous.observe('click', function(e){
      this.activate($(this.current_slide).readAttribute('previous_slide'))
      e.stop()
    }.bind(this))    
    
    // Build/verify ngallery_thumbs_next
    var ngallery_thumbs_next = ngallery_thumbs_container.down('.ngallery_thumbs_next')
    if (!ngallery_thumbs_next) {
      ngallery_thumbs_outer.insert({after:'<a class="ngallery_thumbs_next" href="#next"></a>'})
      ngallery_thumbs_next = ngallery_thumbs_container.down('.ngallery_thumbs_next')
    }
    this.ngallery_thumbs_next = ngallery_thumbs_next.identify()
    
    // Click event for ngallery_thumbs_next
    ngallery_thumbs_next.observe('click', function(e){
      this.activate($(this.current_slide).readAttribute('next_slide'))
      e.stop()
    }.bind(this))

    // Clean-up
    ngallery_thumbs_inner = null
    ngallery_thumbs_outer = null
    ngallery_thumbs_container = null
    ngallery_thumbs_pointer = null
    ngallery_thumbs_previous = null
    ngallery_thumbs_next = null
  },  

  // slot_positions, number_per_page, page_offset
  calculate_groups: function() {
    // Require thumbs
    if (!this.thumbs) { return }
    // Arguments
    var first_round = ($A(arguments)[0]) ? true : false
    // First round, clear the ul.thumbs and zero out the counter
    if (first_round) {
      $(this.thumbs).update('')
      $(this.thumbs).setStyle({ width: ($(this.ngallery_thumbs_inner).getWidth() * 2) + 'px' })
      this.number_per_page = 0
    }
    // Add another thumb
    this.build_thumb()
    // Get the xpos of this new thumb
    var new_thumb_position = $(this.thumbs).childElements().last().positionedOffset().left
    // Check if there is room for another thumb
    if (new_thumb_position < $(this.ngallery_thumbs_inner).getWidth()) {        
      
      // Prevent infinite loop if something is wrong      
      if (this.number_per_page < 50) {        
        
        // Save the slot position
        this.slot_position[this.number_per_page] = new_thumb_position
        
        this.number_per_page++
        
        // Do it again
        this.calculate_groups()
      }
    } else {
      // How many pixels each page should shift the ul over
      this.page_offset = new_thumb_position
      
      // Finished, clear the thumbs ul
      $(this.thumbs).update('')
      $(this.thumbs).setStyle({ width:'' })
    }
    
    // Clean-up
    first_round = null
    new_thumb_position = null
  },

  // Add blanks so all groups are equal, then repeat the first page on the last, and the last page on the first
  pad_with_blanks_and_copies: function() {
    var slides_count = $(this.slides).childElements().size()
    
    // Just skip this if there are less slides than pages. Maybe improve later.
    if (slides_count <= this.number_per_page) { return }
    // -- 1. Pad the slides with blanks so you can divide with no remainders --
    if ( ((this.options.pointer_navigation=='active') && (this.number_per_page > 1)) || (slides_count <= this.number_per_page) ) {
      // The idea is to pad a page with blanks so it has the full number of thumbs
      var number_of_blanks = 0
      if (slides_count <= this.number_per_page) {
        number_of_blanks = this.number_per_page - slides_count
      } else {
        number_of_blanks = this.number_per_page - (slides_count % this.number_per_page)
      }    
      
      // Now that we know how many, add them all to the end
      number_of_blanks.times(function(index){
        $(this.slides).insert('<li class="ngallery_slide_blank"></li>')
      }.bind(this))
      
    }
    
    // -- 2. Add the first page to the last and the last page to the first
    var number_per_page_for_padding = (this.options.number_per_page_for_padding > 0) ? this.options.number_per_page_for_padding : this.number_per_page
    var pages = $(this.slides).childElements().eachSlice(number_per_page_for_padding)    
    if (pages.size() > 2) { // having only 2 pages and fixed nav causes an infinite loop 
      var first_page = (this.options.pointer_navigation=='active') ? pages.first() : $A(pages[0].concat(pages[1])) // double-up if fixed pointer
      var last_page = pages.last().reverse()
    } else {
      var first_page = pages.first()
      var last_page = pages.last().reverse()
    }
    
    first_page.each(function(slide_original){
      var slide_copy = slide_original.clone(true)
      slide_copy.removeClassName('ngallery_slide_original').addClassName('ngallery_slide_copy')
      slide_copy.writeAttribute('original_slide', slide_original.identify())
      $(this.slides).insert({bottom: slide_copy })
    }.bind(this))
    
    last_page.each(function(slide_original){
      var slide_copy = slide_original.clone(true)
      slide_copy.removeClassName('ngallery_slide_original').addClassName('ngallery_slide_copy')
      slide_copy.writeAttribute('original_slide', slide_original.identify())
      $(this.slides).insert({top: slide_copy })
    }.bind(this))

    // Clean-up
    slides_size = null
    number_of_blanks = null
    number_per_page_for_padding = null
    pages = null
    slide_copy = null
    first_page = null
    last_page = null  
  },

  build_slide: function(slide) {
    // li.ngallery_slide     
    var index = ($A(arguments)[1]!=undefined) ? $A(arguments)[1] : Number(new Date())
    // Don't process a blank slide
    if (slide.hasClassName('ngallery_slide_blank')) { return }
    // Stripe slides with odd/even classes
    slide.addClassName('ngallery_slide_' + this.odd_even)
    this.odd_even = (this.odd_even=='odd') ? 'even' : 'odd'
    slide.identify()
    slide.writeAttribute({    
      item_index: index
    })
    // Set previous slide
    var previous_slide = slide.previous('.ngallery_slide')
    if (previous_slide) {
      slide.writeAttribute('previous_slide', previous_slide.identify())
    
    // If there's no previous slide, this is the first slide. Wrap around!
    } else {
      var last_slide = $(this.slides).childElements().last()      
      slide.writeAttribute('previous_slide', last_slide.identify())
    }
    // Set the next slide for the slideshow
    var next_slide = slide.next('.ngallery_slide')
    if (next_slide) {
      slide.writeAttribute('next_slide', next_slide.identify())
    // If there's no next slide, this is the last slide. Wrap around!
    } else {
      var first_slide = $(this.slides).childElements().first()      
      slide.writeAttribute('next_slide', first_slide.identify())      
    }
    // Clean-up
    slide = null
    index = null
    previous_slide = null
    next_slide = null
    first_slide = null
    last_slide = null
  },

  // Insert new thumb based off slide
  build_thumb: function() {
    var slide = ($A(arguments)[0]) ? $A(arguments)[0] : false
    if (slide) { if (slide.hasClassName('ngallery_slide_blank')) { slide = false } }
    
    // li.ngallery_thumb
    //   a
    //     span
    //       img
    //     span
    
    if (this.thumbs) {
    
      // Thumb template    
      var thumb = '<li class="ngallery_thumb" item_index="#{index}" style="float:left;">'+
                    '<a href="#{slide_image}" style="display:block;">'+
                      '<span class="ngallery_thumb_image"><img src="#{thumb_image}" style="#{link_style}" /></span>'+
                      '<span class="ngallery_thumb_caption">#{thumb_caption}</span>'+
                    '</a>'+
                  '</li>'
      
      if (slide) {
        
        thumb = thumb.interpolate({
          index         : slide.readAttribute('item_index'),
          slide_image   : slide.down('img').readAttribute('src'),
          thumb_image   : slide.down('img').readAttribute('thumb') || slide.down('img').readAttribute('src'),
          thumb_caption : slide.down('img').readAttribute('alt') || slide.down('img').readAttribute('title'),
          link_style    : ''
        })
        
      } else {
        
        thumb = thumb.interpolate({
          index         : 'blank',
          slide_image   : '#',
          thumb_image   : '',
          thumb_caption : '',
          link_style    : 'visibility:hidden; cursor:default;'
        })
                
      }
      
      // Insert new thumb into the DOM
      $(this.thumbs).insert(thumb)
      // If this is a blank thumb, abort here
      if (!slide) { return }
      // TODO: clicking blanks selects next real slide
      // console.log(this.slides.select('.ngallery_slide_blank').last().next('.ngallery_slide'))
      
      // Retrieve this new thumb and assign it to the slide
      var new_thumb = $(this.thumbs).childElements().last()
      slide.writeAttribute('thumb', new_thumb.identify())
      new_thumb.writeAttribute('slide', slide.identify())
      // If this is first slide, set the thumb active
      if (!slide.previous()) { new_thumb.addClassName('active') }
                                    
      // Set click behavior for this thumb
      new_thumb.down('a').observe('click', function(e){
        e.stop()
        
        // Activate the chosen slide
        this.activate(e.findElement('li').readAttribute('slide'))
      }.bind(this))
    }
    
    // Clean-up
    slide = null
    thumb = null
    new_thumb = null
  },

  // Add some CSS rules to the generated markup
  apply_css: function() {
    
    var all_slides = $(this.slides).childElements()
    var one_slide = all_slides.first()
    var slides_count = all_slides.size()    
    // div.ngallery 
    $(this.container).setStyle({      
      position    : 'relative',
      display     : 'block',
      visibility  : 'visible'
    })
    
    // div.ngallery_slides_container
    $(this.ngallery_slides_container).setStyle({
      position    : 'static',
      clear       : 'both'
    })
    
    // div.ngallery_slides_outer
    $(this.ngallery_slides_outer).setStyle({
      position    : 'relative'
    })    
    
    // div.ngallery_slides_inner
    var ngallery_slides_inner_dimensions = one_slide.getDimensions()
    $(this.ngallery_slides_inner).setStyle({
      position    : 'relative',
      overflow    : 'hidden',
      margin      : '0 auto',
      width       : ngallery_slides_inner_dimensions['width'] + 'px',  // width from slide li
      height      : ngallery_slides_inner_dimensions['height'] + 'px' // height from slide li
    })
        
    // ul.slides
    $(this.slides).setStyle({
      position    : 'relative'
    })
    
    
    // The ul.slide li's need different styles based on transition type
    switch (this.options.transition) {
      
      
      // These transitions require all slides to be adjacent to each other
      case 'push':
      case 'left':
      case 'right':
        
        // ul.slides li    
        slides.invoke('setStyle',{
          float    : 'left'
        })
        
        // ul.slides
        $(this.slides).setStyle({
          position    : 'absolute',
          display     : 'block',
          width       : slides_count * one_slide.getWidth() + 'px'
        })        
        break
        
      // These transitions require all slides to be adjacent to each other
      case 'up':
      case 'down':
        // ul.slides li    
        all_slides.invoke('setStyle',{
          float       : 'none'
        })
        // ul.slides
        $(this.slides).setStyle({
          position    : 'absolute',
          display     : 'block',
          height      : slides_count * one_slide.getHeight() + 'px'
        })        
        break        
        
      
      // These transitions require all slides to stacked on top of each other
      case 'cut':
      case 'fade':
      case 'crossfade':
      default:
      
        // ul.slides li    
        all_slides.invoke('setStyle',{
          position    : 'absolute',
          opacity     : '0' 
        })
        break
    }

    // == The rest is thumbs-related ==
    if (!this.thumbs) { return }
    
    var all_thumbs = $(this.thumbs).childElements()
    var one_thumb = all_thumbs.first()
    var all_thumb_links = $(this.thumbs).select('li a')
    var one_thumb_link = all_thumb_links.first()
    var thumbs_count = all_thumbs.size()    
    // div.ngallery_thumbs_container
    $(this.ngallery_thumbs_container).setStyle({
      position    : 'static',
      overflow    : 'hidden',
      clear       : 'both',
      height      : '1%'
    })
    
    // ul.thumbs li
    all_thumbs.invoke('setStyle',{
      float       : 'left',
      minHeight   : '40px'
    })
    
    // ul.thumbs li a
    all_thumb_links.invoke('setStyle',{
      display     : 'block',
      overflow    : 'hidden',
      textDecoration: 'none'
    })    
    
    // ul.thumbs
    $(this.thumbs).setStyle({
      position    : 'absolute',
      display     : 'block',
      width       : (thumbs_count * one_thumb.getWidth()) + 'px'
    })    
    // div.ngallery_thumbs_inner
    $(this.ngallery_thumbs_inner).setStyle({
      position    : 'relative',
      overflow    : 'hidden',
      height      : one_thumb.getHeight() + 'px', // height from thumb li
      width       : ($(this.ngallery_thumbs_container).getWidth() * .8) + 'px' // width is 80% of container (this should probably be over-ridden to fit just right) 
    })
    
    // div.ngallery_thumbs_outer
    $(this.ngallery_thumbs_outer).setStyle({
      position    : 'relative',
      float       : 'left',
      zIndex      : '10'      
    })
    // div.ngallery_thumbs_pointer
    $(this.ngallery_thumbs_pointer).setStyle({
      position    : 'absolute',
      zIndex      : '10',
      display     : 'block',
      cursor      : 'pointer',      
      width       : one_thumb_link.getWidth() + 'px', // width from thumb li a
      height      : $(this.ngallery_thumbs_outer).getHeight() + 'px' // height from .ngallery_thumbs_outer
    })
    // a.ngallery_thumbs_previous
    // a.ngallery_thumbs_next
    var ngallery_thumbs_previous_next_styles = {
      posiition   : 'relative',
      display     : 'block',
      float       : 'left',
      cursor      : 'pointer',
      height      : $(this.ngallery_thumbs_outer).getHeight() + 'px', // height from .ngallery_thumbs_outer
      width       : (($(this.ngallery_thumbs_container).getWidth() - $(this.ngallery_thumbs_outer).getWidth()) / 2) + 'px' // width from remaining space / 2
    }
    $(this.ngallery_thumbs_previous).setStyle(ngallery_thumbs_previous_next_styles)
    $(this.ngallery_thumbs_next).setStyle(ngallery_thumbs_previous_next_styles)
    
    
    // Clean-up
    all_slides = null
    one_slide = null
    slides_count = null
    ngallery_slides_inner_dimensions = null
    all_thumbs = null
    one_thumb = null
    all_thumb_links = null
    one_thumb_link = null
    thumbs_count = null
    ngallery_thumbs_previous_next_styles = null
  },


  
  activate: function(next_slide_id) {
    if (!next_slide_id) { return false }
    
    // Ensure a different slide was chosen
    if (this.current_slide == next_slide_id) { return false }
    // Turn off the slideshow loop
    this.looping = false
    if (this.slideshow) {
      this.slideshow.stop()
    }
    if (this.ignore_input) { return false }  
    
    // Change slides
    this.transition(next_slide_id)
  },
  
  
  loop: function() {
    // Since the PeriodicalExecuter doesn't have an "on" flag, this keeps track
    this.looping = true
    // Keep running the transitions over and over
    this.slideshow = new PeriodicalExecuter(function(){
      if (!this.ignore_input) {
        this.transition($(this.current_slide).readAttribute('next_slide'))
      }
    }.bind(this), this.options.delay)
    
    // Re-enable looping if the mouse exits the container
    $(this.container).observe('mouseleave', function(){
      // this.looping = true
      if ((this.slideshow) && (!this.looping)) {
        this.looping = true
        this.slideshow.registerCallback()
      }
    }.bind(this))
  },
  
  
  transition: function(next_slide_id){
    // this.transition(slide, true) forces an immediate transition
    var transition_type = ($A(arguments)[1]) ? 'immediate' : this.options.transition
    // Ensure there's a next slide
    if (!next_slide_id) { return false }
    
    // Ensure there's a current slide
    this.current_slide = (this.current_slide) ? $(this.current_slide).identify() : $(this.slides).childElements().first().identify()    
    
    // Get the dom elements
    var current_slide = $(this.current_slide)
    var next_slide = $(next_slide_id)
    // Ignore input until transition is done
    this.ignore_input = true
        
    // -- THUMBS --
    
    if (this.thumbs){
      var current_thumb = $(current_slide.readAttribute('thumb'))
      var next_thumb = $(next_slide.readAttribute('thumb'))
      
      // Swap active class name from old thumb to new
      current_thumb.removeClassName('active')
      next_thumb.addClassName('active')
      
      // Two different types of thumb animation
      switch(this.options.pointer_navigation) {
        
        // Position the thumb under the pointer
        case 'fixed':
          if (transition_type=='immediate') {
            $(this.thumbs).setStyle({ left: '-' + next_thumb.positionedOffset().left + 'px' })
          } else {
            $(this.thumbs).morph('left:-' + next_thumb.positionedOffset().left + 'px', { duration: (this.options.duration / 1.5), transition: this.options.thumbs_easing })
          }
          break

        // Position the pointer over the thumb
        case 'active':
        default:
          
          // Which thumb is this (0..number of thumbs)
          var thumb_index = parseInt(next_thumb.readAttribute('item_index'))
          // Current page the thumb is on (0..number of pages)
          var current_page = Math.ceil((thumb_index + 1) / this.number_per_page) - 1
          // Slot position this thumb is on (0..number of thumbs per page)
          var slot_position = this.slot_position[thumb_index % this.number_per_page]
          if (transition_type=='immediate') {            
            
            // Move the pointer to the slot position this thumb is sitting
            $(this.ngallery_thumbs_pointer).setStyle({ left: slot_position + 'px' })          
            
            // Move the ul to the page this thumb is on
            $(this.thumbs).setStyle({ left: '-' + (current_page * this.page_offset) + 'px' })
            
          } else {
            
            // Move the pointer to the slot position this thumb is sitting                   // this event must be triggered AFTER the previous is finished
            $(this.ngallery_thumbs_pointer).morph('left:' + slot_position + 'px', { duration: (this.options.duration / 1.5), transition: this.options.thumbs_easing })
            
            // Move the ul to the page this thumb is on
            $(this.thumbs).morph('left:-' + (current_page * this.page_offset) + 'px', { duration: (this.options.duration / 1.5), transition: this.options.thumbs_easing })
          }
          break
      }
    }
    
    
    // -- SLIDES --
    
    // Hook before slide transition
    this.options.before_transition(this.current_slide, next_slide_id)
    // Different types of slide transitions
    switch(transition_type) {
      // -- Immediate --
      case 'immediate':
        if (next_slide.getStyle('position')=='absolute'){
          current_slide.setStyle({ opacity: '0'})
          next_slide.setStyle({ opacity: '1'})          
        } else {
          $(this.slides).setStyle({ 
            left: '-' + next_slide.positionedOffset().left + 'px',
            top: '-' + next_slide.positionedOffset().top + 'px'
          })
        }        
        this.after_transition(next_slide_id)
        break

      // -- Push left --
      case 'push':
      case 'left':
        // Next slide transition in with hook after slide transition
        $(this.slides).morph('left:-' + next_slide.positionedOffset().left + 'px', { duration: this.options.duration, transition: this.options.slides_easing, after: this.after_transition.bind(this, next_slide_id)})
        break
        
      // -- Push right --
      case 'right':
        // Next slide transition in with hook after slide transition
        $(this.slides).morph('left:' + next_slide.positionedOffset().left + 'px', { duration: this.options.duration, transition: this.options.slides_easing, after: this.after_transition.bind(this, next_slide_id)})
        break 
        
      // -- Push up --
      case 'up':
        // Next slide transition in with hook after slide transition
        $(this.slides).morph('top:-' + next_slide.positionedOffset().top + 'px', { duration: this.options.duration, transition: this.options.slides_easing, after: this.after_transition.bind(this, next_slide_id)})
        break               
        
      // -- Cut (immediately switch from previous to next) --
      case 'cut':
        // Previous slide transition out
        current_slide.morph('opacity:0', { duration: 0, transition: this.options.slides_easing } )
      
        // Next slide transition in with hook after slide transition
        next_slide.morph('opacity:1', { duration: 0, transition: this.options.slides_easing, after: this.after_transition.bind(this, next_slide_id)})
        break

      // -- Fade (fade out previous, fade in next) --
      case 'fade':
        var fade_next_slide_id = next_slide_id
        var fade_next_slide = next_slide
        // Previous slide transition out
        current_slide.morph('opacity:0', { duration: (this.options.duration/2), transition: this.options.slides_easing, after: function() {
          // Next slide transition in with hook after slide transition
          fade_next_slide.morph('opacity:1', { duration: (this.options.duration/2), transition: this.options.slides_easing, after: this.after_transition.bind(this, fade_next_slide_id)})
          
          // Clean-up
          fade_next_slide_id = null
          fade_next_slide = null
          
        }.bind(this)})
        break

      // -- Crossfade (fade from previous to next) --
      case 'crossfade':
      default:
        // Previous slide transition out
        current_slide.morph('opacity:0', { duration: this.options.duration, transition: this.options.slides_easing } )
        // Next slide transition
        next_slide.morph('opacity:1', { duration: this.options.duration, transition: this.options.slides_easing, after: this.after_transition.bind(this, next_slide_id)})
        break
    }
    
    // Clean-up
    next_slide_id = null
    next_slide = null
    current_slide = null
    current_thumb = null
    next_thumb = null
    transition_type = null
    thumb_index = null
    current_page = null
    slot_position = null
  },
  
  
  // Called after every slide transition is completed
  after_transition: function(next_slide_id) {
    // Hook after slide transition
    this.options.after_transition(this.current_slide, next_slide_id)
    // Update what the current slide is
    this.current_slide = next_slide_id
    
    // Stop ignoring input
    this.ignore_input = false    
    
    // Jump to original slide if necessary
    if ($(next_slide_id).readAttribute('original_slide')) {
      this.transition($(next_slide_id).readAttribute('original_slide'), true)
    }
  }
})
