// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

typedef void SelectHandler(String menuText);

/**
 * This implements a horizontal menu bar with a sliding triangle arrow
 * that points at the currently selected item.
 */
class SliderMenu extends View {
  static final int TRIANGLE_WIDTH = 24;

  // currently selected menu item
  Element selectedItem;

  // This holds the element where a touchstart occured.  (This is set
  // in touchstart, and cleared in touchend.)  If this is null, then a
  // touch operation is not in progress.
  // TODO(mattsh) - move this to a touch mixin
  Element touchItem;

  /**
   * Callback function that we call when the user chooses something from
   * the menu.  This is passed the menu item text.
   */
  SelectHandler onSelect;

  List<String> _menuItems;

  SliderMenu(this._menuItems, this.onSelect) : super() {}

  Element render() {
    // Create a div for each menu item.
    final items = new StringBuffer();
    for (final item in _menuItems) {
      items.add('<div class="sm-item">$item</div>');
    }

    // Create a root node to hold this view.
    return new Element.html('''
        <div class="sm-root">
          <div class="sm-item-box">
            <div class="sm-item-filler"></div>
            $items
            <div class="sm-item-filler"></div>
          </div>
          <div class="sm-slider-box">
            <div class="sm-triangle"></div>
          </div>
        </div>
        ''');
  }

  void enterDocument() {
    // select the first item
    // todo(jacobr): too much actual work is performed in enterDocument.
    // Ideally, enterDocument should do nothing more than redecorate a view
    // and perhaps calculating the correct child sizes for edge cases that
    // cannot be handled by the browser layout engine.
    selectItem(node.queryOne('.sm-item'), false);

    // TODO(mattsh), abstract this somehow into a touch click mixin
    if (Device.supportsTouch) {
      node.on.touchStart.add((event) {
        touchItem = itemOfTouchEvent(event);
        if (touchItem != null) {
          selectItemText(touchItem);
        }
        event.preventDefault();
      });
      node.on.touchEnd.add((event) {
        if (touchItem != null) {
          if (itemOfTouchEvent(event) == touchItem) {
            selectItem(touchItem, true);
          } else {
            // the Touch target is somewhere other where than the touchstart
            // occured, so revert the selected menu text back to where it was
            // before the touchstart,
            selectItemText(selectedItem);
          }
          // touch operation has ended
          touchItem = null;
        }
        event.preventDefault();
      });
    } else {
      node.on.click.add((event) => selectItem(event.target, true));
    }

    window.on.resize.add((Event event) => updateIndicator(false));
  }

  /**
   * Walks the parent chain of the first Touch target to find the first ancestor
   * that has sm-item class.
   */
  Element itemOfTouchEvent(event) {
    Node node = event.changedTouches[0].target;
    return itemOfNode(node);
  }

  Element itemOfNode(Node node) {
    // TODO(jmesserly): workaround for bug 5399957, document.parent == document
    while (node != null && node != document) {
      if (node is Element) {
        Element element = node;
        if (element.classes.contains('sm-item')) {
          return element;
        }
      }
      node = node.parent;
    }
    return null;
  }

  void selectItemText(Element item) {
    // unselect all menu items
    for (final sliderItem in node.query('.sm-item')) {
      sliderItem.classes.remove('sel');
    }

    // select the item the user clicked on
    item.classes.add('sel');
  }

  void selectItem(Element item, bool animate) {
    if (!item.classes.contains('sm-item')) {
      return;
    }

    selectedItem = item;
    selectItemText(item);
    updateIndicator(animate);
    onSelect(item.text);
  }

  void selectNext(bool animate) {
    final result =  node.queryOne('.sm-item.sel').nextElementSibling;
    if (result != null) {
      selectItem(result, animate);
    }
  }

  void selectPrevious(bool animate) {
    final result = node.queryOne('.sm-item.sel').previousElementSibling;
    if (result != null) {
      selectItem(result, animate);
    }
  }

  /**
   * animate - if true, then animate the movement of the triangle slider
   */
  void updateIndicator(bool animate) {
    num x = 0;
    if (selectedItem != null) {
      // calculate where we want to put the triangle
      x = selectedItem.offsetLeft +
          selectedItem.offsetWidth / 2 - TRIANGLE_WIDTH / 2;
    }

    // find the slider filler (the div element to the left of the
    // triangle) set its width the push the triangle to where we want it.
    String duration = animate ? '.3s' : '0s';
    final triangle = node.queryOne('.sm-triangle');
    Css.setTransitionDuration(triangle.style, duration);
    FxUtil.setWebkitTransform(triangle, x, 0);
  }
}
