/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.user.client.ui;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.Command;

import java.util.List;
import java.util.Locale;

/**
 * Tests the DockPanel widget.
 */
public class MenuBarTest extends WidgetTestBase {

  private static final String html = "<b>hello</b><i>world</i>";

  /**
   * A blank command.
   */
  private static final Command BLANK_COMMAND = new Command() {
    @Override
    public void execute() {
    }
  };

  /**
   * A blank scheduled command.
   */
  private static final ScheduledCommand BLANK_SCHEDULED_COMMAND = new ScheduledCommand() {
    @Override
    public void execute() {
    }
  };

  @Override
  public String getModuleName() {
    return "com.google.gwt.user.DebugTest";
  }

  /**
   * Test adding and removing {@link MenuItem}s and {@link MenuItemSeparator}s
   * from a menu.
   */
  public void testAddRemoveItemsWithCommand() {
    // Create a menu bar
    MenuBar bar = new MenuBar(true);

    // Add an item, default to text
    MenuItem item0 = bar.addItem("<b>test</b>", BLANK_COMMAND);
    assertEquals("<b>test</b>", item0.getText());
    assertEquals(BLANK_COMMAND, item0.getCommand());
    assertEquals(bar, item0.getParentMenu());

    // Add a separator
    MenuItemSeparator separator0 = bar.addSeparator();
    assertEquals(bar, separator0.getParentMenu());

    // Add another item, force to html
    MenuItem item1 = bar.addItem("<b>test1</b>", true, BLANK_COMMAND);
    assertEquals("test1", item1.getText());
    assertEquals(BLANK_COMMAND, item1.getCommand());
    assertEquals(bar, item1.getParentMenu());

    // Get all items
    List<MenuItem> items = bar.getItems();
    assertEquals(item0, items.get(0));
    assertEquals(item1, items.get(1));

    // Remove an item
    bar.removeItem(item0);
    assertEquals(item1, items.get(0));
    assertNull(item0.getParentMenu());

    // Remove the separator
    bar.removeSeparator(separator0);
    assertEquals(item1, items.get(0));
    assertNull(separator0.getParentMenu());

    // Add a bunch of items and clear them all
    MenuItem item2 = bar.addItem("test2", true, BLANK_COMMAND);
    MenuItemSeparator separator1 = bar.addSeparator();
    MenuItem item3 = bar.addItem("test3", true, BLANK_COMMAND);
    MenuItemSeparator separator2 = bar.addSeparator();
    MenuItem item4 = bar.addItem("test4", true, BLANK_COMMAND);
    MenuItemSeparator separator3 = bar.addSeparator();
    bar.clearItems();
    assertEquals(0, bar.getItems().size());
    assertNull(item2.getParentMenu());
    assertNull(item3.getParentMenu());
    assertNull(item4.getParentMenu());
    assertNull(separator1.getParentMenu());
    assertNull(separator2.getParentMenu());
    assertNull(separator3.getParentMenu());
  }

  /**
   * Test adding and removing {@link MenuItem}s and {@link MenuItemSeparator}s
   * from a menu.
   */
  public void testAddRemoveItemsWithScheduledCommand() {
    // Create a menu bar
    MenuBar bar = new MenuBar(true);

    // Add an item, default to text
    MenuItem item0 = bar.addItem("<b>test</b>", BLANK_SCHEDULED_COMMAND);
    assertEquals("<b>test</b>", item0.getText());
    assertEquals(BLANK_SCHEDULED_COMMAND, item0.getScheduledCommand());
    assertEquals(bar, item0.getParentMenu());

    // Add a separator
    MenuItemSeparator separator0 = bar.addSeparator();
    assertEquals(bar, separator0.getParentMenu());

    // Add another item, force to html
    MenuItem item1 = bar.addItem("<b>test1</b>", true, BLANK_SCHEDULED_COMMAND);
    assertEquals("test1", item1.getText());
    assertEquals(BLANK_SCHEDULED_COMMAND, item1.getScheduledCommand());
    assertEquals(bar, item1.getParentMenu());

    // Get all items
    List<MenuItem> items = bar.getItems();
    assertEquals(item0, items.get(0));
    assertEquals(item1, items.get(1));

    // Remove an item
    bar.removeItem(item0);
    assertEquals(item1, items.get(0));
    assertNull(item0.getParentMenu());

    // Remove the separator
    bar.removeSeparator(separator0);
    assertEquals(item1, items.get(0));
    assertNull(separator0.getParentMenu());

    // Add a bunch of items and clear them all
    MenuItem item2 = bar.addItem("test2", true, BLANK_SCHEDULED_COMMAND);
    MenuItemSeparator separator1 = bar.addSeparator();
    MenuItem item3 = bar.addItem("test3", true, BLANK_SCHEDULED_COMMAND);
    MenuItemSeparator separator2 = bar.addSeparator();
    MenuItem item4 = bar.addItem("test4", true, BLANK_SCHEDULED_COMMAND);
    MenuItemSeparator separator3 = bar.addSeparator();
    bar.clearItems();
    assertEquals(0, bar.getItems().size());
    assertNull(item2.getParentMenu());
    assertNull(item3.getParentMenu());
    assertNull(item4.getParentMenu());
    assertNull(separator1.getParentMenu());
    assertNull(separator2.getParentMenu());
    assertNull(separator3.getParentMenu());
  }

  public void testAutoHideChildMenuPopup() {
    // Create a menu bar with children.
    MenuBar l0 = new MenuBar();
    l0.setAutoOpen(true);
    MenuBar l1 = new MenuBar();
    l1.setAutoOpen(true);
    MenuBar l2 = new MenuBar();
    l2.setAutoOpen(true);
    MenuItem item2 = l2.addItem("l2", BLANK_COMMAND);
    MenuItem item1 = l1.addItem("l1", l2);
    MenuItem item0 = l0.addItem("l0", l1);
    RootPanel.get().add(l0);

    // Open l2.
    l0.itemOver(item0, true);
    l1.itemOver(item1, true);
    l2.itemOver(item2, true);
    assertTrue(l0.getPopup().isShowing());
    assertEquals(item0, l0.getSelectedItem());
    assertTrue(l1.getPopup().isShowing());
    assertEquals(item1, l1.getSelectedItem());

    // Auto-hide the child popup.
    l1.getPopup().hide(true);
    assertNull(l0.getPopup());
    assertNull(l0.getSelectedItem());
    assertNull(l1.getPopup());

    // Open l2.
    l0.itemOver(item0, true);
    l1.itemOver(item1, true);
    l2.itemOver(item2, true);
    assertTrue(l0.getPopup().isShowing());
    assertEquals(item0, l0.getSelectedItem());
    assertTrue(l1.getPopup().isShowing());
    assertEquals(item1, l1.getSelectedItem());

    // Close child menus below l1.
    l1.closeAllChildren(true);
    assertTrue(l0.getPopup().isShowing());
    assertEquals(item0, l0.getSelectedItem());
    assertNull(l1.getPopup());
    assertNull(l2.getPopup());
  }

  public void testBlur() {
    // Create a menu bar with children.
    final MenuBar menu = new MenuBar();
    MenuItem item0 = menu.addItem("item0", BLANK_COMMAND);
    RootPanel.get().add(menu);

    // Select the item.
    menu.focus();
    menu.selectItem(item0);
    assertEquals(item0, menu.getSelectedItem());

    // Blur the menu bar.
    NativeEvent event = Document.get().createBlurEvent();
    menu.getElement().dispatchEvent(event);
    assertNull(menu.getSelectedItem());
  }

  public void testSetFocusOnHoverEnabled() {
    delayTestFinish(1000);
    TextBox focusOwner = new TextBox();
    RootPanel.get().add(focusOwner);
    focusOwner.setFocus(true);
    assertFocused(focusOwner.getElement());

    final MenuBar menu = new MenuBar();
    MenuItem item0 = menu.addItem("item0", BLANK_COMMAND);
    RootPanel.get().add(menu);

    assertFocused(focusOwner.getElement());
    menu.itemOver(item0, true);
    Scheduler.get().scheduleDeferred(new Command() {
      @Override
      public void execute() {
        assertFocused(menu.getElement());
        finishTest();
      }
    });
  }

  public void testSetFocusOnHoverDisabled() {
    TextBox focusOwner = new TextBox();
    RootPanel.get().add(focusOwner);
    focusOwner.setFocus(true);
    assertFocused(focusOwner.getElement());

    // Create a menu bar with children.
    MenuBar menu = new MenuBar();
    menu.setAutoOpen(true);
    menu.setFocusOnHoverEnabled(false);
    MenuItem item0 = menu.addItem("item0", BLANK_COMMAND);
    MenuBar subMenu = new MenuBar();
    subMenu.setFocusOnHoverEnabled(false);
    MenuItem item1 = menu.addItem("item1", subMenu);
    RootPanel.get().add(menu);

    menu.itemOver(item0, true);
    assertFocused(focusOwner.getElement());

    menu.itemOver(item1, true);
    assertFocused(focusOwner.getElement());

    // Cause item1 to hide
    menu.itemOver(item0, true);
    assertFocused(focusOwner.getElement());
  }

  private static void assertFocused(Element element) {
    assertTrue(isFocused(element));
  }

  private static native boolean isFocused(Element element) /*-{
    return $doc.activeElement == element;
  }-*/;

  public void testPopupAnimationEnabled() {
    // Create a menu bar with children.
    MenuBar l0 = new MenuBar();
    l0.setAutoOpen(true);
    MenuBar l1 = new MenuBar();
    l1.setAutoOpen(true);
    l1.setAnimationEnabled(true);
    MenuBar l2 = new MenuBar();
    l2.setAutoOpen(true);
    MenuItem item2 = l2.addItem("l2", BLANK_COMMAND);
    MenuItem item1 = l1.addItem("l1", l2);
    MenuItem item0 = l0.addItem("l0", l1);
    RootPanel.get().add(l0);

    l0.itemOver(item0, true);
    assertFalse(l0.getPopup().isAnimationEnabled());

    l1.itemOver(item1, true);
    assertTrue(l1.getPopup().isAnimationEnabled());
  }

  public void testDebugId() {
    // Create a sub menu
    MenuBar subMenu = new MenuBar(true);
    subMenu.addItem("sub0", BLANK_COMMAND);
    subMenu.addItem("sub1", BLANK_COMMAND);
    subMenu.addItem("sub2", BLANK_COMMAND);

    // Create a menu bar
    MenuBar bar = new MenuBar(false);
    bar.setAnimationEnabled(false);
    bar.setAutoOpen(true);
    bar.addItem("top0", BLANK_COMMAND);
    bar.addItem("top1", BLANK_COMMAND);
    MenuItem top2 = bar.addItem("top2", subMenu);
    RootPanel.get().add(bar);

    // Open the item with a submenu
    bar.itemOver(top2, true);

    // Set the Debug Id
    bar.ensureDebugId("myMenu");
    UIObjectTest.assertDebugId("myMenu", bar.getElement());

    delayTestFinish(5000);
    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
      @Override
      public void execute() {
        UIObjectTest.assertDebugIdContents("myMenu-item0", "top0");
        UIObjectTest.assertDebugIdContents("myMenu-item1", "top1");
        UIObjectTest.assertDebugIdContents("myMenu-item2", "top2");

        UIObjectTest.assertDebugIdContents("myMenu-item2-item0", "sub0");
        UIObjectTest.assertDebugIdContents("myMenu-item2-item1", "sub1");
        UIObjectTest.assertDebugIdContents("myMenu-item2-item2", "sub2");
        finishTest();
      }
    });
  }

  public void testDisabledItem() {
    MenuBar bar = new MenuBar(true);
    MenuItem item1 = new MenuItem("item1", BLANK_COMMAND);
    MenuItem item2 = new MenuItem("item2", BLANK_COMMAND);
    MenuItem item3 = new MenuItem("item3", BLANK_COMMAND);
    bar.addItem(item1);
    bar.addItem(item2);
    bar.addItem(item3);
    RootPanel.get().add(bar);

    item2.setEnabled(false);

    bar.moveSelectionDown();
    assertEquals(item1, bar.getSelectedItem());
    bar.moveSelectionDown();
    assertEquals(item3, bar.getSelectedItem());
    bar.moveSelectionUp();
    assertEquals(item1, bar.getSelectedItem());
  }

  public void testEscapeKey() {
    // Create a menu bar with children.
    MenuBar l0 = new MenuBar();
    l0.setAutoOpen(true);
    MenuBar l1 = new MenuBar();
    l1.setAutoOpen(true);
    MenuBar l2 = new MenuBar();
    l2.setAutoOpen(true);
    MenuItem item2 = l2.addItem("l2", BLANK_COMMAND);
    MenuItem item1 = l1.addItem("l1", l2);
    MenuItem item0 = l0.addItem("l0", l1);
    RootPanel.get().add(l0);

    // Open l2.
    l0.itemOver(item0, true);
    l1.itemOver(item1, true);
    l2.itemOver(item2, true);
    assertTrue(l0.getPopup().isShowing());
    assertEquals(item0, l0.getSelectedItem());
    assertTrue(l1.getPopup().isShowing());
    assertEquals(item1, l1.getSelectedItem());

    // Escape from the menu.
    NativeEvent event = Document.get().createKeyDownEvent(
        false, false, false, false, KeyCodes.KEY_ESCAPE);
    l1.getElement().dispatchEvent(event);
    assertNull(l0.getPopup());
    assertNull(l0.getSelectedItem());
    assertNull(l1.getPopup());
  }

  /**
   * Test inserting {@link MenuItem}s and {@link MenuItemSeparator}s into the
   * menu.
   */
  public void testInsertItems() {
    // Create a menu bar
    MenuBar bar = new MenuBar(true);

    // Insert first item
    MenuItem item0 = bar.insertItem(new MenuItem("test", BLANK_COMMAND), 0);
    assertEquals(0, bar.getItemIndex(item0));

    // Insert item at 0
    MenuItem item1 = bar.insertItem(new MenuItem("test", BLANK_COMMAND), 0);
    assertEquals(0, bar.getItemIndex(item1));
    assertEquals(1, bar.getItemIndex(item0));

    // Insert item at end
    MenuItem item2 = bar.insertItem(new MenuItem("test", BLANK_COMMAND), 2);
    assertEquals(0, bar.getItemIndex(item1));
    assertEquals(1, bar.getItemIndex(item0));
    assertEquals(2, bar.getItemIndex(item2));

    // Insert a separator at 0
    MenuItemSeparator separator0 = bar.insertSeparator(0);
    assertEquals(0, bar.getSeparatorIndex(separator0));
    assertEquals(1, bar.getItemIndex(item1));
    assertEquals(2, bar.getItemIndex(item0));
    assertEquals(3, bar.getItemIndex(item2));

    // Insert a separator at end
    MenuItemSeparator separator1 = bar.insertSeparator(4);
    assertEquals(0, bar.getSeparatorIndex(separator0));
    assertEquals(1, bar.getItemIndex(item1));
    assertEquals(2, bar.getItemIndex(item0));
    assertEquals(3, bar.getItemIndex(item2));
    assertEquals(4, bar.getSeparatorIndex(separator1));

    // Insert a separator at middle
    MenuItemSeparator separator2 = bar.insertSeparator(2);
    assertEquals(0, bar.getSeparatorIndex(separator0));
    assertEquals(1, bar.getItemIndex(item1));
    assertEquals(2, bar.getSeparatorIndex(separator2));
    assertEquals(3, bar.getItemIndex(item0));
    assertEquals(4, bar.getItemIndex(item2));
    assertEquals(5, bar.getSeparatorIndex(separator1));
  }

  /**
   * Test inserting {@link MenuItem}s and {@link MenuItemSeparator}s into the
   * menu at indexes that are out of bounds.
   */
  public void testInsertItemsOutOfBounds() {
    // Create a menu bar
    MenuBar bar = new MenuBar(true);

    // Add some items to the menu
    for (int i = 0; i < 3; i++) {
      bar.addItem("item", BLANK_COMMAND);
    }

    // Add an item at a negative index
    try {
      bar.insertItem(new MenuItem("test", BLANK_COMMAND), -1);
      fail("Expected IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
      // Expected exception
    }

    // Add an item at a high index
    try {
      bar.insertItem(new MenuItem("test", BLANK_COMMAND), 4);
      fail("Expected IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
      // Expected exception
    }
  }

  public void testSafeHtmlWithCommand() {
    MenuBar bar = new MenuBar(true);

    // ensure safehtml passes through when a command is set.
    MenuItem item1 =
      bar.addItem(SafeHtmlUtils.fromSafeConstant(html), BLANK_COMMAND);
    assertEquals(html, item1.getHTML().toLowerCase(Locale.ROOT));
    assertEquals(BLANK_COMMAND, item1.getCommand());
    assertEquals(bar, item1.getParentMenu());

    // ensure safehtml passes through when a submenu/popup is set.
    MenuBar foo = new MenuBar(true);
    MenuItem item2 = foo.addItem(SafeHtmlUtils.fromSafeConstant(html), bar);
    assertEquals(html, item2.getHTML().toLowerCase(Locale.ROOT));
    assertEquals(bar, item2.getSubMenu());
    assertEquals(foo, item2.getParentMenu());
  }

  public void testSafeHtmlWithScheduledCommand() {
    MenuBar bar = new MenuBar(true);

    // ensure safehtml passes through when a command is set.
    MenuItem item1 =
      bar.addItem(SafeHtmlUtils.fromSafeConstant(html), BLANK_SCHEDULED_COMMAND);
    assertEquals(html, item1.getHTML().toLowerCase(Locale.ROOT));
    assertEquals(BLANK_SCHEDULED_COMMAND, item1.getScheduledCommand());
    assertEquals(bar, item1.getParentMenu());

    // ensure safehtml passes through when a submenu/popup is set.
    MenuBar foo = new MenuBar(true);
    MenuItem item2 = foo.addItem(SafeHtmlUtils.fromSafeConstant(html), bar);
    assertEquals(html, item2.getHTML().toLowerCase(Locale.ROOT));
    assertEquals(bar, item2.getSubMenu());
    assertEquals(foo, item2.getParentMenu());
  }

  /**
   * Test that the selected item points to the correct item.
   */
  public void testSelectedItem() {
    // Create a menu bar
    MenuBar bar = new MenuBar(true);

    // Add some items
    MenuItem item1 = bar.addItem("item1", BLANK_COMMAND);
    MenuItem item2 = bar.addItem("item2", BLANK_COMMAND);
    MenuItem item3 = bar.addItem("item3", BLANK_COMMAND);

    // Test setting the selected item
    assertNull(bar.getSelectedItem());
    bar.selectItem(item1);
    assertEquals(item1, bar.getSelectedItem());

    // Test removing the selected item
    bar.removeItem(item1);
    assertNull(bar.getSelectedItem());

    // Test removing an item that is not selected
    bar.selectItem(item3);
    assertEquals(item3, bar.getSelectedItem());
    bar.removeItem(item2);
    assertEquals(item3, bar.getSelectedItem());

    // Test clearing all items
    bar.clearItems();
    assertNull(bar.getSelectedItem());
  }

  public void testClearSelectionAfterCommand() {
    MenuBar bar = new MenuBar();
    MenuItem item1 = bar.addItem("item1", BLANK_COMMAND);
    RootPanel.get().add(bar);

    bar.itemOver(item1, true);
    assertNotNull(bar.getSelectedItem());

    bar.itemOver(null, false);
    assertNull(bar.getSelectedItem());
  }
  public void testSelectItem() {
    MenuBar bar = new MenuBar(false);
    MenuItem item1 = new MenuItem("item1", BLANK_COMMAND);
    MenuItem item2 = new MenuItem("item2", BLANK_COMMAND);
    MenuItem item3 = new MenuItem("item3", BLANK_COMMAND);
    bar.addItem(item1);
    bar.addItem(item2);
    bar.addItem(item3);
    RootPanel.get().add(bar);

    bar.selectItem(item1);
    assertEquals(item1, bar.getSelectedItem());
    bar.selectItem(item3);
    assertEquals(item3, bar.getSelectedItem());
    bar.selectItem(null);
    assertNull(bar.getSelectedItem());
  }

  public void testTabKey() {
    // Create a menu bar with children.
    MenuBar l0 = new MenuBar();
    l0.setAutoOpen(true);
    MenuBar l1 = new MenuBar();
    l1.setAutoOpen(true);
    MenuBar l2 = new MenuBar();
    l2.setAutoOpen(true);
    MenuItem item2 = l2.addItem("l2", BLANK_COMMAND);
    MenuItem item1 = l1.addItem("l1", l2);
    MenuItem item0 = l0.addItem("l0", l1);
    RootPanel.get().add(l0);

    // Open l2.
    l0.itemOver(item0, true);
    l1.itemOver(item1, true);
    l2.itemOver(item2, true);
    assertTrue(l0.getPopup().isShowing());
    assertEquals(item0, l0.getSelectedItem());
    assertTrue(l1.getPopup().isShowing());
    assertEquals(item1, l1.getSelectedItem());

    // Tab away from the menu.
    NativeEvent event = Document.get().createKeyDownEvent(
        false, false, false, false, KeyCodes.KEY_TAB);
    l1.getElement().dispatchEvent(event);
    assertNull(l0.getPopup());
    assertNull(l0.getSelectedItem());
    assertNull(l1.getPopup());
  }
  
  public void testPresentationRole() {
    MenuBar menu = new MenuBar();
    assertNotNull(menu.getElement());
    assertEquals("presentation",
        Element.as(menu.getElement().getChild(1)).getAttribute("role"));
  }
}
