// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('cr.ui', function() {
  /** @const */ var Event = cr.Event;
  /** @const */ var EventTarget = cr.EventTarget;

  /**
   * Creates a new selection model that is to be used with lists. This only
   * allows a single index to be selected.
   *
   * @param {number=} opt_length The number items in the selection.
   *
   * @constructor
   * @extends {!cr.EventTarget}
   */
  function ListSingleSelectionModel(opt_length) {
    this.length_ = opt_length || 0;
    this.selectedIndex = -1;

    // True if any item could be lead or anchor. False if only selected ones.
    this.independentLeadItem_ = !cr.isMac && !cr.isChromeOS;
  }

  ListSingleSelectionModel.prototype = {
    __proto__: EventTarget.prototype,

    /**
     * The number of items in the model.
     * @type {number}
     */
    get length() {
      return this.length_;
    },

    /**
     * @type {!Array} The selected indexes.
     */
    get selectedIndexes() {
      var i = this.selectedIndex;
      return i != -1 ? [this.selectedIndex] : [];
    },
    set selectedIndexes(indexes) {
      this.selectedIndex = indexes.length ? indexes[0] : -1;
    },

    /**
     * Convenience getter which returns the first selected index.
     * Setter also changes lead and anchor indexes if value is nonegative.
     * @type {number}
     */
    get selectedIndex() {
      return this.selectedIndex_;
    },
    set selectedIndex(selectedIndex) {
      var oldSelectedIndex = this.selectedIndex;
      var i = Math.max(-1, Math.min(this.length_ - 1, selectedIndex));

      if (i != oldSelectedIndex) {
        this.beginChange();
        this.selectedIndex_ = i;
        this.leadIndex = i >= 0 ? i : this.leadIndex;
        this.endChange();
      }
    },

    /**
     * Selects a range of indexes, starting with {@code start} and ends with
     * {@code end}.
     * @param {number} start The first index to select.
     * @param {number} end The last index to select.
     */
    selectRange: function(start, end) {
      // Only select first index.
      this.selectedIndex = Math.min(start, end);
    },

    /**
     * Selects all indexes.
     */
    selectAll: function() {
      // Select all is not allowed on a single selection model
    },

    /**
     * Clears the selection
     */
    clear: function() {
      this.beginChange();
      this.length_ = 0;
      this.selectedIndex = this.anchorIndex = this.leadIndex = -1;
      this.endChange();
    },

    /**
     * Unselects all selected items.
     */
    unselectAll: function() {
      this.selectedIndex = -1;
    },

    /**
     * Sets the selected state for an index.
     * @param {number} index The index to set the selected state for.
     * @param {boolean} b Whether to select the index or not.
     */
    setIndexSelected: function(index, b) {
      // Only allow selection
      var oldSelected = index == this.selectedIndex_;
      if (oldSelected == b)
        return;

      if (b)
        this.selectedIndex = index;
      else if (index == this.selectedIndex_)
        this.selectedIndex = -1;
    },

    /**
     * Whether a given index is selected or not.
     * @param {number} index The index to check.
     * @return {boolean} Whether an index is selected.
     */
    getIndexSelected: function(index) {
      return index == this.selectedIndex_;
    },

    /**
     * This is used to begin batching changes. Call {@code endChange} when you
     * are done making changes.
     */
    beginChange: function() {
      if (!this.changeCount_) {
        this.changeCount_ = 0;
        this.selectedIndexBefore_ = this.selectedIndex_;
      }
      this.changeCount_++;
    },

    /**
     * Call this after changes are done and it will dispatch a change event if
     * any changes were actually done.
     */
    endChange: function() {
      this.changeCount_--;
      if (!this.changeCount_) {
        if (this.selectedIndexBefore_ != this.selectedIndex_) {
          var beforeChange = this.createChangeEvent('beforeChange');
          if (this.dispatchEvent(beforeChange))
            this.dispatchEvent(this.createChangeEvent('change'));
          else
            this.selectedIndex_ = this.selectedIndexBefore_;
        }
      }
    },

    /**
     * Creates event with specified name and fills its {changes} property.
     * @param {string} name Event name.
     */
    createChangeEvent: function(eventName) {
      var e = new Event(eventName);
      var indexes = [this.selectedIndexBefore_, this.selectedIndex_];
      e.changes = indexes.filter(function(index) {
        return index != -1;
      }).map(function(index) {
        return {
          index: index,
          selected: index == this.selectedIndex_
        };
      }, this);

      return e;
    },

    leadIndex_: -1,

    /**
     * The leadIndex is used with multiple selection and it is the index that
     * the user is moving using the arrow keys.
     * @type {number}
     */
    get leadIndex() {
      return this.leadIndex_;
    },
    set leadIndex(leadIndex) {
      var li = this.adjustIndex_(leadIndex);
      if (li != this.leadIndex_) {
        var oldLeadIndex = this.leadIndex_;
        this.leadIndex_ = li;
        cr.dispatchPropertyChange(this, 'leadIndex', li, oldLeadIndex);
        cr.dispatchPropertyChange(this, 'anchorIndex', li, oldLeadIndex);
      }
    },

    adjustIndex_: function(index) {
      index = Math.max(-1, Math.min(this.length_ - 1, index));
      if (!this.independentLeadItem_)
        index = this.selectedIndex;
      return index;
    },

    /**
     * The anchorIndex is used with multiple selection.
     * @type {number}
     */
    get anchorIndex() {
      return this.leadIndex;
    },
    set anchorIndex(anchorIndex) {
      this.leadIndex = anchorIndex;
    },

    /**
     * Whether the selection model supports multiple selected items.
     * @type {boolean}
     */
    get multiple() {
      return false;
    },

    /**
     * Adjusts the selection after reordering of items in the table.
     * @param {!Array.<number>} permutation The reordering permutation.
     */
    adjustToReordering: function(permutation) {
      if (this.leadIndex != -1)
        this.leadIndex = permutation[this.leadIndex];

      var oldSelectedIndex = this.selectedIndex;
      if (oldSelectedIndex != -1) {
        this.selectedIndex = permutation[oldSelectedIndex];
      }
    },

    /**
     * Adjusts selection model length.
     * @param {number} length New selection model length.
     */
    adjustLength: function(length) {
      this.length_ = length;
    }
  };

  return {
    ListSingleSelectionModel: ListSingleSelectionModel
  };
});
