GUI = function(pars) {
  const _this = this;

  let params = pars || {};

  /**
   * Outermost DOM Element
   * @type {DOMElement}
   */
  this.domElement = document.createElement('div');
  this.__ul = document.createElement('ul');
  this.domElement.appendChild(this.__ul);

  dom.addClass(this.domElement, CSS_NAMESPACE);

  /**
   * Nested GUI's by name
   * @ignore
   */
  this.__folders = {};

  this.__controllers = [];

  /**
   * List of objects I'm remembering for save, only used in top level GUI
   * @ignore
   */
  this.__rememberedObjects = [];

  /**
   * Maps the index of remembered objects to a map of controllers, only used
   * in top level GUI.
   *
   * @private
   * @ignore
   *
   * @example
   * [
   *  {
     *    propertyName: Controller,
     *    anotherPropertyName: Controller
     *  },
   *  {
     *    propertyName: Controller
     *  }
   * ]
   */
  this.__rememberedObjectIndecesToControllers = [];

  this.__listening = [];

  // Default parameters
  params = common.defaults(params, {
    closeOnTop: false,
    autoPlace: true,
    width: GUI.DEFAULT_WIDTH
  });

  params = common.defaults(params, {
    resizable: params.autoPlace,
    hideable: params.autoPlace
  });

  if (!common.isUndefined(params.load)) {
    // Explicit preset
    if (params.preset) {
      params.load.preset = params.preset;
    }
  } else {
    params.load = { preset: DEFAULT_DEFAULT_PRESET_NAME };
  }

  if (common.isUndefined(params.parent) && params.hideable) {
    hideableGuis.push(this);
  }

  // Only root level GUI's are resizable.
  params.resizable = common.isUndefined(params.parent) && params.resizable;

  if (params.autoPlace && common.isUndefined(params.scrollable)) {
    params.scrollable = true;
  }
  //    params.scrollable = common.isUndefined(params.parent) && params.scrollable === true;

  // Not part of params because I don't want people passing this in via
  // constructor. Should be a 'remembered' value.
  let useLocalStorage =
    SUPPORTS_LOCAL_STORAGE &&
    localStorage.getItem(getLocalStorageHash(this, 'isLocal')) === 'true';

  let saveToLocalStorage;
  let titleRow;

  Object.defineProperties(this,
    /** @lends GUI.prototype */
    {
      /**
       * The parent <code>GUI</code>
       * @type dat.gui.GUI
       */
      parent: {
        get: function() {
          return params.parent;
        }
      },

      scrollable: {
        get: function() {
          return params.scrollable;
        }
      },

      /**
       * Handles <code>GUI</code>'s element placement for you
       * @type Boolean
       */
      autoPlace: {
        get: function() {
          return params.autoPlace;
        }
      },

      /**
       * Handles <code>GUI</code>'s position of open/close button
       * @type Boolean
       */
      closeOnTop: {
        get: function() {
          return params.closeOnTop;
        }
      },

      /**
       * The identifier for a set of saved values
       * @type String
       */
      preset: {
        get: function() {
          if (_this.parent) {
            return _this.getRoot().preset;
          }

          return params.load.preset;
        },

        set: function(v) {
          if (_this.parent) {
            _this.getRoot().preset = v;
          } else {
            params.load.preset = v;
          }
          setPresetSelectIndex(this);
          _this.revert();
        }
      },

      /**
       * The width of <code>GUI</code> element
       * @type Number
       */
      width: {
        get: function() {
          return params.width;
        },
        set: function(v) {
          params.width = v;
          setWidth(_this, v);
        }
      },

      /**
       * The name of <code>GUI</code>. Used for folders. i.e
       * a folder's name
       * @type String
       */
      name: {
        get: function() {
          return params.name;
        },
        set: function(v) {
          // TODO Check for collisions among sibling folders
          params.name = v;
          if (titleRow) {
            titleRow.innerHTML = params.name;
          }
        }
      },

      /**
       * Whether the <code>GUI</code> is collapsed or not
       * @type Boolean
       */
      closed: {
        get: function() {
          return params.closed;
        },
        set: function(v) {
          params.closed = v;
          if (params.closed) {
            dom.addClass(_this.__ul, GUI.CLASS_CLOSED);
          } else {
            dom.removeClass(_this.__ul, GUI.CLASS_CLOSED);
          }
          // For browsers that aren't going to respect the CSS transition,
          // Lets just check our height against the window height right off
          // the bat.
          this.onResize();

          if (_this.__closeButton) {
            _this.__closeButton.innerHTML = v ? GUI.TEXT_OPEN : GUI.TEXT_CLOSED;
          }
        }
      },

      /**
       * Contains all presets
       * @type Object
       */
      load: {
        get: function() {
          return params.load;
        }
      },

      /**
       * Determines whether or not to use <a href="https://developer.mozilla.org/en/DOM/Storage#localStorage">localStorage</a> as the means for
       * <code>remember</code>ing
       * @type Boolean
       */
      useLocalStorage: {

        get: function() {
          return useLocalStorage;
        },
        set: function(bool) {
          if (SUPPORTS_LOCAL_STORAGE) {
            useLocalStorage = bool;
            if (bool) {
              dom.bind(window, 'unload', saveToLocalStorage);
            } else {
              dom.unbind(window, 'unload', saveToLocalStorage);
            }
            localStorage.setItem(getLocalStorageHash(_this, 'isLocal'), bool);
          }
        }
      }
    });

  // Are we a root level GUI?
  if (common.isUndefined(params.parent)) {
    this.closed = params.closed || false;

    dom.addClass(this.domElement, GUI.CLASS_MAIN);
    dom.makeSelectable(this.domElement, false);

    // Are we supposed to be loading locally?
    if (SUPPORTS_LOCAL_STORAGE) {
      if (useLocalStorage) {
        _this.useLocalStorage = true;

        const savedGui = localStorage.getItem(getLocalStorageHash(this, 'gui'));

        if (savedGui) {
          params.load = JSON.parse(savedGui);
        }
      }
    }

    this.__closeButton = document.createElement('div');
    this.__closeButton.innerHTML = GUI.TEXT_CLOSED;
    dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BUTTON);
    if (params.closeOnTop) {
      dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_TOP);
      this.domElement.insertBefore(this.__closeButton, this.domElement.childNodes[0]);
    } else {
      dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BOTTOM);
      this.domElement.appendChild(this.__closeButton);
    }

    dom.bind(this.__closeButton, 'click', function() {
      _this.closed = !_this.closed;
    });
    // Oh, you're a nested GUI!
  } else {
    if (params.closed === undefined) {
      params.closed = true;
    }

    const titleRowName = document.createTextNode(params.name);
    dom.addClass(titleRowName, 'controller-name');

    titleRow = addRow(_this, titleRowName);

    const onClickTitle = function(e) {
      e.preventDefault();
      _this.closed = !_this.closed;
      return false;
    };

    dom.addClass(this.__ul, GUI.CLASS_CLOSED);

    dom.addClass(titleRow, 'title');
    dom.bind(titleRow, 'click', onClickTitle);

    if (!params.closed) {
      this.closed = false;
    }
  }

  if (params.autoPlace) {
    if (common.isUndefined(params.parent)) {
      if (autoPlaceVirgin) {
        autoPlaceContainer = document.createElement('div');
        dom.addClass(autoPlaceContainer, CSS_NAMESPACE);
        dom.addClass(autoPlaceContainer, GUI.CLASS_AUTO_PLACE_CONTAINER);
        document.body.appendChild(autoPlaceContainer);
        autoPlaceVirgin = false;
      }

      // Put it in the dom for you.
      autoPlaceContainer.appendChild(this.domElement);

      // Apply the auto styles
      dom.addClass(this.domElement, GUI.CLASS_AUTO_PLACE);
    }


    // Make it not elastic.
    if (!this.parent) {
      setWidth(_this, params.width);
    }
  }

  this.__resizeHandler = function() {
    _this.onResizeDebounced();
  };

  dom.bind(window, 'resize', this.__resizeHandler);
  dom.bind(this.__ul, 'webkitTransitionEnd', this.__resizeHandler);
  dom.bind(this.__ul, 'transitionend', this.__resizeHandler);
  dom.bind(this.__ul, 'oTransitionEnd', this.__resizeHandler);
  this.onResize();

  if (params.resizable) {
    addResizeHandle(this);
  }

  saveToLocalStorage = function() {
    if (SUPPORTS_LOCAL_STORAGE && localStorage.getItem(getLocalStorageHash(_this, 'isLocal')) === 'true') {
      localStorage.setItem(getLocalStorageHash(_this, 'gui'), JSON.stringify(_this.getSaveObject()));
    }
  };

  // expose this method publicly
  this.saveToLocalStorageIfPossible = saveToLocalStorage;

  function resetWidth() {
    const root = _this.getRoot();
    root.width += 1;
    common.defer(function() {
      root.width -= 1;
    });
  }

  if (!params.parent) {
    resetWidth();
  }
}
@class A lightweight controller library for JavaScript. It allows you to easily
manipulate variables and fire functions on the fly.

@typicalname gui

@example
// Creating a GUI with options.
var gui = new dat.GUI({name: 'My GUI'});

@example
// Creating a GUI and a subfolder.
var gui = new dat.GUI();
var folder1 = gui.addFolder('Flow Field');

@param {Object} [params]
@param {String} [params.name] The name of this GUI.
@param {Object} [params.load] JSON object representing the saved state of
this GUI.
@param {dat.gui.GUI} [params.parent] The GUI I'm nested in.
@param {Boolean} [params.autoPlace=true]
@param {Boolean} [params.hideable=true] If true, GUI is shown/hidden by <kbd>h</kbd> keypress.
@param {Boolean} [params.closed=false] If true, starts closed
@param {Boolean} [params.closeOnTop=false] If true, close/open button shows on top of the GUI