(function() {
  'use strict';

  var registeredUtils = [];
  var activeUtilInstances = [];

  var DEBUG_MODE = /localhost/.test(window.location.href) && 0;

  // Registry
  // (revealing module pattern)
  window.UtilRegistry = (function() {

    /**
     * function registerUtil
     *
     * utils need to have at least these properties:
     *   name: string | utils name, e.g. 'example'
     *   selector: string | utils selector, e.g. '[uw-example]'
     *   setup: Function | utils setup function, see below
     *
     * setup function must return instance object with at least these properties:
     *   name: string | utils name
     *   element: HTMLElement | element the util is applied to
     *   destroy: Function | function to destroy the util and remove any listeners
     *
     * @param util Object Utility that should be added to the registry
     */
    function registerUtil(util) {
      if (DEBUG_MODE > 2) {
        console.log('registering util "' + util.name + '"');
        console.log({ util });
      }

      registeredUtils.push(util);
    }

    function deregisterUtil(name, destroy) {
      var utilIndex = _findUtilIndex(name);

      if (utilIndex >= 0) {
        if (destroy === true) {
          _destroyUtilInstances(name);
        }

        registeredUtils.splice(utilIndex, 1);
      }
    }

    function setupAllUtils(scope) {
      if (DEBUG_MODE > 1) {
        console.info('registered js utilities:');
        console.table(registeredUtils);
      }

      registeredUtils.forEach(function(util) {
        setupUtil(util, scope);
      });
    }

    function setupUtil(util, scope) {
      if (DEBUG_MODE > 2) {
        console.log('setting up util', { util });
      }

      scope = scope || document.body;

      if (util && typeof util.setup === 'function') {
        const elements = _findUtilElements(util, scope);

        elements.forEach(function(element) {
          var utilInstance = null;

          try {
            utilInstance = util.setup(element);
          } catch(err) {
            if (DEBUG_MODE > 0) {
              console.warn('Error while trying to initialize a utility!', { util , element, err });
            }
          }

          if (utilInstance) {
            if (DEBUG_MODE > 2) {
              console.info('Got utility instance for utility "' + util.name + '"', { utilInstance });
            }

            activeUtilInstances.push(utilInstance);
          }
        });
      }
    }

    function findUtil(name) {
      return registeredUtils.find(function(util) {
        return util.name === name;
      });
    }

    function _findUtilElements(util, scope) {
      if (scope && scope.matches(util.selector)) {
        return [scope];
      }
      return Array.from(scope.querySelectorAll(util.selector));
    }

    function _findUtilIndex(name) {
      return registeredUtils.findIndex(function(util) {
        return util.name === name;
      });
    }

    function _destroyUtilInstances(name) {
      activeUtilInstances
        .map(function(util, index) {
          return {
            util: util,
            index: index,
          };
        }).filter(function(activeUtil) {
          // find utils instances to destroy
          return activeUtil.util.name === name;
        }).forEach(function(activeUtil) {
          // destroy util instance
          activeUtil.util.destroy();
          delete activeUtilInstances[activeUtil.index];
        });

      // get rid of now empty array slots
      activeUtilInstances = activeUtilInstances.filter(function(util) {
        return !!util;
      })
    }

    // public API
    return {
      register: registerUtil,
      deregister: deregisterUtil,
      setupAll: setupAllUtils,
      setup: setupUtil,
      find: findUtil,
    }
  })();

  document.addEventListener('DOMContentLoaded', function() {
    window.UtilRegistry.setupAll();
  });

})();
