(function($) {

function put(obj, name, val) {
  var r = $.extend({}, obj);
  r[name] = val;
  return r;
}

jQuery.fn.maxHeight = function() {
  var max = 0;
  $(this).each(function() {
    if($(this).height() > max) max = $(this).height();
  });
  return max;
}

jQuery.fn.marquee = function(options) {

  options = $.extend({
    afterNext: function(activeIndex, nextIndex, dir) {},
    autoPlay: true,     /* start the marquee when the page loads */
    beforeNext: function(activeIndex, nextIndex, dir) {},
    bulletFunc: function(i) { $(this).find("span").text(i); }, /* For manipulating the bullets after creation. */
    effect: {
      delay: 0,         /* For 'fade', delay after fadeOut and before fadeIn */
      easing: "swing",  /* The style of animation */
      name: "slide",    /* just slide or fade at the moment. specifying anything else will do show/hide with the easing parameter */
      speed: 600        /* The speed of the animation */
    },
    height: null,       /* Marquee Height. default: use height of largest frame */
    interval: 5000,     /* Time interval between transitions. */
    loop: true,         /* if false, prev has no effect on first slide and next has no effect on last slide */
    nextText: "Next",
    onDisableNext: function() { $(this).hide(); },
    onDisablePrev: function() { $(this).hide(); },
    onEnableNext: function() { $(this).hide(); },
    onEnablePrev: function() { $(this).hide(); },
    onPause: function() { $(this).find("span").text("Play"); $(this).attr("title", "Play"); },
    onPlay: function() { $(this).find("span").text("Pause"); $(this).attr("title", "Pause"); },
    prevText: "Previous",
    startFrame: 0,      /* index of the initial frame to display */
    width: null         /* Marquee Width. default: use width of target */
  }, options);
  
  $(this).each(function() {
    // get all elements into vars, create controls
    var $frameWrapper = $(this).addClass("frame_wrapper");
    var $frames = $frameWrapper.children().addClass("frame");
    var $marqueeWrapper = $("<div class='marquee_wrapper'></div>");
    $frameWrapper.wrap($marqueeWrapper);
    var $controls = $("<div class='controls'></div>");
    $frameWrapper.after($controls);
    var $togglePlay = $("<a href='#' class='toggle_play'><span>" + options.togglePlayText + "</span></a>");
    $controls.append($togglePlay);
    var $next = $("<a href='#' class='next'><span>" + options.nextText + "</span></a>");
    var $prev = $("<a href='#' class='prev'><span>" + options.prevText + "</span></a>");
    $controls.append($prev);
    var $controlBulletsWrapper = $("<ul class='bullets'></ul>");
    $controls.append($controlBulletsWrapper);
    $frames.each(function(i) {
      $controlBulletsWrapper.append("<li><a href='#'><span>&nbsp;</span></a></li>");
    });
    var $controlBullets = $controlBulletsWrapper.children().addClass("bullet");
    $controlBullets.each(options.bulletFunc);
    $controls.append($next);
    
    // initialize frame sizes
    $frameWrapper.css({ height: $frames.maxHeight(), width: $frameWrapper.width() });
    if(options.height) $frameWrapper.css("height", options.height);
    if(options.width) $frameWrapper.css("width", options.width);
    $frames.css("position", "absolute");
    
    // standard functions
    function play(timeout) {
      if(!timeout) timeout = options.interval;
      $togglePlay.removeClass("paused");
      options.onPlay.call($togglePlay[0]);
      if(!options.loop && $frames.filter(".active")[0] == $frames.filter(":last")[0]) {
        return;
      }
      $frameWrapper.timeout('marquee', timeout, function() { nextFrame(null, "forward") });
    }
    
    function pause() {
      $togglePlay.addClass("paused");
      options.onPause.call($togglePlay[0]);
      $frameWrapper.cancelTimeout('marquee');
    }
    
    function nextFrame($nextF, dir) {
      // cancel any existing nextFrame calls
      $frameWrapper.cancelTimeout('marquee marquee_transition');
      
      // figure out the next frame if it's not explicitly given
      var $active = $frames.filter(".active");
      if(!$nextF) {
        if($active[0] != $frames.filter(":last")[0]) $nextF = $active.next();
        else $nextF = $frames.filter(":first");
        dir = "forward"
      }
      else if($nextF == "next") $nextF = getNextFrame();
      else if($nextF == "prev") $nextF = getPrevFrame();
      
      // get the index values
      var activeIndex = $frames.index($active);
      var nextIndex = $frames.index($nextF);
      if(activeIndex == nextIndex) {
        return;
      }
      
      // figure out the direction... if using next/prev, this is already specified
      if(!dir) dir = nextIndex > activeIndex ? "forward" : "backward";
      options.beforeNext.call($frameWrapper[0], activeIndex, nextIndex, dir);
      
      var beforeFunc = function() {
        $controlBullets.removeClass("active");
        $controlBullets.eq(nextIndex).addClass("active");
      }
      var cancelFunc = function() {
        $frames.removeClass("active").hide();
        $nextF.addClass("active").show();
      };
      var completeFunc = function() {
        options.afterNext.call($frameWrapper[0], activeIndex, nextIndex, dir);
        if(!$togglePlay.is(".paused")) play();
      };
      if(options.effect.name == "slide") {
        $frames.stop();
        var side = dir == "forward" ? "left" : "right";
        var otherSide = dir == "forward" ? "right" : "left";
        var animCss = {};
        $active.css(otherSide, "auto");
        $nextF.css(otherSide, "auto");
        animCss[side] = -$frameWrapper.width();
        $nextF.css(side, $frameWrapper.width()).show();
        $active.css(side, 0).show();
        $frames.not($active[0]).not($nextF[0]).css(animCss).removeClass("active");
        $active.removeClass("active").animate(animCss, options.effect.speed, options.effect.easing, function() {
          $frames.not($active[0]).not($nextF[0]).css(side, $frameWrapper.width());
          $active.css(side, $frameWrapper.width());
          $active.hide();
          options.afterNext.call($frameWrapper[0], activeIndex, nextIndex, dir);
        });
        animCss[side] = 0;
        $nextF.addClass("active").animate(animCss, options.effect.speed);
        $controlBullets.removeClass("active");
        $controlBullets.eq(nextIndex).addClass("active");
        if(!$togglePlay.is(".paused")) play();
      }
      else if(options.effect.name == "fade") {
        beforeFunc();
        $frames.removeClass("active").stop().animate({ opacity: 0.0 }, options.effect.speed);
        $frameWrapper.timeout('marquee_transition', options.effect.delay, function() {
          $nextF.addClass("active").stop().show().animate({ opacity: 1.0 }, options.effect.speed);
        }, cancelFunc, completeFunc);
      }
      else {
        // no effect, just hide/show
        beforeFunc();
        $frames.removeClass("active").hide(options.effect.name, options.effect.speed);
        $frameWrapper.timeout('marquee_transition', options.effect.delay, function() {
          $nextF.addClass("active").show(options.effect.name, options.effect.speed);
        }, cancelFunc, completeFunc);
      }
      // disable next/prev if necessary
      if(!options.loop && nextIndex == $frames.size()-1) $next.addClass("disabled");
      else $next.removeClass("disabled");
      if(!options.loop && nextIndex == 0) $prev.addClass("disabled");
      else $prev.removeClass("disabled");
    }
    
    // jump to frames using the controls
    $controlBullets.each(function(index) {
      $(this).click(function(e) {
        e.preventDefault();
        if($(this).is(".active")) {
          pause();
          return;
        }
        pause();
        nextFrame($frames.eq(index));
        return;
      });
    });
    
    // play/pause toggling
    $togglePlay.click(function(e) {
      e.preventDefault();
      if($togglePlay.is(".paused")) {
        play(100);
      }
      else {
        pause();
      }
    });
    
    function getNextFrame() {
      var $active = $frames.filter(".active");
      var $nextF = null;
      if(!$nextF) {
        if($active[0] != $frames.filter(":last")[0]) {
          $nextF = $active.next();
        }
        else {
          $nextF = $frames.filter(":first");
        }
      }
      return $nextF;
    }
    
    function getPrevFrame() {
      var $active = $frames.filter(".active");
      var $prevF = null;
      if(!$prevF) {
        if($active[0] != $frames.filter(":first")[0]) {
          $prevF = $active.prev();
        }
        else {
          $prevF = $frames.filter(":last");
        }
      }
      return $prevF;
    }
    
    $next.click(function(e) {
      e.preventDefault();
      if($(this).is(".disabled")) return;
      pause();
      nextFrame("next", "forward");
    });
    
    $prev.click(function(e) {
      e.preventDefault();
      if($(this).is(".disabled")) return;
      pause();
      nextFrame("prev", "backward");
    });
    
    // init to first frame on load
    if(new String(options.startFrame).match(/last/i)) options.startFrame = $frames.size() - 1;
    $controlBullets.removeClass("active").eq(options.startFrame).addClass("active");
    $frames.hide().removeClass("active").eq(options.startFrame).addClass("active").show();
    if(options.effect.name == "fade") $frames.not(".active").css("opacity", 0.0);
    if(!options.loop && options.startFrame == $frames.size()-1) $next.addClass("disabled");
    if(!options.loop && options.startFrame == 0) $prev.addClass("disabled");
    $frames.find("a").attr("tabindex",-1);
    $frameWrapper.bind("play", play);
    $frameWrapper.bind("pause", pause);
    $frameWrapper.bind("jump", function(event, index) { nextFrame($frames.eq(index)); });
    $frameWrapper.bind("next", function() { pause(); nextFrame("next", "forward"); });
    $frameWrapper.bind("prev", function() { pause(); nextFrame("prev", "backward"); });
    if(options.autoPlay) play();
    else pause();
  });
}
})(jQuery);
