/* =========================================================
// jquery.innerFade.js

// Date: 2010-07-23
// Author: Wes Baker
// Mail: wes@wesbaker.com	
// Web: http://www.wesbaker.com
// ========================================================= */

(function ($) {
  var default_options = {
    'animationType': 'fade',
    'animate': true,
    'first_slide': 0,
    'easing': 'linear',
    'speed': 'normal',
    'type': 'sequence',
    'timeout': 2000,
    'startDelay': 0,
    'loop': true,
    'containerHeight': 'auto',
    'runningClass': 'innerFade',
    'children': null,
    'cancelLink': null,
    'pauseLink': null,
    'prevLink': null,
    'nextLink': null,
    'indexContainer': null,
    'currentItemContainer': null,
    'totalItemsContainer': null,
    'callback_index_update': null
  };

  $.fn.innerFade = function (options) {
    return this.each(function () {
      $fade_object = new Object();
      // Assign the container
      $fade_object.container = this;
      // Combine default and set settings or use default
      // Pay attention kids, there's an important lesson here. When using $.extend, the first parameter will
      // be CHANGED to the combination of all the parameters. In my situation, I just wanted to combine two 
      // objects, but not affect them in any way hence the empty object.
      $fade_object.settings = $.extend({}, default_options, options);
      // If children option is set use that as elements, otherwise use the called jQuery object
      $fade_object.elements = ($fade_object.settings.children === null) ? $($fade_object.container).children() : $($fade_object.container).children($fade_object.settings.children);
      // Setup the count
      $fade_object.count = 0;
      // Save data to container for use later
      $($fade_object.container).data('object', $fade_object);

      // Start the loop
      if ($fade_object.elements.length > 1) {
        // Establish the Next and Previous Handlers
        $.bindControls($fade_object);

        // Establish Cancel Handler
        if ($fade_object.settings.cancelLink) { $.bindCancel($fade_object); };

        // Set outer container as relative, and use the height that's set and add the running class
        $($fade_object.container).css({ 'position': 'relative' }).addClass($fade_object.settings.runningClass);
        if ($fade_object.settings.containerHeight == 'auto') {
          height = $($fade_object.elements).filter(':first').height();
          $($fade_object.container).css({ 'height': height + 'px' });
        } else {
          $($fade_object.container).css({ 'height': $fade_object.settings.containerHeight });
        };

        // Build the Index if one is specified
        if ($fade_object.settings.indexContainer) {
          $.innerFadeIndex($fade_object);
        };

        $($fade_object.elements).filter(':gt(0)').hide(0);
        // Set the z-index from highest to lowest (20, 19, 18...) and set their position as absolute
        for (var i = 0; i < $fade_object.elements.length; i++) {
          $($fade_object.elements[i]).css('z-index', String($fade_object.elements.length - i)).css('position', 'absolute');
        }

        var toShow = '';
        var toHide = '';

        if ($fade_object.settings.type == "random") {
          toHide = Math.floor(Math.random() * $fade_object.elements.length);
          do {
            toShow = Math.floor(Math.random() * $fade_object.elements.length);
          } while (toHide == toShow);
          $($fade_object.elements[toHide]).show();
        } else if ($fade_object.settings.type == 'random_start') {
          $fade_object.settings.type = 'sequence';
          toHide = Math.floor(Math.random() * ($fade_object.elements.length));
          toShow = (toHide + 1) % $fade_object.elements.length;
        } else {
          // Otherwise and if its sequence
          toShow = $fade_object.settings.first_slide;
          toHide = ($fade_object.settings.first_slide == 0) ? $fade_object.elements.length - 1 : $fade_object.settings.first_slide - 1;
        }

        if ($fade_object.settings.animate) {
          $.fadeTimeout($fade_object, toShow, toHide, true);
        } else {
          $($fade_object.elements[toShow]).show();
          $($fade_object.elements[toHide]).hide();
          $.updateIndexes($fade_object, toShow);
        };
        $.updateIndexes($fade_object, toShow);

        if ($fade_object.settings.type == 'random') {
          $($fade_object.elements[toHide]).show();
        } else {
          $($fade_object.elements[toShow]).show();
        };

        // Set item count containers
        if ($fade_object.settings.currentItemContainer) { $.currentItem($fade_object, toShow); };
        if ($fade_object.settings.totalItemsContainer) { $.totalItems($fade_object); };

        // Establish the Pause Handler
        if ($fade_object.settings.pauseLink) {
          $.bind_pause($fade_object);
        };
      }
    });
  };

  /**
  * Public function to change to a specific slide. This is expecting a zero-index slide number.
  * @param {Number} slide_number Zero-indexed slide number
  */
  $.fn.innerFadeTo = function (slide_number) {
    return this.each(function (index) {
      var $fade_object = $(this).data('object');

      var $currentVisibleItem = $($fade_object.elements).filter(':visible');
      var currentItemIndex = $($fade_object.elements).index($currentVisibleItem);
      $.stopSlideshow($fade_object);
      if (slide_number != currentItemIndex) {
        $.fadeToItem($fade_object, slide_number, currentItemIndex);
      };
    });
  };

  /**
  * Fades the slideshow to the item selected from the previous item
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  * @param {Number} toShow The position in the elements array of the item to be shown
  * @param {Number} toHide The position in the elements array of the item to be hidden
  */
  $.fadeToItem = function ($fade_object, toShow, toHide) {
    // Update the next and previous controls
    var buildControls = function () {
      if ($fade_object.settings.nextLink || $fade_object.settings.prevLink) { $.bindControls($fade_object); }
    };

    if ($fade_object.settings.animationType == 'slide') {
      $($fade_object.elements[toHide]).slideUp($fade_object.settings.speed);
      $($fade_object.elements[toShow]).slideDown($fade_object.settings.speed, function () { buildControls(); });
    } else if ($fade_object.settings.animationType == 'slideOver') {
      var itemWidth = $($fade_object.elements[0]).width(),
				to_hide_css = {},
				to_show_css = {},
				to_hide_animation = {},
				to_show_animation = {};

      $($fade_object.container).css({ 'overflow': 'hidden' });

      // Both CSS Declarations use the same initial CSS
      to_hide_css = {
        'position': 'absolute',
        'top': '0px'
      };

      to_show_css = $.extend({}, to_hide_css);

      // If going forward, we want the item (to be shown) to animate from the right to left
      // If going backwards, we want the item (to be shown) to animate from the left to the right
      if (toShow > toHide) { // Forwards
        console.log('Forwards!');
        to_hide_css.left = "0px";
        to_hide_css.right = "auto";

        to_show_css.left = 'auto';
        to_show_css.right = '-' + itemWidth + 'px';

        to_hide_animation.left = '-' + itemWidth + 'px';

        to_show_animation.right = '0px';

        console.log(to_hide_css);
      } else { // Backwards
        console.log("Backwards!");
        to_hide_css.left = "auto";
        to_hide_css.right = "0px";

        to_show_css.left = '-' + itemWidth + 'px';
        to_show_css.right = 'auto';

        to_hide_animation.right = '-' + itemWidth + 'px';

        to_show_animation.left = '0px';
      };

      $($fade_object.elements[toHide]).css(to_hide_css);
      $($fade_object.elements[toShow]).css(to_show_css).show();

      $($fade_object.elements[toHide]).animate(to_hide_animation, $fade_object.settings.speed, $fade_object.settings.easing, function () {
        $(this).hide();
      });

      $($fade_object.elements[toShow]).animate(to_show_animation, $fade_object.settings.speed, $fade_object.settings.easing, function () {
        buildControls();
      });
    } else {
      $($fade_object.elements[toHide]).fadeOut($fade_object.settings.speed);
      $($fade_object.elements[toShow]).fadeIn($fade_object.settings.speed, function () {
        buildControls();
      });
    }
    // Update the toShow item
    if ($fade_object.settings.currentItemContainer) {
      $.currentItem($fade_object, toShow);
    };

    // Update indexes with active classes
    if ($fade_object.settings.indexContainer || $fade_object.settings.callback_index_update) {
      $.updateIndexes($fade_object, toShow);
    };
  };

  /**
  * Fades to the item of your choosing and establishes the timeout for the next item to fade to
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  * @param {Number} toShow The position in the elements array of the item to be shown
  * @param {Number} toHide The position in the elements array of the item to be hidden
  * @param {Boolean} firstRun If this is the first run of innerfade, pass true, otherwise pass false
  */
  $.fadeTimeout = function ($fade_object, toShow, toHide, firstRun) {
    // If its not the first run, then fade
    if (firstRun != true) {
      $.fadeToItem($fade_object, toShow, toHide);
    };

    // Increment the count of slides shown
    $fade_object.count++;

    // Check if loop is false, if it is check to see how many slides have been shown.
    // In the case that you're at the last slide, stop the slideshow and return.
    if ($fade_object.settings.loop == false && $fade_object.count >= $fade_object.elements.length) {
      $.stopSlideshow($fade_object);
      return;
    };

    // Get ready for next fade
    if ($fade_object.settings.type == "random") {
      toHide = toShow;
      while (toShow == toHide) { toShow = Math.floor(Math.random() * $fade_object.elements.length); }
    } else {
      toHide = (toHide > toShow) ? 0 : toShow;
      toShow = (toShow + 1 >= $fade_object.elements.length) ? 0 : toShow + 1;
    }

    // Set the time out; if its first run and a start delay exists, use the start delay
    var timeout = (firstRun && $fade_object.settings.startDelay) ? $fade_object.settings.startDelay : $fade_object.settings.timeout;
    $($fade_object.container).data('current_timeout', setTimeout((function () { $.fadeTimeout($fade_object, toShow, toHide, false); }), timeout));
  };

  /* Allows the unbind function to be called from javascript */
  $.fn.innerFadeUnbind = function () {
    return this.each(function (index) {
      var $fade_object = $(this).data('object');
      $.stopSlideshow($fade_object);
    });
  };

  /**
  * Stops the slideshow
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  */
  $.stopSlideshow = function ($fade_object) {
    clearTimeout($($fade_object.container).data('current_timeout'));
    $($fade_object.container).data('current_timeout', null);
  };

  /**
  * Establishes the Next and Previous link behavior
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  */
  $.bindControls = function ($fade_object) {
    $($fade_object.settings.nextLink).unbind().one('click', function (event) {
      event.preventDefault();
      //$.stopSlideshow($fade_object);

      var $currentElement = $($fade_object.elements).filter(':visible');
      var currentElementIndex = $($fade_object.elements).index($currentElement);

      var $nextElement = ($currentElement.next().length > 0) ? $currentElement.next() : $($fade_object.elements).filter(':first');
      var nextElementIndex = $($fade_object.elements).index($nextElement);

      $.fadeToItem($fade_object, nextElementIndex, currentElementIndex);
    });

    $($fade_object.settings.prevLink).unbind().one('click', function (event) {
      event.preventDefault();
      //$.stopSlideshow($fade_object);

      var $currentElement = $($fade_object.elements).filter(':visible');
      var currentElementIndex = $($fade_object.elements).index($currentElement);

      var $previousElement = ($currentElement.prev().length > 0) ? $currentElement.prev() : $($fade_object.elements).filter(':last');
      var previousElementIndex = $($fade_object.elements).index($previousElement);

      $.fadeToItem($fade_object, previousElementIndex, currentElementIndex);
    });
  };

  /**
  * Establishes the Pause Button
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  */
  $.bind_pause = function ($fade_object) {
    $($fade_object.settings.pauseLink).unbind().click(function (event) {
      event.preventDefault();
      if ($($fade_object.container).data('current_timeout') != null) {
        $.stopSlideshow($fade_object);
      } else {
        // Restart the slideshow				
        var tag = $($fade_object.container).children(':first').attr('tagName').toLowerCase();
        var nextItem = '';
        var previousItem = '';

        if ($fade_object.settings.type == "random") {
          previousItem = Math.floor(Math.random() * $fade_object.elements.length);
          do {
            nextItem = Math.floor(Math.random() * $fade_object.elements.length);
          } while (previousItem == nextItem);
        } else if ($fade_object.settings.type == "random_start") {
          previousItem = Math.floor(Math.random() * $fade_object.elements.length);
          nextItem = (previousItem + 1) % $fade_object.elements.length;
        } else {
          previousItem = $(tag, $($fade_object.container)).index($(tag + ':visible', $($fade_object.container)));
          nextItem = ((previousItem + 1) == $fade_object.elements.length) ? 0 : previousItem + 1;
        }

        $.fadeTimeout($fade_object, nextItem, previousItem, false);
      }
    });
  };

  /**
  * Establishes the Cancel Button
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  */
  $.bindCancel = function ($fade_object) {
    $($fade_object.settings.cancelLink).unbind().click(function (event) {
      event.preventDefault();
      $.stopSlideshow($fade_object);
    });
  };

  /**
  * Updates the indexes and adds an active class to the visible item
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  * @param {Number} toShow The position in the elements array of the item to be shown
  */
  $.updateIndexes = function ($fade_object, toShow) {
    $($fade_object.settings.indexContainer).children().removeClass('active');
    $('> :eq(' + toShow + ')', $($fade_object.settings.indexContainer)).addClass('active');

    // Check for the callback index update
    if (typeof ($fade_object.settings.callback_index_update) == "function") {
      $fade_object.settings.callback_index_update.call(this, toShow);
    };
  };

  /**
  * Creates handlers for the links created by the $.handleIndexes and $.generateIndexes functions
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  * @param {Number} count The item to be setting the link on
  * @param {jQuery Object} link The selector or jQuery object of the link
  */
  $.createIndexHandler = function ($fade_object, count, link) {
    $(link).click(function (event) {
      event.preventDefault();
      var $currentVisibleItem = $($fade_object.elements).filter(':visible');
      var currentItemIndex = $($fade_object.elements).index($currentVisibleItem);
      $.stopSlideshow($fade_object);
      if ($currentVisibleItem.size() <= 1 && count != currentItemIndex) {
        $.fadeToItem($fade_object, count, currentItemIndex);
      };
    });
  };

  /**
  * Creates one link for each item in the slideshow, to show that item immediately
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  */
  $.createIndexes = function ($fade_object) {
    var $indexContainer = $($fade_object.settings.indexContainer);
    $indexContainer.html("");
    for (var i = 0; i < $fade_object.elements.length; i++) {
      var $link = $('<li>' + (i + 1) + '</li>');
      $.createIndexHandler($fade_object, i, $link);
      $indexContainer.append($link);
    };
    var indexMargin = -($indexContainer.width() / 2);
    $indexContainer.css({ "margin-left": indexMargin });
  };

  /**
  * Establishes links between the slide elements and index items in the indexContainer
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  */
  $.linkIndexes = function ($fade_object) {
    var $indexContainer = $($fade_object.settings.indexContainer);
    var $indexContainerChildren = $('> :visible', $indexContainer);

    if ($indexContainerChildren.size() == $fade_object.elements.length) {
      var count = $fade_object.elements.length;
      for (var i = 0; i < count; i++) {
        $('a', $indexContainer).click(function (event) { event.preventDefault(); });
        $.createIndexHandler($fade_object, i, $indexContainerChildren[i]);
      };
    } else {
      alert("There is a different number of items in the menu and slides. There needs to be the same number in both.\nThere are " + $indexContainerChildren.size() + " in the indexContainer.\nThere are " + $fade_object.elements.length + " in the slides container.");
    };
  };

  /**
  * Determines if the index container is empty or not. If its empty then it generates links, if its not empty 
  * it links one to one
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  */
  $.innerFadeIndex = function ($fade_object) {
    var $indexContainer = $($fade_object.settings.indexContainer);
    $.createIndexes($fade_object);
  };

  /**
  * Changes the text of the current item selector to the index of the current item
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  * @param {Number} current Index of the current slide
  */
  $.currentItem = function ($fade_object, current) {
    var $container = $($fade_object.settings.currentItemContainer);
    $container.text(current + 1);
  };

  /**
  * Changes the text of the total item selector to the total number of items
  * @param {Object} $fade_object The object that contains the settings, elements and container for this slideshow
  */
  $.totalItems = function ($fade_object) {
    var $container = $($fade_object.settings.totalItemsContainer);
    $container.text($fade_object.elements.length);
  };
})(jQuery);
