/*
 * jQuery UI Tabs 1.7.2
 *
 * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Tabs
 *
 * Depends:
 *  ui.core.js
 */
(function($) {

$.widget("ui.tabs", {

  _init: function() {
    if (this.options.deselectable !== undefined) {
      this.options.collapsible = this.options.deselectable;
    }
    this._tabify(true);
  },

  _setData: function(key, value) {
    if (key == 'selected') {
      if (this.options.collapsible && value == this.options.selected) {
        return;
      }
      this.select(value);
    }
    else {
      this.options[key] = value;
      if (key == 'deselectable') {
        this.options.collapsible = value;
      }
      this._tabify();
    }
  },

  _tabId: function(a) {
    return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '') ||
      this.options.idPrefix + $.data(a);
  },

  _sanitizeSelector: function(hash) {
    return hash.replace(/:/g, '\\:'); // we need this because an id may contain a ":"
  },

  _cookie: function() {
    var cookie = this.cookie || (this.cookie = this.options.cookie.name || 'ui-tabs-' + $.data(this.list[0]));
    return $.cookie.apply(null, [cookie].concat($.makeArray(arguments)));
  },

  _ui: function(tab, panel) {
    return {
      tab: tab,
      panel: panel,
      index: this.anchors.index(tab)
    };
  },

  _cleanup: function() {
    // restore all former loading tabs labels
    this.lis.filter('.ui-state-processing').removeClass('ui-state-processing')
        .find('span:data(label.tabs)')
        .each(function() {
          var el = $(this);
          el.html(el.data('label.tabs')).removeData('label.tabs');
        });
  },

  _tabify: function(init) {

    this.list = this.element.children('ul:first');
    this.lis = $('li:has(a[href])', this.list);
    this.anchors = this.lis.map(function() { return $('a', this)[0]; });
    this.panels = $([]);

    var self = this, o = this.options;

    var fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hash
    this.anchors.each(function(i, a) {
      var href = $(a).attr('href');

      // For dynamically created HTML that contains a hash as href IE < 8 expands
      // such href to the full page url with hash and then misinterprets tab as ajax.
      // Same consideration applies for an added tab with a fragment identifier
      // since a[href=#fragment-identifier] does unexpectedly not match.
      // Thus normalize href attribute...
      var hrefBase = href.split('#')[0], baseEl;
      if (hrefBase && (hrefBase === location.toString().split('#')[0] ||
          (baseEl = $('base')[0]) && hrefBase === baseEl.href)) {
        href = a.hash;
        a.href = href;
      }

      // inline tab
      if (fragmentId.test(href)) {
        self.panels = self.panels.add(self._sanitizeSelector(href));
      }

      // remote tab
      else if (href != '#') { // prevent loading the page itself if href is just "#"
        $.data(a, 'href.tabs', href); // required for restore on destroy

        // TODO until #3808 is fixed strip fragment identifier from url
        // (IE fails to load from such url)
        $.data(a, 'load.tabs', href.replace(/#.*$/, '')); // mutable data

        var id = self._tabId(a);
        a.href = '#' + id;
        var $panel = $('#' + id);
        if (!$panel.length) {
          $panel = $(o.panelTemplate).attr('id', id).addClass('ui-tabs-panel ui-widget-content ui-corner-bottom')
            .insertAfter(self.panels[i - 1] || self.list);
          $panel.data('destroy.tabs', true);
        }
        self.panels = self.panels.add($panel);
      }

      // invalid tab href
      else {
        o.disabled.push(i);
      }
    });

    // initialization from scratch
    if (init) {

      // attach necessary classes for styling
      this.element.addClass('ui-tabs ui-widget ui-widget-content ui-corner-all');
      this.list.addClass('ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all');
      this.lis.addClass('ui-state-default ui-corner-top');
      this.panels.addClass('ui-tabs-panel ui-widget-content ui-corner-bottom');

      // Selected tab
      // use "selected" option or try to retrieve:
      // 1. from fragment identifier in url
      // 2. from cookie
      // 3. from selected class attribute on <li>
      if (o.selected === undefined) {
        if (location.hash) {
          this.anchors.each(function(i, a) {
            if (a.hash == location.hash) {
              o.selected = i;
              return false; // break
            }
          });
        }
        if (typeof o.selected != 'number' && o.cookie) {
          o.selected = parseInt(self._cookie(), 10);
        }
        if (typeof o.selected != 'number' && this.lis.filter('.ui-tabs-selected').length) {
          o.selected = this.lis.index(this.lis.filter('.ui-tabs-selected'));
        }
        o.selected = o.selected || 0;
      }
      else if (o.selected === null) { // usage of null is deprecated, TODO remove in next release
        o.selected = -1;
      }

      // sanity check - default to first tab...
      o.selected = ((o.selected >= 0 && this.anchors[o.selected]) || o.selected < 0) ? o.selected : 0;

      // Take disabling tabs via class attribute from HTML
      // into account and update option properly.
      // A selected tab cannot become disabled.
      o.disabled = $.unique(o.disabled.concat(
        $.map(this.lis.filter('.ui-state-disabled'),
          function(n, i) { return self.lis.index(n); } )
      )).sort();

      if ($.inArray(o.selected, o.disabled) != -1) {
        o.disabled.splice($.inArray(o.selected, o.disabled), 1);
      }

      // highlight selected tab
      this.panels.addClass('ui-tabs-hide');
      this.lis.removeClass('ui-tabs-selected ui-state-active');
      if (o.selected >= 0 && this.anchors.length) { // check for length avoids error when initializing empty list
        this.panels.eq(o.selected).removeClass('ui-tabs-hide');
        this.lis.eq(o.selected).addClass('ui-tabs-selected ui-state-active');

        // seems to be expected behavior that the show callback is fired
        self.element.queue("tabs", function() {
          self._trigger('show', null, self._ui(self.anchors[o.selected], self.panels[o.selected]));
        });

        this.load(o.selected);
      }

      // clean up to avoid memory leaks in certain versions of IE 6
      $(window).bind('unload', function() {
        self.lis.add(self.anchors).unbind('.tabs');
        self.lis = self.anchors = self.panels = null;
      });

    }
    // update selected after add/remove
    else {
      o.selected = this.lis.index(this.lis.filter('.ui-tabs-selected'));
    }

    // update collapsible
    this.element[o.collapsible ? 'addClass' : 'removeClass']('ui-tabs-collapsible');

    // set or update cookie after init and add/remove respectively
    if (o.cookie) {
      this._cookie(o.selected, o.cookie);
    }

    // disable tabs
    for (var i = 0, li; (li = this.lis[i]); i++) {
      $(li)[$.inArray(i, o.disabled) != -1 &&
        !$(li).hasClass('ui-tabs-selected') ? 'addClass' : 'removeClass']('ui-state-disabled');
    }

    // reset cache if switching from cached to not cached
    if (o.cache === false) {
      this.anchors.removeData('cache.tabs');
    }

    // remove all handlers before, tabify may run on existing tabs after add or option change
    this.lis.add(this.anchors).unbind('.tabs');

    if (o.event != 'mouseover') {
      var addState = function(state, el) {
        if (el.is(':not(.ui-state-disabled)')) {
          el.addClass('ui-state-' + state);
        }
      };
      var removeState = function(state, el) {
        el.removeClass('ui-state-' + state);
      };
      this.lis.bind('mouseover.tabs', function() {
        addState('hover', $(this));
      });
      this.lis.bind('mouseout.tabs', function() {
        removeState('hover', $(this));
      });
      this.anchors.bind('focus.tabs', function() {
        addState('focus', $(this).closest('li'));
      });
      this.anchors.bind('blur.tabs', function() {
        removeState('focus', $(this).closest('li'));
      });
    }

    // set up animations
    var hideFx, showFx;
    if (o.fx) {
      if ($.isArray(o.fx)) {
        hideFx = o.fx[0];
        showFx = o.fx[1];
      }
      else {
        hideFx = showFx = o.fx;
      }
    }

    // Reset certain styles left over from animation
    // and prevent IE's ClearType bug...
    function resetStyle($el, fx) {
      $el.css({ display: '' });
      if ($.browser.msie && fx.opacity) {
        $el[0].style.removeAttribute('filter');
      }
    }

    // Show a tab...
    var showTab = showFx ?
      function(clicked, $show) {
        $(clicked).closest('li').removeClass('ui-state-default').addClass('ui-tabs-selected ui-state-active');
        $show.hide().removeClass('ui-tabs-hide') // avoid flicker that way
          .animate(showFx, showFx.duration || 'normal', function() {
            resetStyle($show, showFx);
            self._trigger('show', null, self._ui(clicked, $show[0]));
          });
      } :
      function(clicked, $show) {
        $(clicked).closest('li').removeClass('ui-state-default').addClass('ui-tabs-selected ui-state-active');
        $show.removeClass('ui-tabs-hide');
        self._trigger('show', null, self._ui(clicked, $show[0]));
      };

    // Hide a tab, $show is optional...
    var hideTab = hideFx ?
      function(clicked, $hide) {
        $hide.animate(hideFx, hideFx.duration || 'normal', function() {
          self.lis.removeClass('ui-tabs-selected ui-state-active').addClass('ui-state-default');
          $hide.addClass('ui-tabs-hide');
          resetStyle($hide, hideFx);
          self.element.dequeue("tabs");
        });
      } :
      function(clicked, $hide, $show) {
        self.lis.removeClass('ui-tabs-selected ui-state-active').addClass('ui-state-default');
        $hide.addClass('ui-tabs-hide');
        self.element.dequeue("tabs");
      };

    // attach tab event handler, unbind to avoid duplicates from former tabifying...
    this.anchors.bind(o.event + '.tabs', function() {
      var el = this, $li = $(this).closest('li'), $hide = self.panels.filter(':not(.ui-tabs-hide)'),
          $show = $(self._sanitizeSelector(this.hash));

      // If tab is already selected and not collapsible or tab disabled or
      // or is already loading or click callback returns false stop here.
      // Check if click handler returns false last so that it is not executed
      // for a disabled or loading tab!
      if (($li.hasClass('ui-tabs-selected') && !o.collapsible) ||
        $li.hasClass('ui-state-disabled') ||
        $li.hasClass('ui-state-processing') ||
        self._trigger('select', null, self._ui(this, $show[0])) === false) {
        this.blur();
        return false;
      }

      o.selected = self.anchors.index(this);

      self.abort();

      // if tab may be closed
      if (o.collapsible) {
        if ($li.hasClass('ui-tabs-selected')) {
          o.selected = -1;

          if (o.cookie) {
            self._cookie(o.selected, o.cookie);
          }

          self.element.queue("tabs", function() {
            hideTab(el, $hide);
          }).dequeue("tabs");

          this.blur();
          return false;
        }
        else if (!$hide.length) {
          if (o.cookie) {
            self._cookie(o.selected, o.cookie);
          }

          self.element.queue("tabs", function() {
            showTab(el, $show);
          });

          self.load(self.anchors.index(this)); // TODO make passing in node possible, see also http://dev.jqueryui.com/ticket/3171

          this.blur();
          return false;
        }
      }

      if (o.cookie) {
        self._cookie(o.selected, o.cookie);
      }

      // show new tab
      if ($show.length) {
        if ($hide.length) {
          self.element.queue("tabs", function() {
            hideTab(el, $hide);
          });
        }
        self.element.queue("tabs", function() {
          showTab(el, $show);
        });

        self.load(self.anchors.index(this));
      }
      else {
        throw 'jQuery UI Tabs: Mismatching fragment identifier.';
      }

      // Prevent IE from keeping other link focussed when using the back button
      // and remove dotted border from clicked link. This is controlled via CSS
      // in modern browsers; blur() removes focus from address bar in Firefox
      // which can become a usability and annoying problem with tabs('rotate').
      if ($.browser.msie) {
        this.blur();
      }

    });

    // disable click in any case
    this.anchors.bind('click.tabs', function(){return false;});

  },

  destroy: function() {
    var o = this.options;

    this.abort();

    this.element.unbind('.tabs')
      .removeClass('ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible')
      .removeData('tabs');

    this.list.removeClass('ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all');

    this.anchors.each(function() {
      var href = $.data(this, 'href.tabs');
      if (href) {
        this.href = href;
      }
      var $this = $(this).unbind('.tabs');
      $.each(['href', 'load', 'cache'], function(i, prefix) {
        $this.removeData(prefix + '.tabs');
      });
    });

    this.lis.unbind('.tabs').add(this.panels).each(function() {
      if ($.data(this, 'destroy.tabs')) {
        $(this).remove();
      }
      else {
        $(this).removeClass([
          'ui-state-default',
          'ui-corner-top',
          'ui-tabs-selected',
          'ui-state-active',
          'ui-state-hover',
          'ui-state-focus',
          'ui-state-disabled',
          'ui-tabs-panel',
          'ui-widget-content',
          'ui-corner-bottom',
          'ui-tabs-hide'
        ].join(' '));
      }
    });

    if (o.cookie) {
      this._cookie(null, o.cookie);
    }
  },

  add: function(url, label, index) {
    if (index === undefined) {
      index = this.anchors.length; // append by default
    }

    var self = this, o = this.options,
      $li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label)),
      id = !url.indexOf('#') ? url.replace('#', '') : this._tabId($('a', $li)[0]);

    $li.addClass('ui-state-default ui-corner-top').data('destroy.tabs', true);

    // try to find an existing element before creating a new one
    var $panel = $('#' + id);
    if (!$panel.length) {
      $panel = $(o.panelTemplate).attr('id', id).data('destroy.tabs', true);
    }
    $panel.addClass('ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide');

    if (index >= this.lis.length) {
      $li.appendTo(this.list);
      $panel.appendTo(this.list[0].parentNode);
    }
    else {
      $li.insertBefore(this.lis[index]);
      $panel.insertBefore(this.panels[index]);
    }

    o.disabled = $.map(o.disabled,
      function(n, i) { return n >= index ? ++n : n; });

    this._tabify();

    if (this.anchors.length == 1) { // after tabify
      $li.addClass('ui-tabs-selected ui-state-active');
      $panel.removeClass('ui-tabs-hide');
      this.element.queue("tabs", function() {
        self._trigger('show', null, self._ui(self.anchors[0], self.panels[0]));
      });

      this.load(0);
    }

    // callback
    this._trigger('add', null, this._ui(this.anchors[index], this.panels[index]));
  },

  remove: function(index) {
    var o = this.options, $li = this.lis.eq(index).remove(),
      $panel = this.panels.eq(index).remove();

    // If selected tab was removed focus tab to the right or
    // in case the last tab was removed the tab to the left.
    if ($li.hasClass('ui-tabs-selected') && this.anchors.length > 1) {
      this.select(index + (index + 1 < this.anchors.length ? 1 : -1));
    }

    o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }),
      function(n, i) { return n >= index ? --n : n; });

    this._tabify();

    // callback
    this._trigger('remove', null, this._ui($li.find('a')[0], $panel[0]));
  },

  enable: function(index) {
    var o = this.options;
    if ($.inArray(index, o.disabled) == -1) {
      return;
    }

    this.lis.eq(index).removeClass('ui-state-disabled');
    o.disabled = $.grep(o.disabled, function(n, i) { return n != index; });

    // callback
    this._trigger('enable', null, this._ui(this.anchors[index], this.panels[index]));
  },

  disable: function(index) {
    var self = this, o = this.options;
    if (index != o.selected) { // cannot disable already selected tab
      this.lis.eq(index).addClass('ui-state-disabled');

      o.disabled.push(index);
      o.disabled.sort();

      // callback
      this._trigger('disable', null, this._ui(this.anchors[index], this.panels[index]));
    }
  },

  select: function(index) {
    if (typeof index == 'string') {
      index = this.anchors.index(this.anchors.filter('[href$=' + index + ']'));
    }
    else if (index === null) { // usage of null is deprecated, TODO remove in next release
      index = -1;
    }
    if (index == -1 && this.options.collapsible) {
      index = this.options.selected;
    }

    this.anchors.eq(index).trigger(this.options.event + '.tabs');
  },

  load: function(index) {
    var self = this, o = this.options, a = this.anchors.eq(index)[0], url = $.data(a, 'load.tabs');

    this.abort();

    // not remote or from cache
    if (!url || this.element.queue("tabs").length !== 0 && $.data(a, 'cache.tabs')) {
      this.element.dequeue("tabs");
      return;
    }

    // load remote from here on
    this.lis.eq(index).addClass('ui-state-processing');

    if (o.spinner) {
      var span = $('span', a);
      span.data('label.tabs', span.html()).html(o.spinner);
    }

    this.xhr = $.ajax($.extend({}, o.ajaxOptions, {
      url: url,
      success: function(r, s) {
        $(self._sanitizeSelector(a.hash)).html(r);

        // take care of tab labels
        self._cleanup();

        if (o.cache) {
          $.data(a, 'cache.tabs', true); // if loaded once do not load them again
        }

        // callbacks
        self._trigger('load', null, self._ui(self.anchors[index], self.panels[index]));
        try {
          o.ajaxOptions.success(r, s);
        }
        catch (e) {}

        // last, so that load event is fired before show...
        self.element.dequeue("tabs");
      }
    }));
  },

  abort: function() {
    // stop possibly running animations
    this.element.queue([]);
    this.panels.stop(false, true);

    // terminate pending requests from other tabs
    if (this.xhr) {
      this.xhr.abort();
      delete this.xhr;
    }

    // take care of tab labels
    this._cleanup();

  },

  url: function(index, url) {
    this.anchors.eq(index).removeData('cache.tabs').data('load.tabs', url);
  },

  length: function() {
    return this.anchors.length;
  }

});

$.extend($.ui.tabs, {
  version: '1.7.2',
  getter: 'length',
  defaults: {
    ajaxOptions: null,
    cache: false,
    cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
    collapsible: false,
    disabled: [],
    event: 'click',
    fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
    idPrefix: 'ui-tabs-',
    panelTemplate: '<div></div>',
    spinner: '<em>Loading&#8230;</em>',
    tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>'
  }
});

/*
 * Tabs Extensions
 */

/*
 * Rotate
 */
$.extend($.ui.tabs.prototype, {
  rotation: null,
  rotate: function(ms, continuing) {

    var self = this, o = this.options;

    var rotate = self._rotate || (self._rotate = function(e) {
      clearTimeout(self.rotation);
      self.rotation = setTimeout(function() {
        var t = o.selected;
        self.select( ++t < self.anchors.length ? t : 0 );
      }, ms);

      if (e) {
        e.stopPropagation();
      }
    });

    var stop = self._unrotate || (self._unrotate = !continuing ?
      function(e) {
        if (e.clientX) { // in case of a true click
          self.rotate(null);
        }
      } :
      function(e) {
        t = o.selected;
        rotate();
      });

    // start rotation
    if (ms) {
      this.element.bind('tabsshow', rotate);
      this.anchors.bind(o.event + '.tabs', stop);
      rotate();
    }
    // stop rotation
    else {
      clearTimeout(self.rotation);
      this.element.unbind('tabsshow', rotate);
      this.anchors.unbind(o.event + '.tabs', stop);
      delete this._rotate;
      delete this._unrotate;
    }
  }
});

})(jQuery);
