/**
 * @file
 * JQuery.fn.sortElements
 * --------------.
 * @param Function comparator:
 *   Exactly the same behaviour as [1,2,3].sort(comparator)
 * @param Function getSortable
 *   A function that should return the element that is
 *   to be sorted. The comparator will run on the
 *   current collection, but you may want the actual
 *   resulting sort to occur on a parent or another
 *   associated element.
 *
 *   E.g. $('td').sortElements(comparator, function(){
 *      return this.parentNode;
 *   })
 *
 *   The <td>'s parent (<tr>) will be sorted instead
 *   of the <td> itself.
 *
 * Credit: http://james.padolsey.com/javascript/sorting-elements-with-jquery/
 */

jQuery.fn.sortElements = (function () {

  "use strict";

  var sort = [].sort;

  return function (comparator, getSortable) {

    getSortable = getSortable || function () {return this;};

    var placements = this.map(function () {

      var sortElement = getSortable.call(this);
      var parentNode = sortElement.parentNode;

      // Since the element itself will change position, we have
      // to have some way of storing its original position in
      // the DOM. The easiest way is to have a 'flag' node:
      var nextSibling = parentNode.insertBefore(
          document.createTextNode(''),
          sortElement.nextSibling
        );

      return function () {

        if (parentNode === this) {
          throw new Error(
            "You can't sort elements if any one is a descendant of another."
          );
        }

        // Insert before flag:
        parentNode.insertBefore(this, nextSibling);
        // Remove flag:
        parentNode.removeChild(nextSibling);

      };

    });

    return sort.call(this, comparator).each(function (i) {
      placements[i].call(getSortable.call(this));
    });

  };

})();

(function ($) {

  "use strict";

  Drupal.behaviors.features = {
    attach: function (context) {

      // Mark any conflicts with a class.
      if ((typeof drupalSettings.features !== 'undefined') && (typeof drupalSettings.features.conflicts !== 'undefined')) {
      // For (var configType in drupalSettings.features.conflicts) {.
          if (drupalSettings.features.conflicts) {
            var configConflicts = drupalSettings.features.conflicts;
            $('.js-features-export-wrapper input[type=checkbox]:not(.js-features-checkall)', context).each(function () {
              var key = $(this).attr('name');
              var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/);
              var component = matches[1];
              var item = matches[4];
              if ((component in configConflicts) && (item in configConflicts[component])) {
                $(this).parent().addClass('component-conflict');
              }
            });
          }
        // }
      }

      function _checkAll(value) {
        if (value) {
          $('.js-components-select input[type=checkbox]:visible', context).each(function () {
            var move_id = $(this).attr('id');
            $(this).click();
            $('#' + move_id).prop('checked', true);
          });
        }
        else {
          $('.js-components-added input[type=checkbox]:visible', context).each(function () {
            var move_id = $(this).attr('id');
            $(this).click();
            $('#' + move_id).prop('checked', false);
          });
        }
      }

      function updateComponentCountInfo(item, section) {
        var parent;

        switch (section) {
          case 'select':
            parent = $(item).closest('.js-features-export-list').siblings('.js-features-export-component');
            $('.js-component-count', parent).text(function (index, text) {
                return +text + 1;
              }
            );
            break;

          case 'added':
          case 'detected':
            parent = $(item).closest('.js-features-export-component');
            $('.js-component-count', parent).text(function (index, text) {
              return text - 1;
            });
        }
      }

      function moveCheckbox(item, section, value) {
        updateComponentCountInfo(item, section);
        var curParent = item;
        if ($(item).hasClass('js-form-type-checkbox')) {
          item = $(item).children('input[type=checkbox]');
        }
        else {
          curParent = $(item).parents('.js-form-type-checkbox');
        }
        var newParent = $(curParent).parents('.js-features-export-parent').find('.js-components-' + section + ' .form-checkboxes');
        $(curParent).detach();
        $(curParent).appendTo(newParent);
        var list = ['select', 'added', 'detected', 'included'];
        for (var i in list) {
          if (list[i]) {
            $(curParent).removeClass('component-' + list[i]);
            $(item).removeClass('component-' + list[i]);
          }
        }
        $(curParent).addClass('component-' + section);
        $(item).addClass('component-' + section);
        if (value) {
          $(item).attr('checked', 'checked');
        }
        else {
          $(item).removeAttr('checked');
        }
        $(newParent).parents('.js-features-export-list').removeClass('features-export-empty');

        // re-sort new list of checkboxes based on labels.
        $(newParent).find('label').sortElements(
          function (a, b) {
            return $(a).text() > $(b).text() ? 1 : -1;
          },
          function () {
            return this.parentNode;
          }
        );
      }

      // Provide timer for auto-refresh trigger.
      var timeoutID = 0;
      var inTimeout = 0;
      function _triggerTimeout() {
        timeoutID = 0;
        _updateDetected();
      }
      function _resetTimeout() {
        inTimeout++;
        // If timeout is already active, reset it.
        if (timeoutID !== 0) {
          window.clearTimeout(timeoutID);
          if (inTimeout > 0) {
            inTimeout--;
          }
        }
        timeoutID = window.setTimeout(_triggerTimeout, 500);
      }

      function _updateDetected() {
        if (!drupalSettings.features.autodetect) {
          return;
        }
        // Query the server for a list of components/items in the feature and update
        // the auto-detected items.
        var items = [];  // Will contain a list of selected items exported to feature.
        var components = {};  // Contains object of component names that have checked items.
        $('.js-features-export-wrapper input[type=checkbox]:not(.js-features-checkall):checked', context).each(function () {
          var key = $(this).attr('name');
          var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/);
          components[matches[1]] = matches[1];
          if (!$(this).hasClass('component-detected')) {
            items.push(key);
          }
        });
        var featureName = $('#edit-machine-name').val();
        if (featureName === '') {
          featureName = '*';
        }

        var url = Drupal.url('features/api/detect/' + featureName);
        var excluded = drupalSettings.features.excluded;
        var required = drupalSettings.features.required;
        var postData = {'items': items, 'excluded': excluded, 'required': required};
        jQuery.post(url, postData, function (data) {
          if (inTimeout > 0) {
inTimeout--; }
          // If we have triggered another timeout then don't update with old results.
          if (inTimeout === 0) {
            // Data is an object keyed by component listing the exports of the feature.
            for (var component in data) {
              if (data[component]) {
                var itemList = data[component];
                $('.js-component--name-' + component + ' input[type=checkbox]', context).each(function () {
                  var key = $(this).attr('value');
                  // First remove any auto-detected items that are no longer in component.
                  if ($(this).hasClass('component-detected')) {
                    if (!(key in itemList)) {
                      moveCheckbox(this, 'select', false);
                    }
                  }
                  // Next, add any new auto-detected items.
                  else if ($(this).hasClass('component-select')) {
                    if (key in itemList) {
                      moveCheckbox(this, 'detected', itemList[key]);
                      $(this).prop('checked', true);
                      $(this).parent().show(); // Make sure it's not hidden from filter.
                    }
                  }
                });
              }
            }
            // Loop over all selected components and check for any that have been completely removed.
            for (var selectedComponent in components) {
              if ((data == null) || !(selectedComponent in data)) {
                $('.js-component--name-' + selectedComponent + ' input[type=checkbox].component-detected', context).each(function () {
                  moveCheckbox(this, 'select', false);
                });
              }
            }
          }
        }, "json");
      }

      // Handle component selection UI.
      $('.js-features-export-wrapper input[type=checkbox]', context).click(function () {
        _resetTimeout();
        if ($(this).hasClass('component-select')) {
          moveCheckbox(this, 'added', true);
        }
        else if ($(this).hasClass('component-included')) {
          moveCheckbox(this, 'added', false);
        }
        else if ($(this).hasClass('component-added')) {
          if ($(this).is(':checked')) {
            moveCheckbox(this, 'included', true);
          }
          else {
            moveCheckbox(this, 'select', false);
          }
        }
      });

      // Handle select/unselect all.
      $('.js-features-checkall', context).click(function () {
        if ($(this).prop('checked')) {
          _checkAll(true);
          $(this).next().html(Drupal.t('Deselect all'));
        }
        else {
          _checkAll(false);
          $(this).next().html(Drupal.t('Select all'));
        }
        _resetTimeout();
      });

      // Handle filtering.
      // Provide timer for auto-refresh trigger.
      var filterTimeoutID = 0;
      function _triggerFilterTimeout() {
        filterTimeoutID = 0;
        _updateFilter();
      }
      function _resetFilterTimeout() {
        // If timeout is already active, reset it.
        if (filterTimeoutID !== 0) {
          window.clearTimeout(filterTimeoutID);
          filterTimeoutID = null;
        }
        filterTimeoutID = window.setTimeout(_triggerFilterTimeout, 200);
      }
      function _updateFilter() {
        var filter = $('.js-features-filter-input').val();
        var regex = new RegExp(filter, 'i');
        // Collapse fieldsets.
        var newState = {};
        var currentState = {};
        $('.js-features-export-component', context).each(function () {
          // Expand parent fieldset.
          var section = $(this).attr('id');
          var details = $(this);

          currentState[section] = details.prop('open');
          if (!(section in newState)) {
            newState[section] = false;
          }

          details.find('.form-checkboxes label').each(function () {
            if (filter === '') {
              // Collapse the section, but make checkbox visible.
              if (currentState[section]) {
                details.prop('open', false);
                currentState[section] = false;
              }
              $(this).parent().show();
            }
            else if ($(this).text().match(regex)) {
              $(this).parent().show();
              newState[section] = true;
            }
            else {
              $(this).parent().hide();
            }
          });
        });
        for (var section in newState) {
          if (currentState[section] !== newState[section]) {
            if (newState[section]) {
              $('#' + section).prop('open', true);
            }
            else {
              $('#' + section).prop('open', false);
            }
          }
        }
      }
      $('.js-features-filter-input', context).bind("input", function () {
        _resetFilterTimeout();
      });
      $('.js-features-filter-clear', context).click(function () {
        $('.js-features-filter-input').val('');
        _updateFilter();
      });

      // Show the filter bar.
      $('.js-features-filter', context).removeClass('visually-hidden');

      // Handle Package selection checkboxes in the Differences page.
      $('.features-diff-listing .features-diff-header input.form-checkbox', context).click(function () {
        var value = $(this).prop('checked');
        $('.features-diff-listing .diff-' + $(this).prop('value') + ' input.form-checkbox', context).each(function () {
          $(this).prop('checked', value);
          if (value) {
            $(this).parents('tr').addClass('selected');
          }
          else {
            $(this).parents('tr').removeClass('selected');
          }
        });
      });

      // Handle special theming of headers in tableselect.
      $('td.features-export-header-row', context).each(function () {
        var row = $(this).parent('tr');
        row.addClass('features-export-header-row');
        var checkbox = row.find('td input:checkbox');
        if (checkbox.length) {
          checkbox.hide();
        }
      });

      // Handle clicking anywhere in row on Differences page.
      $('.features-diff-listing tr td:nth-child(2)', context).click(function () {
        var checkbox = $(this).parent().find('td input:checkbox');
        checkbox.prop('checked', !checkbox.prop('checked')).triggerHandler('click');
        if (checkbox.prop('checked')) {
          $(this).parents('tr').addClass('selected');
        }
        else {
          $(this).parents('tr').removeClass('selected');
        }
      });
      $('.features-diff-listing thead th:nth-child(2)', context).click(function () {
        var checkbox = $(this).parent().find('th input:checkbox');
        checkbox.click();
      });
    }
  };

})(jQuery);
