// 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.

cr.define('cr.ui', function() {

  /**
   * Returns the TabBox for a Tab or a TabPanel.
   * @param {Tab|TabPanel} el The tab or tabpanel element.
   * @return {TabBox} The tab box if found.
   */
  function getTabBox(el) {
    return findAncestor(el, function(node) {
      return node.tagName == 'TABBOX';
    });
  }

  /**
   * Returns whether an element is a tab related object.
   * @param {HTMLElement} el The element whose tag is being checked
   * @return {boolean} Whether the element is a tab related element.
   */
  function isTabElement(el) {
    return el.tagName == 'TAB' || el.tagName == 'TABPANEL';
  }

  /**
   * Set hook for the selected property for Tab and TabPanel.
   * This sets the selectedIndex on the parent TabBox.
   * @param {boolean} newValue The new selected value
   * @param {boolean} oldValue The old selected value. (This is ignored atm.)
   * @this {Tab|TabPanel}
   */
  function selectedSetHook(newValue, oldValue) {
    var tabBox;
    if (newValue && (tabBox = getTabBox(this)))
      tabBox.selectedIndex = Array.prototype.indexOf.call(p.children, this);
  }

  /**
   * Decorates all the children of an element.
   * @this {HTMLElement}
   */
  function decorateChildren() {
    var map = {
      TABBOX: TabBox,
      TABS: Tabs,
      TAB: Tab,
      TABPANELS: TabPanels,
      TABPANEL: TabPanel
    };

    Object.keys(map).forEach(function(tagName) {
      var children = this.getElementsByTagName(tagName);
      var constr = map[tagName];
      for (var i = 0; child = children[i]; i++) {
        cr.ui.decorate(child, constr);
      }
    }.bind(this));
  }

  /**
   * Set hook for TabBox selectedIndex.
   * @param {number} selectedIndex The new selected index.
   * @this {TabBox}
   */
  function selectedIndexSetHook(selectedIndex) {
    var child, tabChild, element;
    element = this.querySelector('tabs');
    if (element) {
      for (var i = 0; child = element.children[i]; i++) {
        child.selected = i == selectedIndex;
      }
    }

    element = this.querySelector('tabpanels');
    if (element) {
      for (var i = 0; child = element.children[i]; i++) {
        child.selected = i == selectedIndex;
      }
    }
  }

  /**
   * Creates a new tabbox element.
   * @param {Object=} opt_propertyBag Optional properties.
   * @constructor
   * @extends {HTMLElement}
   */
  var TabBox = cr.ui.define('tabbox');

  TabBox.prototype = {
    __proto__: HTMLElement.prototype,
    decorate: function() {
      decorateChildren.call(this);
      this.addEventListener('selectedChange', this.handleSelectedChange_, true);
      this.selectedIndex = 0;
    },

    /**
     * Callback for when a Tab or TabPanel changes its selected property.
     * @param {Event} e The property change event.
     * @private
     */
    handleSelectedChange_: function(e) {
      var target = e.target;
      if (e.newValue && isTabElement(target) && getTabBox(target) == this) {
        var index = Array.prototype.indexOf.call(target.parentElement.children,
                                                 target);
        this.selectedIndex = index;
      }
    },

    selectedIndex_: -1
  };

  /**
   * The index of the selected tab or -1 if no tab is selected.
   * @type {number}
   */
  cr.defineProperty(TabBox, 'selectedIndex', cr.PropertyKind.JS_PROP,
                    selectedIndexSetHook);

  /**
   * Creates a new tabs element.
   * @param {string} opt_label The text label for the item.
   * @constructor
   * @extends {HTMLElement}
   */
  var Tabs = cr.ui.define('tabs');
  Tabs.prototype = {
    __proto__: HTMLElement.prototype,
    decorate: function() {
      decorateChildren.call(this);

      // Make the Tabs element focusable.
      this.tabIndex = 0;
      this.addEventListener('keydown', this.handleKeyDown_.bind(this));

      // Get (and initializes a focus outline manager.
      this.focusOutlineManager_ =
          cr.ui.FocusOutlineManager.forDocument(this.ownerDocument);
    },

    /**
     * Handle keydown to change the selected tab when the user presses the
     * arrow keys.
     * @param {Event} e The keyboard event.
     * @private
     */
    handleKeyDown_: function(e) {
      var delta = 0;
      switch (e.keyIdentifier) {
        case 'Left':
        case 'Up':
          delta = -1;
          break;
        case 'Right':
        case 'Down':
          delta = 1;
          break;
      }

      if (!delta)
        return;

      var cs = this.ownerDocument.defaultView.getComputedStyle(this);
      if (cs.direction == 'rtl')
        delta *= -1;

      var count = this.children.length;
      var tabbox = getTabBox(this);
      var index = tabbox.selectedIndex;
      tabbox.selectedIndex = (index + delta + count) % count;

      // Show focus outline since we used the keyboard.
      this.focusOutlineManager_.visible = true;
    }
  };

  /**
   * Creates a new tab element.
   * @param {string} opt_label The text label for the item.
   * @constructor
   * @extends {HTMLElement}
   */
  var Tab = cr.ui.define('tab');
  Tab.prototype = {
    __proto__: HTMLElement.prototype,
    decorate: function() {
      var self = this;
      this.addEventListener(cr.isMac ? 'click' : 'mousedown', function() {
        self.selected = true;
      });
    }
  };

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

  /**
   * Creates a new tabpanels element.
   * @param {string} opt_label The text label for the item.
   * @constructor
   * @extends {HTMLElement}
   */
  var TabPanels = cr.ui.define('tabpanels');
  TabPanels.prototype = {
    __proto__: HTMLElement.prototype,
    decorate: decorateChildren
  };

  /**
   * Creates a new tabpanel element.
   * @param {string} opt_label The text label for the item.
   * @constructor
   * @extends {HTMLElement}
   */
  var TabPanel = cr.ui.define('tabpanel');
  TabPanel.prototype = {
    __proto__: HTMLElement.prototype,
    decorate: function() {}
  };

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

  return {
    TabBox: TabBox,
    Tabs: Tabs,
    Tab: Tab,
    TabPanels: TabPanels,
    TabPanel: TabPanel
  };
});
