/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview A color picker component.  A color picker can compose several
 * instances of goog.ui.ColorPalette.
 *
 * NOTE: The ColorPicker is in a state of transition towards the common
 * component/control/container interface we are developing.  If the API changes
 * we will do our best to update your code.  The end result will be that a
 * color picker will compose multiple color palettes.  In the simple case this
 * will be one grid, but may consistute 3 distinct grids, a custom color picker
 * or even a color wheel.
 */

goog.provide('goog.ui.ColorPicker');
goog.provide('goog.ui.ColorPicker.EventType');

goog.require('goog.ui.ColorPalette');
goog.require('goog.ui.Component');
goog.requireType('goog.dom.DomHelper');
goog.requireType('goog.events.Event');
goog.requireType('goog.math.Size');



/**
 * Create a new, empty color picker.
 *
 * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
 * @param {goog.ui.ColorPalette=} opt_colorPalette Optional color palette to
 *     use for this color picker.
 * @extends {goog.ui.Component}
 * @constructor
 * @final
 */
goog.ui.ColorPicker = function(opt_domHelper, opt_colorPalette) {
  'use strict';
  goog.ui.Component.call(this, opt_domHelper);

  /**
   * The color palette used inside the color picker.
   * @type {goog.ui.ColorPalette?}
   * @private
   */
  this.colorPalette_ = opt_colorPalette || null;

  this.getHandler().listen(
      this, goog.ui.Component.EventType.ACTION, this.onColorPaletteAction_);
};
goog.inherits(goog.ui.ColorPicker, goog.ui.Component);


/**
 * Default number of columns in the color palette. May be overridden by calling
 * setSize.
 *
 * @type {number}
 */
goog.ui.ColorPicker.DEFAULT_NUM_COLS = 5;


/**
 * Constants for event names.
 * @enum {string}
 */
goog.ui.ColorPicker.EventType = {
  CHANGE: 'change'
};


/**
 * Whether the component is focusable.
 * @type {boolean}
 * @private
 */
goog.ui.ColorPicker.prototype.focusable_ = true;


/**
 * Gets the array of colors displayed by the color picker.
 * Modifying this array will lead to unexpected behavior.
 * @return {Array<string>?} The colors displayed by this widget.
 */
goog.ui.ColorPicker.prototype.getColors = function() {
  'use strict';
  return this.colorPalette_ ? this.colorPalette_.getColors() : null;
};


/**
 * Sets the array of colors to be displayed by the color picker.
 * @param {Array<string>} colors The array of colors to be added.
 */
goog.ui.ColorPicker.prototype.setColors = function(colors) {
  'use strict';
  // TODO(user): Don't add colors directly, we should add palettes and the
  // picker should support multiple palettes.
  if (!this.colorPalette_) {
    this.createColorPalette_(colors);
  } else {
    this.colorPalette_.setColors(colors);
  }
};


/**
 * Sets the array of colors to be displayed by the color picker.
 * @param {Array<string>} colors The array of colors to be added.
 * @deprecated Use setColors.
 */
goog.ui.ColorPicker.prototype.addColors = function(colors) {
  'use strict';
  this.setColors(colors);
};


/**
 * Sets the size of the palette.  Will throw an error after the picker has been
 * rendered.
 * @param {goog.math.Size|number} size The size of the grid.
 */
goog.ui.ColorPicker.prototype.setSize = function(size) {
  'use strict';
  // TODO(user): The color picker should contain multiple palettes which will
  // all be resized at this point.
  if (!this.colorPalette_) {
    this.createColorPalette_([]);
  }
  this.colorPalette_.setSize(size);
};


/**
 * Gets the number of columns displayed.
 * @return {goog.math.Size?} The size of the grid.
 */
goog.ui.ColorPicker.prototype.getSize = function() {
  'use strict';
  return this.colorPalette_ ? this.colorPalette_.getSize() : null;
};


/**
 * Sets the number of columns.  Will throw an error after the picker has been
 * rendered.
 * @param {number} n The number of columns.
 * @deprecated Use setSize.
 */
goog.ui.ColorPicker.prototype.setColumnCount = function(n) {
  'use strict';
  this.setSize(n);
};


/**
 * @return {number} The index of the color selected.
 */
goog.ui.ColorPicker.prototype.getSelectedIndex = function() {
  'use strict';
  return this.colorPalette_ ? this.colorPalette_.getSelectedIndex() : -1;
};


/**
 * Sets which color is selected. A value that is out-of-range means that no
 * color is selected.
 * @param {number} ind The index in this.colors_ of the selected color.
 */
goog.ui.ColorPicker.prototype.setSelectedIndex = function(ind) {
  'use strict';
  if (this.colorPalette_) {
    this.colorPalette_.setSelectedIndex(ind);
  }
};


/**
 * Gets the color that is currently selected in this color picker.
 * @return {?string} The hex string of the color selected, or null if no
 *     color is selected.
 */
goog.ui.ColorPicker.prototype.getSelectedColor = function() {
  'use strict';
  return this.colorPalette_ ? this.colorPalette_.getSelectedColor() : null;
};


/**
 * Sets which color is selected.  Noop if the color palette hasn't been created
 * yet.
 * @param {string} color The selected color.
 */
goog.ui.ColorPicker.prototype.setSelectedColor = function(color) {
  'use strict';
  // TODO(user): This will set the color in the first available palette that
  // contains it
  if (this.colorPalette_) {
    this.colorPalette_.setSelectedColor(color);
  }
};


/**
 * Returns true if the component is focusable, false otherwise.  The default
 * is true.  Focusable components always have a tab index and allocate a key
 * handler to handle keyboard events while focused.
 * @return {boolean} True iff the component is focusable.
 */
goog.ui.ColorPicker.prototype.isFocusable = function() {
  'use strict';
  return this.focusable_;
};


/**
 * Sets whether the component is focusable.  The default is true.
 * Focusable components always have a tab index and allocate a key handler to
 * handle keyboard events while focused.
 * @param {boolean} focusable True iff the component is focusable.
 */
goog.ui.ColorPicker.prototype.setFocusable = function(focusable) {
  'use strict';
  this.focusable_ = focusable;
  if (this.colorPalette_) {
    this.colorPalette_.setSupportedState(
        goog.ui.Component.State.FOCUSED, focusable);
  }
};


/**
 * ColorPickers cannot be used to decorate pre-existing html, since the
 * structure they build is fairly complicated.
 * @param {Element} element Element to decorate.
 * @return {boolean} Returns always false.
 * @override
 */
goog.ui.ColorPicker.prototype.canDecorate = function(element) {
  'use strict';
  return false;
};


/**
 * Renders the color picker inside the provided element. This will override the
 * current content of the element.
 * @override
 */
goog.ui.ColorPicker.prototype.enterDocument = function() {
  'use strict';
  goog.ui.ColorPicker.superClass_.enterDocument.call(this);
  if (this.colorPalette_) {
    this.colorPalette_.render(this.getElement());
  }
  this.getElement().unselectable = 'on';
};


/** @override */
goog.ui.ColorPicker.prototype.disposeInternal = function() {
  'use strict';
  goog.ui.ColorPicker.superClass_.disposeInternal.call(this);
  if (this.colorPalette_) {
    this.colorPalette_.dispose();
    this.colorPalette_ = null;
  }
};


/**
 * Sets the focus to the color picker's palette.
 */
goog.ui.ColorPicker.prototype.focus = function() {
  'use strict';
  if (this.colorPalette_) {
    this.colorPalette_.getElement().focus();
  }
};


/**
 * Handles actions from the color palette.
 *
 * @param {goog.events.Event} e The event.
 * @private
 */
goog.ui.ColorPicker.prototype.onColorPaletteAction_ = function(e) {
  'use strict';
  e.stopPropagation();
  this.dispatchEvent(goog.ui.ColorPicker.EventType.CHANGE);
};


/**
 * Create a color palette for the color picker.
 * @param {Array<string>} colors Array of colors.
 * @private
 */
goog.ui.ColorPicker.prototype.createColorPalette_ = function(colors) {
  'use strict';
  // TODO(user): The color picker should eventually just contain a number of
  // palettes and manage the interactions between them.  This will go away then.
  var cp = new goog.ui.ColorPalette(colors, null, this.getDomHelper());
  cp.setSize(goog.ui.ColorPicker.DEFAULT_NUM_COLS);
  cp.setSupportedState(goog.ui.Component.State.FOCUSED, this.focusable_);
  // TODO(user): Use addChild(cp, true) and remove calls to render.
  this.addChild(cp);
  this.colorPalette_ = cp;
  if (this.isInDocument()) {
    this.colorPalette_.render(this.getElement());
  }
};


/**
 * Returns an unrendered instance of the color picker.  The colors and layout
 * are a simple color grid, the same as the old Gmail color picker.
 * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
 * @return {!goog.ui.ColorPicker} The unrendered instance.
 */
goog.ui.ColorPicker.createSimpleColorGrid = function(opt_domHelper) {
  'use strict';
  var cp = new goog.ui.ColorPicker(opt_domHelper);
  cp.setSize(7);
  cp.setColors(goog.ui.ColorPicker.SIMPLE_GRID_COLORS);
  return cp;
};


/**
 * Array of colors for a 7-cell wide simple-grid color picker.
 * @type {Array<string>}
 */
goog.ui.ColorPicker.SIMPLE_GRID_COLORS = [
  // grays
  '#ffffff', '#cccccc', '#c0c0c0', '#999999', '#666666', '#333333', '#000000',
  // reds
  '#ffcccc', '#ff6666', '#ff0000', '#cc0000', '#990000', '#660000', '#330000',
  // oranges
  '#ffcc99', '#ff9966', '#ff9900', '#ff6600', '#cc6600', '#993300', '#663300',
  // yellows
  '#ffff99', '#ffff66', '#ffcc66', '#ffcc33', '#cc9933', '#996633', '#663333',
  // olives
  '#ffffcc', '#ffff33', '#ffff00', '#ffcc00', '#999900', '#666600', '#333300',
  // greens
  '#99ff99', '#66ff99', '#33ff33', '#33cc00', '#009900', '#006600', '#003300',
  // turquoises
  '#99ffff', '#33ffff', '#66cccc', '#00cccc', '#339999', '#336666', '#003333',
  // blues
  '#ccffff', '#66ffff', '#33ccff', '#3366ff', '#3333ff', '#000099', '#000066',
  // purples
  '#ccccff', '#9999ff', '#6666cc', '#6633ff', '#6600cc', '#333399', '#330099',
  // violets
  '#ffccff', '#ff99ff', '#cc66cc', '#cc33cc', '#993399', '#663366', '#330033'
];
