/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: EMenuBar.java
 *
 * Copyright (c) 2003, Static Free Software. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.user.menus;

import com.sun.electric.tool.user.ActivityLogger;
import com.sun.electric.tool.user.KeyBindingManager;
import com.sun.electric.tool.user.KeyBindingManager.KeyMaps;
import com.sun.electric.tool.user.ui.KeyBindings;
import com.sun.electric.tool.user.ui.KeyStrokePair;
import com.sun.electric.tool.user.ui.WindowFrame;

import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.prefs.Preferences;

import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;

/**
 * EMenuBar is a menu bar template. It associates several menu bars together.
 * This maintains consistency of state and key bindings.
 * It also acts as an listener for updating all menu items on a state change.
 */
public class EMenuBar extends EMenu {

    /** Preferences for User Bindings */                            private Preferences prefs;
    /** All menu items created, stores as ArrayLists in HashMap */  Map<String,EMenuItem> menuItems = new HashMap<String,EMenuItem>();
    /** Key Binding Manager for menu items */                       public final KeyBindingManager keyBindingManager;
    /** Hidden menu */                                              private final EMenu hiddenMenu;
    /** Updatable items. */                                         private final List<EMenuItem> updatableItems = new ArrayList<EMenuItem>();  
    
    /**
	 *@param name name of generic menu bar.
     *@param hiddenMenu menu which items are invoked by shortcuts only.
     *@param items var-arg menu items. Null arguments are skipped.
     */
    EMenuBar(String name, EMenu hiddenMenu, List<EMenuItem> items) {
        super(name, items);
        this.hiddenMenu = hiddenMenu;
        prefs = Preferences.userNodeForPackage(EMenuBar.class);
        keyBindingManager = new KeyBindingManager(name+"MenuKeyBinding-", prefs);
        // add to hashmap of existing groups
        this.path = new int[0];
        int indexInParent = 0;
        for (EMenuItem item: items)
        {
            if (item != null)
                item.registerTree(this, path, indexInParent++);
        }
        if (hiddenMenu != null)
            hiddenMenu.registerTree(this, path, -1);
    }

    /**
     * Returns hidden menu whcih items are invoked by shortcuts only.
     */
    public EMenu getHiddenMenu() {
        return hiddenMenu;
    }
    
    /** Get a string description of the menu item.
     * Takes the form <p>
     * Menu | SubMenu | SubMenu | item
     * <p>
     * @param path a path to EMenuItem
     * @return a string of the description.
     */
    public String getDescription(int[] path) {
        StringBuilder sb = new StringBuilder();
        int topIndex = path[0];
        EMenuItem item = topIndex >= 0 ? items.get(topIndex) : hiddenMenu;
        sb.append(item.getText());
        for (int i = 1; i < path.length; i++) {
            EMenu menu = (EMenu)item;
            item = menu.items.get(path[i]);
            sb.append(" | ");
            sb.append(item.getText());
        }
        return sb.toString();
    }
    
    /**
     * Register key binding of menu item.
     */
    void registerKeyBindings(EMenuItem menuItem) {
        String key = menuItem.getDescription();
        menuItems.put(key, menuItem);
        
        // add default binding
        KeyStroke [] bindings = menuItem.getAccelerators();
        for (int i=0; i<bindings.length; i++)
            addDefaultKeyBinding(menuItem, bindings[i]);
        
        // The generic menu item is an action listener for instance menu items.
        keyBindingManager.addActionListener(key, menuItem);
    } 
    
    /**
     * Register updatable menu item.
     */
    void registerUpdatable(EMenuItem menuItem) {
        updatableItems.add(menuItem);
    }
        
    /**
     * Update updatable buttons of this menu.
     */
    public void updateAllButtons() {
        for (EMenuItem item: updatableItems)
            item.updateJMenuItems();
    }
    
    /**
     * Get the key bindings for the menu item.
     * @return key bindings for the menu item, or null if no item or no bindings.
     */
    public KeyBindings getKeyBindings(EMenuItem item) {
        return keyBindingManager.getKeyBindings(item.getDescription());
    }
    
    /**
     * Adds a default KeyBinding.
     * @param actionDesc the action description
     * @param pair a key stroke pair
     */
    private void addDefaultKeyBinding(EMenuItem item, KeyStroke stroke) {
        String description = item.getDescription();
        keyBindingManager.addDefaultKeyBinding(description, KeyStrokePair.getKeyStrokePair(null, stroke));
    }
    
    /**
     * Add a user defined Key binding. This gets stored to preferences.
     * @param item the menu item
     * @param stroke the key stroke bound to menu item
     * @param prefixStroke an option prefix stroke (may be null)
     */
    public void addUserKeyBinding(EMenuItem item, KeyStroke stroke, KeyStroke prefixStroke) {
        String description = item.getDescription();
        keyBindingManager.addUserKeyBinding(description, KeyStrokePair.getKeyStrokePair(prefixStroke, stroke));
        updateAccelerator(item);
     }
    
    /**
     * Sets <code>item<code> back to default Key Bindings
     * @param item the item to reset to default bindings
     */
    public void resetKeyBindings(EMenuItem item) {
        // tell KeyBindingManager to reset to default KeyBindings
        keyBindingManager.resetKeyBindings(item.getDescription());
        updateAccelerator(item);
    }
    
    /**
     * Sets *All* menu items back to their default key bindings
     */
    public void resetAllKeyBindings() {
        for (EMenuItem item: menuItems.values())
            resetKeyBindings(item);
    }
    
    /**
     * Removes a key binding.
     * @param actionDesc the item to remove the binding from
     * @param pair the key stroke pair to remove
     */
    public void removeKeyBinding(String actionDesc, KeyStrokePair pair) {
        // remove binding from binding manager
        keyBindingManager.removeKeyBinding(actionDesc, pair);
        EMenuItem item = menuItems.get(actionDesc);
        if (item != null)
            updateAccelerator(item);
    }
    
    public void restoreSavedBindings(boolean initialCall) {
        keyBindingManager.restoreSavedBindings(initialCall);
        for (EMenuItem item: menuItems.values())
            updateAccelerator(item);
    }
    
    /**
     * Updates a menu item's accelerator.  Menu item is specified by
     * actionDesc (MenuItem.getDescription()).  This is usually called after a menu item's bindings
     * have changed (binding removed, added, or reset to default). This
     * updates the accelerator to a valid binding, which is displayed when
     * the user opens the menu.
     * @param actionDesc key for menu item
     */
    private void updateAccelerator(EMenuItem item) {
        // get valid key binding, update menus with it
        String actionDesc = item.getDescription();
        KeyBindings bindings = keyBindingManager.getKeyBindings(actionDesc);
        KeyStroke accelerator = null;
        if (bindings != null) {
            Iterator<KeyStrokePair> it;
            if (bindings.getUsingDefaultKeys()) it = bindings.getDefaultKeyStrokePairs(); else
                it = bindings.getKeyStrokePairs();
            while (it.hasNext()) {
                KeyStrokePair pair = it.next();
                if (pair.getPrefixStroke() != null) continue;         // menus can't display two-stroke key bindings
                accelerator = pair.getStroke();
                break;
            }
        }
        item.setAccelerator(accelerator);
    }

    /**
     * Method to return an object that has real InputMap and ActionMap objects.
     * @return a KeyMaps object.
     */
    public KeyMaps getKeyMaps()
    {
    	return keyBindingManager.getKeyMaps();
    }
    
    public Instance genInstance() {
        return new Instance();
    }

    public class Instance extends JMenuBar {
        /** whether to ignore all shortcuts keys */ boolean ignoreKeyBindings = false;
        /** whether to ignore text editing keys */  boolean ignoreTextEditKeys = false;
        
        /**
         * Create a new MenuBar that belongs to group "name". All MenuBars
         * in the group should have the same set of menu items. The group
         * serves to maintain consistency of state and key bindings across
         * multiple instances of MenuBars that share the same layout of
         * menu items.
         */
        public Instance() {
            for (EMenuItem item: getItems())
                add(item.genMenu());
        }
        
        public EMenuBar getMenuBarGroup() { return EMenuBar.this; }
        
        /**
         * Overrides JMenuBar's processKeyBinding, which distributes event
         * to it's menu items.  Instead, we pass the event to the keyBindingManager
         * to process the event.
         * @param ks the KeyStroke of the event
         * @param e the KeyEvent
         * @param condition condition (focused/not etc)
         * @param pressed
         */
        @Override
        protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
                int condition, boolean pressed) {
            int code = e.getKeyCode();
            char ch = e.getKeyChar();
            ActivityLogger.logMessage("ProcessKeyBinding " + e.getID() + " when=" + e.getWhen() +
                    " modifiers=" + Integer.toHexString(e.getModifiersEx()) +
                    " code=" + Integer.toHexString(code) + " char=" + Integer.toHexString(ch) +
                    (' ' < ch && ch < 0x7f ? "(" + ch + ")": "") +
                    " " + ignoreKeyBindings + " " + ignoreTextEditKeys);
            // ignore key bindings if set
            if (ignoreKeyBindings) return false;
            
            // if ignoreTextEditKeys, ignore anything that does not have CTRL
            if (ignoreTextEditKeys) {
                if (!e.isControlDown() && !e.isMetaDown() && !e.isAltDown())
                    return false;                   // ignore
            }
            // see if we have a local binding (InputMap on JComponent)
            //boolean retValue = processKeyBinding(ks, e, condition, pressed);
            boolean retValue = false;
            
            // otherwise, pass to our keyBindingManager
            if (!retValue)
                retValue = keyBindingManager.processKeyEvent(e);
            // *do not* pass it to menus
            
            return retValue;
        }
        
        public void setIgnoreKeyBindings(boolean b) { ignoreKeyBindings = b; }
        
        public boolean getIgnoreKeyBindings() { return ignoreKeyBindings; }
        
        public void setIgnoreTextEditKeys(boolean b) { ignoreTextEditKeys = b; }
        
        public boolean getIgnoreTextEditKeys() { return ignoreTextEditKeys; }
        
       /** Get a string description of the menu item.
         * Takes the form <p>
         * Menu | SubMenu | SubMenu | item
         * <p>
         * @param path a path to EMenuItem
         * @return a string of the description.
         */
        JMenuItem findMenuItem(int[] path) {
            int topIndex = path[0];
            if (topIndex < 0) return null;
            JMenuItem item = (JMenuItem)getComponent(topIndex);
            for (int i = 1; i < path.length; i++) {
                JMenu menu = (JMenu)item;
                item = (JMenuItem)menu.getMenuComponent(path[i]);
            }
            return item;
        }
        
        // ------------------------------ Clean Up Methods ----------------------------
        
        /**
         * Called when a TopLevel (in SDI mode) is disposed. This gets rid
         * of references to freed menu items, so that memory allocated to them
         * can be reclaimed.
         */
        public void finished() {
            // all menus
            for (int i=0; i<getMenuCount(); i++) {
                JMenu menu = getMenu(i);
                if (menu == null) continue;
                disposeofMenu(menu);
            }
            removeAll();
        }

        private void disposeofMenu(JMenu menu) {
            for (int j=0; j<menu.getItemCount(); j++) {
                JMenuItem item = menu.getItem(j);
                if (item == null) continue;
                if (item instanceof JMenu) {
                    disposeofMenu((JMenu)item);
                } else {
                    disposeofMenuItem(item);
                }
            }
            menu.removeAll();
        }

        private void disposeofMenuItem(JMenuItem item) {
            // remove all listeners (which contain references to item)
            ActionListener [] listeners = item.getActionListeners();
            for (int k = 0; k < listeners.length; k++) {
                ActionListener listener = listeners[k];
                item.removeActionListener(listener);
            }
        }
    }
}
