$(function() {

  jQuery.fx.interval = 100;

  var effects = {

    slidefx: function (currentNode, direction, nextNode) {
      var scroller = currentNode.parents('.scroller');
      if (scroller.data('busy')) return;

      var width = parseInt(scroller.find('.viewport').width());
      var next	= nextNode || getNextNode(currentNode, direction);

      next.css({'left': width*direction, 'display': 'block'});

      currentNode.css({'left': 0});

      scroller.data('busy', scroller.data('busy')+2);

      next.animate({left: 0, queue: false}, scroller.data('speedfx'), 'linear',
				function() {
					scroller.data('busy', scroller.data('busy')-1)
				}
			);
      currentNode.animate({'left': (width*direction*-1), queue: false}, scroller.data('speedfx'), 'linear',
        function() {
          currentNode.css({'display': 'none'});
          moveActive(currentNode, next);
          scroller.data('busy', scroller.data('busy')-1);
        }
			);
    },

    deckfx: function (currentNode, direction, nextNode) {
      var scroller = currentNode.parents('.scroller');
      if (scroller.data('busy')) return;

      var width					= parseInt(scroller.find('.viewport').width());
      var next					= nextNode || getNextNode(currentNode, direction);
			var nextStartPos	= direction === 1 ?	width : 0;
			var nextEndPos		= direction === 1 ?			0 : width;
			var nextIndex		  = direction === 1 ?			2 : 1;
			var currentIndex	= direction === 1 ?			1 : 2;
			var animatedNode	= direction === 1 ?	 next : currentNode;

			next.css({'left': nextStartPos, 'z-index': nextIndex, 'display': 'block'});

			currentNode.css({'z-index': currentIndex});

			scroller.data('busy', scroller.data('busy')+1);

			animatedNode.animate({'left': nextEndPos}, scroller.data('speedfx'), 'linear',
				function() {
					currentNode.css({'display': 'none', 'z-index': ''});
					moveActive(currentNode, next);
					scroller.data('busy', scroller.data('busy')-1);
				}
			);
    },

    snapfx: function (currentNode, direction, nextNode) {
      var scroller	= currentNode.parents('.scroller');
      var width			= parseInt(scroller.find('.viewport').width());
      var next			= nextNode || getNextNode(currentNode, direction);

      next.css({'display': 'block', 'left': 0});

      currentNode.css({'left': width*direction, 'display': 'none'});

      moveActive(currentNode, next);
    },

    fadefx: function (currentNode, direction, nextNode) {
      var scroller = currentNode.parents('.scroller');
      if (scroller.data('busy')) return;

      var width = parseInt(scroller.find('.viewport').width());
      var next	= nextNode || getNextNode(currentNode, direction);

      next.css({'left': 0, 'display': 'none', 'z-index': 2});

      currentNode.css('z-index', 1);

      scroller.data('busy', scroller.data('busy')+1);
      next.fadeIn(scroller.data('speedfx'), 'linear',
        function() {
          currentNode.css({'display': 'none', 'z-index': ''});
          moveActive(currentNode, next);
          scroller.data('busy', scroller.data('busy')-1);
        }
			);
    }

  };

  setupScroller();

  // Create closures for handlers so we can assign from within a loop but with
  // correct scoping of variables
  function handleFX(fx,direction,next) {
    return function() {
      $(this).parents('.scroller').data('skipAuto', 1);
      effects[fx](getCurrentSlide($(this)), direction, next)
      return false;
    }
  }
  function handlePickFX(fx) {
    return function() {
      $(this).parents('.scroller').data('skipAuto', 1);
      if (nodes = getClickedNext($(this))) effects[fx](nodes.clicked, nodes.direction, nodes.next);
      return false;
     }
  }

  // Attach handlers to controls
  for (var effect in effects) {
		var fxNode = $('.'+effect);
    fxNode.find('a.prevSlide').bind('click', handleFX(effect, -1));
    fxNode.find('a.nextSlide').bind('click', handleFX(effect,  1));
    fxNode.find('a.pickSlide').bind('click', handlePickFX(effect));
  }

  // -=-=-=-=-=- END OF SETUP -=-=-=-=-=-

  function setupScroller() {
    $('.scroller').each( function () {
      var scroller = $(this);
      var viewport = scroller.find('.viewport');
      scroller.data('busy', 0);
      scroller.data('skipAuto', 0);
      scroller.data('speedfx', 1000);
      scroller.data('delayfx', 7500);
      var clazzes = scroller.attr('class').split(' ');
      var matches = null;
      for (var clazz in clazzes) {
        if ((matches = /^speedfx_(\d+)$/.exec(clazzes[clazz])) != null) {
          scroller.data('speedfx', parseInt(matches[1]));
        }
        if ((matches = /^delayfx_(\d+)$/.exec(clazzes[clazz])) != null) {
          scroller.data('delayfx', parseInt(matches[1]));
        }
      }
      viewport.delay(scroller.data('delayfx'), 'tim').queue('tim', auto).dequeue('tim');
      scroller.filter(':not(.auto)').data('skipAuto', 1);
      scroller.filter(':not(.auto)').find('a.pauseSlide,a.playSlide').toggle();
      scroller.find('a.pauseSlide,a.playSlide').bind('click',
        function() {
          $(this).parents('.scroller').toggleClass('auto');
          $(this).parent().children().toggle();
          return false;
        }
      );
      viewport.children().children().css({'display': 'none', 'left': '-'+(viewport.width())+'px', 'position': 'absolute'});
      scroller.find('.navigation').find('.hidden').removeClass('hidden');
      viewport.children().children().first().css({'display': 'block', 'left': 0});
      scroller.find('a.pickSlide').first().addClass('active');

    });
  }

  function auto() {
    var scroller = $(this).parents('.scroller');
    if (scroller.data('skipAuto') === 1 || scroller.hasClass('auto') === false) {
      scroller.data('skipAuto', 0);
    } else {
      var clazzes = scroller.attr('class').split(' ');
      for (var clazz in clazzes) {
        if (typeof effects[clazzes[clazz]] === 'function') {
          effects[clazzes[clazz]](getCurrentSlide($(this)), 1);
          break;
        }
      }
    }
    $(this).delay(scroller.data('delayfx')+scroller.data('speedfx'), 'tim').queue('tim', auto);
    $(this).next();
    $(this).dequeue('tim');
  }

  // Get currently displayed node + next node that was clicked on.
  // Direction returned is current relative to next node.
  function getClickedNext(node) {
    var current = getCurrentSlide(node);
    if (node.index() === current.index()) return;
    var next = node.parents('.scroller').find('.viewport').children().children().eq(node.index());
    var direction = (node.index() < current.index()) ? -1:1;
    return { 'clicked': current, 'direction': direction, 'next': next}
  }

  // Get the currently displayed slide's node
	function getCurrentSlide(node) {
		var currentNode = false;
		node.parents('.scroller').find('.viewport').children().children().each(
			function() {
				if ($(this).css('display') === 'block') { currentNode = $(this); }
			}
		);
    return currentNode;
	}

  // Get the next/previous sibling of the current node based on direction.
  // Wrapping if start/end of siblings reached.
	function getNextNode(currentNode, direction) {
    var next;
    if (direction === 1) {
      if ((next = currentNode.next()).length === 0) {
        next = currentNode.siblings().first();
      }
    } else {
      if ((next = currentNode.prev()).length === 0) {
        next = currentNode.siblings().last();
      }
    }
		return next;
	}

  function moveActive(currentNode, nextNode) {
    var slides = currentNode.parents('.scroller').find('.pickSlide');
    slides.filter('.active').removeClass('active');
    if (nextNode) {
      slides.eq(nextNode.index()).addClass('active');
    }
  }

});

