/*!
 * jQuery.Slider
 *
 * @projectDescription Simple utility which prepares sliding effect controled by next/prev buttons for some set of elements.
 * @author Jiri Prokop
 * @version 0.1
 *
 * @id jQuery.slider
 * @id jQuery.fn.slider
 * @param {Object} settings Hash of settings, settings.subElems are required, other settings do have defaults.
 * @return {jQuery} Returns the same jQuery object, for chaining.
 * 
 * Notes:
 *  - The plugin requires jQuery.buttonizer, jQuery.serialScroll, jQuery.easing, jQuery.doTimeout.  
 */

(function($) {
  var Slider = function(element, options) {
    // private fields and shortcuts
    var $elem = $(element);
    var me = this;
    var settings = $.extend(true, {
      // reqired subelements selectors relative to the current element
      subElems: {
        // prev-button with activated buttonizer
        prev: null,
        // next-button with activated buttonizer
        next: null,
        // 
        scroll: null,
        // 
        container: null,
        // 
        items: null
      },
      // required counts used for disabled/enabled functionality of prev/next buttons
      counts: {
        // how much items are in scroll-container
        all: null,
        // how much items are shown together in one "frame/pack/page"
        pack: null,
        // how much items are scrolled
        step: null
      },
      // possible values are describe here (null means default): http://gsgd.co.uk/sandbox/jquery/easing/
      easing: null,
      // duration of moving
      duration: 500,
      // should items cycle?
      cycle: false,
      // should be disabled prev/next buttons hidden? (it means if both together are disabled <=> counts.all < counts.pack)
      hideDisabledPrevAndNext: false
    }, options || {});
    // TODO: cleanup of variable which are unused outside of initialize
    var $prev = null, prevHandler = null;
    var $next = null, nextHanlder = null;
    var $scroll = null;
    var $items = null;
    var $container = null;
    
    // private methods
    var initialize = function() {
      $prev = initSubElem('prev');
      $next = initSubElem('next');
      $scroll = initSubElem('scroll');
      $container = initSubElem('container');
      $items = initSubElem('items');
      validateCounts();
      
      $items.css({
        'float': 'left',
        'position': 'relative' // IE fix to ensure overflow is hidden
      });
    
      $container.css('width', $items[0].offsetWidth * $items.length);
      $scroll.css('overflow', 'hidden');
      
      var scrollOptions = {
        target: $scroll,
        items: $items,
        axis: 'x',
        lock: false,
        stop: true,
        easing: settings.easing,
        duration: settings.duration,
        cycle: settings.cycle,
        step: settings.counts.step,
        exclude: (settings.counts.pack - 1),
        onBefore: onBefore,
      };
      $elem.serialScroll(scrollOptions);
      
      // buttons disable/enable logic (+ hide logic)
      var enablePrev = true;
      var enableNext = true;
      if (!settings.cycle) {
        enablePrev = false;
      }
      if (settings.counts.all <= settings.counts.pack) {
        enablePrev = false;
        enableNext = false;
      }
      if ((!enablePrev && !enableNext) && settings.hideDisabledPrevAndNext) {
        $next.hide();
        $prev.hide();
      }
      
      prevHandler = function(e) {
        $container.trigger('prev')
      };
      nextHandler = function(e) {
        $container.trigger('next')
      };
      setButtonEnabled(enablePrev, $prev, prevHandler);
      setButtonEnabled(enableNext, $next, nextHandler);
    };
    
    var setButtonEnabled = function(enabled, $button, handler) {
      var buttonizer = $button.data('buttonizer');
      if (enabled) {
        // unbind click for case when function is called more than one time
        $button.unbind('click', handler);
        // and bind it again, just for sure
        $button.bind('click', handler);
        buttonizer.enable();
      } else {
        $button.unbind('click', handler);
        buttonizer.disable();
      }
    }
    
    var onBefore = function(event, shownPanel, elem, items, index) {
      //console.log(event, (settings.counts.all - settings.counts.pack), index);
      if (!settings.cycle) {
        var enablePrev = true;
        var enableNext = true;
        if (index === 0) {
          enablePrev = false;
        } else if (index >= (settings.counts.all - settings.counts.pack)) {
          enableNext = false;
        }
        setButtonEnabled(enablePrev, $prev, prevHandler);
        setButtonEnabled(enableNext, $next, nextHandler);
      }
    }
    
    var initSubElem = function(subElemName) {
      if (settings.subElems[subElemName] !== null && typeof settings.subElems[subElemName] === 'string') {
        $subElem = $elem.find(settings.subElems[subElemName]);
        if ($subElem.length !== 0) {
          return $subElem;
        } else {
          throw ReferenceError('Setting of subElems.' + subElemName + ' is invalid; element with selector ' + settings.subElems[subElemName] + ' not found.');
        }
      } else {
        throw Error('There is missing setting of subElems.' + subElemName + ' selector or it has wrong data-type.');
      }
    }
    
    var validateCounts = function() {
      var countNames = ['all', 'pack', 'step'];
      for (var i in countNames) {
        if (!isNaN(i)) {
          var count = countNames[parseInt(i)];
          if (settings.counts[count] === null || typeof settings.counts[count] !== 'number' || settings.counts[count] < 0 || isNaN(settings.counts[count])) {
            throw Error('There is missing valid value of counts.' + count + '.');
          }
        }
      }
      if (settings.counts.pack === 0 || settings.counts.step === 0) {
        throw Error('There is invalid value of counts.pack or counts.step. One or both of them are 0.');
      }
      if (settings.counts.pack < settings.counts.step) {
        throw Error('There is invalid value of counts.pack according to counts.step.');
      }
    }
    
    // initialize
    initialize();
  };

  $.fn.slider = function(options) {
    return this.each(function() {
      var $elem = $(this);
      var key = 'slider';
      var data = $elem.data(key);
      var instanceExists = false;
      
      if (typeof data !== 'undefined' && data instanceof Slider) {
        instanceExists = true;
      } else if (typeof data === 'object') {
        // options from html data-element are merged together with options of plugin when initializing
        options = $.extend(true, data, options || {});
      }
      
      if (!instanceExists) {
        $elem.data(key, new Slider(this, options));
      }
    });
  };
})(jQuery);
