/*
 * $Id: MenuDisplayer.java 5827 2009-07-08 19:18:25Z uckelman $
 *
 * Copyright (c) 2000-2003 by Rodney Kinney
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */
package VASSAL.build.module.map;

import java.awt.Font;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import javax.swing.Action;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import VASSAL.build.Buildable;
import VASSAL.build.module.Map;
import VASSAL.counters.Deck;
import VASSAL.counters.EventFilter;
import VASSAL.counters.GamePiece;
import VASSAL.counters.KeyCommand;
import VASSAL.counters.KeyCommandSubMenu;
import VASSAL.counters.PieceFinder;
import VASSAL.counters.Properties;

public class MenuDisplayer extends MouseAdapter implements Buildable {
  public static Font POPUP_MENU_FONT = new Font("Dialog", 0, 10);

  protected Map map;
  protected PieceFinder targetSelector;

  public void addTo(Buildable b) {
    targetSelector = createTargetSelector();
    map = (Map) b;
    map.addLocalMouseListener(this);
  }

  /**
   * Return a {@link PieceFinder} instance that will select a
   * {@link GamePiece} whose menu will be displayed when the
   * user clicks on the map
   * @return
   */
  protected PieceFinder createTargetSelector() {
    return new PieceFinder.PieceInStack() {
      public Object visitDeck(Deck d) {
        Point pos = d.getPosition();
        Point p = new Point(pt.x - pos.x, pt.y - pos.y);
        if (d.getShape().contains(p)) {
          return d;
        }
        else {
          return null;
        }
      }
    };
  }

  public void add(Buildable b) {
  }

  public Element getBuildElement(Document doc) {
    return doc.createElement(getClass().getName());
  }

  public void build(Element e) {
  }

  public static JPopupMenu createPopup(GamePiece target) {
    return createPopup(target, false);
  }

  /**
   *
   * @param target
   * @param global If true, then apply the KeyCommands globally,
   * i.e. to all selected pieces
   * @return
   */
  public static JPopupMenu createPopup(GamePiece target, boolean global) {
    JPopupMenu popup = new JPopupMenu();
    KeyCommand c[] = (KeyCommand[]) target.getProperty(Properties.KEY_COMMANDS);
    if (c != null) {
      ArrayList<JMenuItem> commands = new ArrayList<JMenuItem>();
      ArrayList<KeyStroke> strokes = new ArrayList<KeyStroke>();

      // Maps instances of KeyCommandSubMenu to corresponding JMenu
      HashMap<KeyCommandSubMenu,JMenu> subMenus =
        new HashMap<KeyCommandSubMenu,JMenu>();
      // Maps name to a list of commands with that name
      HashMap<String,ArrayList<JMenuItem>> commandNames =
        new HashMap<String,ArrayList<JMenuItem>>();

      for (int i = 0; i < c.length; ++i) {
        c[i].setGlobal(global);
        KeyStroke stroke = c[i].getKeyStroke();
        JMenuItem item = null;
        if (c[i] instanceof KeyCommandSubMenu) {
          JMenu subMenu = new JMenu(c[i].getLocalizedMenuText());
          subMenu.setFont(POPUP_MENU_FONT);
          subMenus.put((KeyCommandSubMenu) c[i], subMenu);
          item = subMenu;
          commands.add(item);
          strokes.add(KeyStroke.getKeyStroke('\0'));
        }
        else {
          if (strokes.contains(stroke)) {
            JMenuItem command = commands.get(strokes.indexOf(stroke));
            Action action = command.getAction();
            if (action != null) {
              String commandName = 
                (String) command.getAction().getValue(Action.NAME);
              if (commandName == null ||
                  commandName.length() < c[i].getName().length()) {
                item = new JMenuItem(c[i].getLocalizedMenuText());
                item.addActionListener(c[i]);
                item.setFont(POPUP_MENU_FONT);
                item.setEnabled(c[i].isEnabled());
                commands.set(strokes.indexOf(stroke), item);
              }
            }
          }
          else {
            strokes.add(stroke != null ? stroke : KeyStroke.getKeyStroke('\0'));
            item = new JMenuItem(c[i].getLocalizedMenuText());
            item.addActionListener(c[i]);
            item.setFont(POPUP_MENU_FONT);
            item.setEnabled(c[i].isEnabled());
            commands.add(item);
          }
        }
        if (c[i].getName() != null &&
            c[i].getName().length() > 0 &&
            item != null) {
          ArrayList<JMenuItem> l = commandNames.get(c[i].getName());
          if (l == null) {
            l = new ArrayList<JMenuItem>();
            commandNames.put(c[i].getName(), l);
          }
          l.add(item);
        }
      }

      // Move commands from main menu into submenus
      for (KeyCommandSubMenu menuCommand : subMenus.keySet()) {      
        JMenu subMenu = subMenus.get(menuCommand);
        for (Iterator<String> it2 = menuCommand.getCommands(); it2.hasNext();) {
          final ArrayList<JMenuItem> matchingCommands =
            commandNames.get(it2.next());
          if (matchingCommands != null) {
            for (JMenuItem item : matchingCommands) {
              subMenu.add(item);
              commands.remove(item);
            }
          }
        }
      }
      
      // Promote contents of a single submenu
      if (commands.size() == 1) {
        if (commands.get(0) instanceof JMenu) {
          JMenu submenu = (JMenu) commands.get(0);
          commands.remove(submenu);
          for (int i = 0; i < submenu.getItemCount(); i++) {
            commands.add(submenu.getItem(i));
          }
        }
      }

      for (JMenuItem item : commands) {      
        popup.add(item);
      }
    }
    return popup;
  }

  public void mouseReleased(MouseEvent e) {
    if (e.isMetaDown()) {
      final GamePiece p = map.findPiece(e.getPoint(), targetSelector);
      if (p != null) {
        EventFilter filter = (EventFilter) p.getProperty(Properties.SELECT_EVENT_FILTER);
        if (filter == null
            || !filter.rejectEvent(e)) {
          JPopupMenu popup = createPopup(p, true);
          Point pt = map.componentCoordinates(e.getPoint());
          popup.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
            public void popupMenuCanceled
                (javax.swing.event.PopupMenuEvent evt) {
              map.repaint();
            }

            public void popupMenuWillBecomeInvisible
                (javax.swing.event.PopupMenuEvent evt) {
              map.repaint();
            }

            public void popupMenuWillBecomeVisible
                (javax.swing.event.PopupMenuEvent evt) {
            }
          });
          // It is possible for the map to close before the menu is displayed
          if (map.getView().isShowing()) {
            popup.show(map.getView(), pt.x, pt.y);
          }
          e.consume();
        }
      }
    }
  }
}
