/* nGallery.js
 * December 16, 2009 Jon Suderman
 * requires prototype and scripty2
*/

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)
    }
  }
})
