/*
 * Table of Contents jQuery Plugin - jquery.toc
 *
 * Copyright 2013-2016 Nikhil Dabas
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied.  See the License for the specific language governing permissions and limitations
 * under the License.
 */

(function($) {
  "use strict";

  // Builds a list with the table of contents in the current selector.
  // options:
  //   content: where to look for headings
  //   headings: string with a comma-separated list of selectors to be used as headings, ordered
  //   by their relative hierarchy level
  var toc = function(options) {
      return this.each(function() {
        var root = $(this),
          data = root.data(),
          thisOptions,
          stack = [root], // The upside-down stack keeps track of list elements
          listTag = this.tagName,
          currentLevel = 0,
          headingSelectors;

        // Defaults: plugin parameters override data attributes, which override our defaults
        thisOptions = $.extend({
            content: "body",
            headings: "h1,h2,h3,h4,h5,h6"
          }, {
            content: data.toc || undefined,
            headings: data.tocHeadings || undefined
          },
          options
        );
        headingSelectors = thisOptions.headings.split(",");

        if (!$(thisOptions.content).find(thisOptions.headings).length) {
          var $module_scope = $('body').find('.fl-node-' + options.scope);
          $module_scope.find('.uabb-parent-wrapper-toc').addClass('uabb-toc-content-empty');
        }

        $(thisOptions.content).find(thisOptions.headings).addClass("uabb-toc-text");

        var exclude_parent = $('body').find('.uabb-toc-hide-heading');
        var exclude_ids = [];
        exclude_parent.each(function(i) {
          var $this = $(this);
          if ($this.hasClass('uabb-toc-text')) {
            $this.addClass('uabb-toc-hidden-item');
          }
          $this.find('.uabb-toc-text').addClass('uabb-toc-hidden-item');
        });

        $(thisOptions.content).find(thisOptions.headings).attr("id", function(index, attr) {
          // Set up some automatic IDs if we do not already have them
          // In HTML5, the id attribute must be at least one character long and must not
          // contain any space characters.
          //
          // We just use the HTML5 spec now because all browsers work fine with it.
          // https://mathiasbynens.be/notes/html5id-class
          if (undefined !== attr) {
            attr = attr.replace(/[&\/\\#,+()$~%.'":*?<>{}]/g, "");
          }
          var generateUniqueId = function(text) {

            // Generate a valid ID. Spaces are replaced with underscores. We also check if
            // the ID already exists in the document. If so, we append "_1", "_2", etc.
            // until we find an unused ID.

            if (text.length === 0) {
              text = "?";
            }

            var baseId = text.replace(/\s+?/g, "_"),
              suffix = "",
              count = 1;
              baseId = baseId.replace(/[&\/\\#,+()$~%.'":*?<>{}]/g, "");
              baseId = baseId.replace(/[^\w\s]/gi, '');           

            while (document.getElementById(baseId + suffix) !== null) {
              suffix = "_" + count++;
            }

            return baseId + suffix;
          };
          return attr || generateUniqueId($(this).text());
        }).each(function() {
          // What level is the current heading?
          var elem = $(this),
            level = $.map(headingSelectors, function(selector, index) {
              return elem.is(selector) ? index : undefined;
            })[0];

          if (elem.hasClass('uabb-toc-hidden-item')) {
            return;
          }

          if (level > currentLevel) {
            // If the heading is at a deeper level than where we are, start a new nested
            // list, but only if we already have some list items in the parent. If we do
            // not, that means that we're skipping levels, so we can just add new list items
            // at the current level.
            // In the upside-down stack, unshift = push, and stack[0] = the top.
            var parentItem = stack[0].children("li:last")[0];
            if (parentItem) {
              stack.unshift($("<" + listTag + "/>").appendTo(parentItem));
            }
          } else {
            // Truncate the stack to the current level by chopping off the 'top' of the
            // stack. We also need to preserve at least one element in the stack - that is
            // the containing element.
            stack.splice(0, Math.min(currentLevel - level, Math.max(stack.length - 1, 0)));
          }

          // Add the list item
          $("<li/>").appendTo(stack[0]).append(
            $("<a/>").text(elem.text()).attr("href", "#" + elem.attr("id"))
          );

          currentLevel = level;
        });
      });
    },
    old = $.fn.toc;

  $.fn.toc = toc;

  $.fn.toc.noConflict = function() {
    $.fn.toc = old;
    return this;
  };

  // Data API
  $(function() {
    toc.call($("[data-toc]"));
  });



}(window.jQuery));
