define([
    'jquery',
    'underscore',
    'viewcontroller',
    'chroma',
    'slickformatters',
    'slickeditors'
], function($, _, ViewControllers, chroma, slickformatters, slickeditors) {

  // we only use the base attribute class, no need to get the base class
  /** @private */
  var EmperorAttributeABC = ViewControllers.EmperorAttributeABC;
  /**
   * @class VisibilityController
   *
   * Manipulates and displays the visibility of objects on screen.
   *
   * @param {UIState} uiState The shared state
   * @param {Node} container Container node to create the controller in.
   * @param {Object} decompViewDict This object is keyed by unique
   * identifiers and the values are DecompositionView objects referring to a
   * set of objects presented on screen. This dictionary will usually be shared
   * by all the tabs in the application. This argument is passed by reference.
   *
   * @return {VisibilityController} An instance of VisibilityController
   * @constructs VisibilityController
   * @extends EmperorAttributeABC
   */
  function VisibilityController(uiState, container, decompViewDict) {
    var helpmenu = 'Change the visibility of the attributes on the plot, ' +
                   'such as spheres, vectors and ellipsoids.';
    var title = 'Visibility';

    // Constant for width in slick-grid
    var SLICK_WIDTH = 25, scope = this;

    // Build the options dictionary
    var options = {'valueUpdatedCallback': function(e, args) {
      var visible = args.item.value;
      var group = args.item.plottables;
      var element = scope.getView();
      scope.setPlottableAttributes(element, visible, group);
    },
      'categorySelectionCallback': function(evt, params) {
        var category = scope.$select.val();

        var decompViewDict = scope.getView();

        // getting all unique values per categories
        var uniqueVals = decompViewDict.decomp.getUniqueValuesByCategory(
          category);
        // getting color for each uniqueVals
        var attributes = {};
        _.each(uniqueVals, function(value) {
          attributes[value] = true;
        });
        // fetch the slickgrid-formatted data
        var data = decompViewDict.setCategory(
          attributes, scope.setPlottableAttributes, category);

        scope.setSlickGridDataset(data);
      },
      'slickGridColumn': {id: 'title', name: '', field: 'value',
        sortable: false, maxWidth: SLICK_WIDTH,
        minWidth: SLICK_WIDTH,
        formatter: Slick.Formatters.Checkmark,
        editor: Slick.Editors.Checkbox}};

    EmperorAttributeABC.call(this, uiState, container, title, helpmenu,
        decompViewDict, options);

    this.$applyAll = $("<input type='button' value='Toggle Visible'>"
    ).css({
      'margin': '0 auto',
      'display': 'block'
    });
    this.$header.append(this.$applyAll);

    $(function() {
      scope.$applyAll.hide();
      scope.$applyAll.on('click', function() {
        scope.toggleVisibility();
      });
    });


    return this;
  }
  VisibilityController.prototype = Object.create(EmperorAttributeABC.prototype);
  VisibilityController.prototype.constructor = EmperorAttributeABC;

  /**
   *
   * Private method to reset all objects to be visible.
   *
   * @extends EmperorAttributeABC
   * @private
   *
   */
  VisibilityController.prototype._resetAttribute = function() {
    EmperorAttributeABC.prototype._resetAttribute.call(this);

    _.each(this.decompViewDict, function(view) {
      view.setVisibility(true);
      view.showEdgesForPlottables();
    });
  };

  /**
   * Helper function to set the visibility of plottable
   *
   * @param {Object} scope the scope where the plottables exist
   * @param {boolean} visible Visibility of the plottables
   * @param {Object[]} group Array of objects that should be changed in scope
   */
  VisibilityController.prototype.setPlottableAttributes =
      function(scope, visible, group) {
    scope.setVisibility(visible, group);
  };

  /**
   * Sets whether or not elements in the tab can be modified.
   *
   * @param {Boolean} trulse option to enable elements.
   */
  VisibilityController.prototype.setEnabled = function(trulse) {
    EmperorAttributeABC.prototype.setEnabled.call(this, trulse);

    // prevent errors when this method is called before the element exists
    if (this.$applyAll) {
      if (trulse) {
        this.$applyAll.show();
      }
      else {
        this.$applyAll.hide();
      }
    }
  };

  /**
   * Toggle visible objects
   */
  VisibilityController.prototype.toggleVisibility = function() {
    var view = this.getView();

    var groups = this.getSlickGridDataset();
    _.each(groups, function(group) {

      // toggle the value in the data container and update the view
      group.value = Boolean(true ^ group.value);
      view.setVisibility(group.value, group.plottables);
    });

    // lastly, update the grid table
    this.setSlickGridDataset(groups);
  };


  return VisibilityController;
});
