// Copyright (c) 2011 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.


/**
 * @fileoverview This implements a tab control.
 *
 * An individual tab within a tab control is, unsurprisingly, a Tab.
 * Tabs must be explicitly added/removed from the control.
 *
 * Tab titles are based on the label attribute of each child:
 *
 * <div>
 * <div label='Tab 1'>Hello</div>
 * <div label='Tab 2'>World</div>
 * </div>
 *
 * Results in:
 *
 * ---------------
 * | Tab1 | Tab2 |
 * | ---------------------------------------
 * | Hello World |
 * -----------------------------------------
 *
 */
cr.define('gpu', function() {
  /**
   * Creates a new tab element. A tab element is one of multiple tabs
   * within a TabControl.
   * @constructor
   * @param {Object=} opt_propertyBag Optional properties.
   * @extends {HTMLDivElement}
   */
  var Tab = cr.ui.define('div');
  Tab.prototype = {
    __proto__: HTMLDivElement.prototype,

    decorate: function() {
    }
  };

  /**
   * Title for the tab.
   * @type {String}
   */
  cr.defineProperty(Tab, 'label', cr.PropertyKind.ATTR);

  /**
   * Whether the item is selected.
   * @type {boolean}
   */
  cr.defineProperty(Tab, 'selected', cr.PropertyKind.BOOL_ATTR);


  /**
   * Creates a new tab button element in the tabstrip
   * @constructor
   * @param {Object=} opt_propertyBag Optional properties.
   * @extends {HTMLDivElement}
   */
  var TabButton = cr.ui.define('a');
  TabButton.prototype = {
    __proto__: HTMLAnchorElement.prototype,

    decorate: function() {
      this.classList.add('tab-button');
      this.onclick = function() {
        if (this.tab_)
          this.parentNode.parentNode.selectedTab = this.tab_;
      }.bind(this);
    },
    get tab() {
      return this.tab_;
    },
    set tab(tab) {
      if (this.tab_)
        throw Error('Cannot set tab once set.');
      this.tab_ = tab;
      this.tab_.addEventListener('titleChange', this.onTabChanged_.bind(this));
      this.tab_.addEventListener('selectedChange',
                                 this.onTabChanged_.bind(this));
      this.onTabChanged_();
    },

    onTabChanged_: function(e) {
      if (this.tab_) {
        this.textContent = this.tab_.label;
        this.selected = this.tab_.selected;
      }
    }

  };

  /**
   * Whether the TabButton is selected.
   * @type {boolean}
   */
  cr.defineProperty(TabButton, 'selected', cr.PropertyKind.BOOL_ATTR);


  /**
   * Creates a new tab control element.
   * @param {Object=} opt_propertyBag Optional properties.
   * @constructor
   * @extends {HTMLDivElement}
   */
  var TabControl = cr.ui.define('div');
  TabControl.prototype = {
    __proto__: HTMLDivElement.prototype,

    selectedTab_: null,

    /**
     * Initializes the tab control element.
     * Any child elements pre-existing on the element will become tabs.
     */
    decorate: function() {
      this.classList.add('tab-control');

      this.tabStrip_ = this.ownerDocument.createElement('div');
      this.tabStrip_.classList.add('tab-strip');

      this.tabs_ = this.ownerDocument.createElement('div');
      this.tabs_.classList.add('tabs');

      this.insertBefore(this.tabs_, this.firstChild);
      this.insertBefore(this.tabStrip_, this.firstChild);

      this.boundOnTabSelectedChange_ = this.onTabSelectedChange_.bind(this);

      // Reparent existing tabs to the tabs_ div.
      while (this.children.length > 2)
        this.addTab(this.children[2]);
    },

    /**
     * Adds an element to the tab control.
     */
    addTab: function(tab) {
      if (tab.parentNode == this.tabs_)
        throw Error('Tab is already part of this control.');
      if (!(tab instanceof Tab))
        throw Error('Provided element is not instanceof Tab.');
      this.tabs_.appendChild(tab);

      tab.addEventListener('selectedChange', this.boundOnTabSelectedChange_);

      var button = new TabButton();
      button.tab = tab;
      tab.tabStripButton_ = button;

      this.tabStrip_.appendChild(button);

      if (this.tabs_.length == 1)
        this.tabs_.children[0].selected = true;
    },

    /**
     * Removes a tab from the tab control.
     * changing the selected tab if needed.
     */
    removeTab: function(tab) {
      if (tab.parentNode != this.tabs_)
        throw new Error('Tab is not attached to this control.');

      tab.removeEventListener('selectedChange', this.boundOnTabSelectedChange_);

      if (this.selectedTab_ == tab) {
        if (this.tabs_.children.length) {
          this.tabs_.children[0].selected = true;
        } else {
          this.selectedTab_ = undefined;
        }
      }

      this.tabs_.removeChild(tab);
      tab.tabStripButton_.parentNode.removeChild(
          tab.tabStripButton_);
    },

    /**
     * Gets the currently selected tab element.
     */
    get selectedTab() {
      return this.selectedTab_;
    },

    /**
     * Sets the currently selected tab element.
     */
    set selectedTab(tab) {
      if (tab.parentNode != this.tabs_)
        throw Error('Tab is not part of this TabControl.');
      tab.selected = true;
    },

    /**
     * Hides the previously selected tab element and dispatches a
     * 'selectedTabChanged' event.
     */
    onTabSelectedChange_: function(e) {
      var tab = e.target;
      if (!e.newValue) {
        // Usually we can ignore this event, as the tab becoming unselected
        // needs no corrective action. However, if the currently selected
        // tab is deselected, we do need to do some work.
        if (tab == this.selectedTab_) {
          var previousTab = this.selectedTab_;
          var newTab;
          for (var i = 0; i < this.tabs_.children.length; ++i) {
            if (this.tabs_.children[i] != tab) {
              newTab = this.tabs_.children[i];
              break;
            }
          }
          if (newTab) {
            newTab.selected = true;
          } else {
            this.selectedTab_ = undefined;
            cr.dispatchPropertyChange(
                this, 'selectedTab', this.selectedTab_, previousTab);
          }
        }
      } else {
        var previousTab = this.selectedTab_;
        this.selectedTab_ = tab;
        if (previousTab)
          previousTab.selected = false;
        cr.dispatchPropertyChange(
            this, 'selectedTab', this.selectedTab_, previousTab);
      }
    },

    /**
     * Returns an array of all the tabs within this control.  This is
     * not the same as this.children because the actual tab elements are
     * attached to the tabs_ element.
     */
    get tabs() {
      return Array.prototype.slice.call(this.tabs_.children);
    }
  };

  return {
    Tab: Tab,
    TabControl: TabControl
  };
});