/*
 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
 */

package com.sun.javafx.tk.quantum;

import com.sun.javafx.menu.CheckMenuItemBase;
import com.sun.javafx.menu.MenuBase;
import com.sun.javafx.menu.MenuItemBase;
import com.sun.javafx.menu.RadioMenuItemBase;
import com.sun.javafx.menu.SeparatorMenuItemBase;
import com.sun.javafx.PlatformUtil;
import com.sun.javafx.tk.TKSystemMenu;
import com.sun.glass.events.KeyEvent;
import com.sun.glass.ui.Application;
import com.sun.glass.ui.Menu;
import com.sun.glass.ui.MenuBar;
import com.sun.glass.ui.MenuItem;
import com.sun.glass.ui.Pixels;

import java.util.ArrayList;
import java.util.List;

import javafx.collections.ObservableList;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyCharacterCombination;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.Node;

/**
 * @author  Morris Meyer
 */
public class GlassSystemMenu implements TKSystemMenu {
        
    private List<MenuBase>      systemMenus = null;
    private MenuBar             glassSystemMenuBar = null;

    private InvalidationListener visibilityListener = new InvalidationListener() {
        @Override public void invalidated(Observable valueModel) {
            if (systemMenus != null) {
                setMenus(systemMenus);
            }
        }
    };

    protected void createMenuBar() {
        if (glassSystemMenuBar == null) {
            Application app = Application.GetApplication();
            glassSystemMenuBar = app.createMenuBar();
            app.installDefaultMenus(glassSystemMenuBar);

            if (systemMenus != null) {
                setMenus(systemMenus);
            }
        }
    }
    
    protected MenuBar getMenuBar() {
        return glassSystemMenuBar;
    }
    
    @Override public boolean isSupported() {
        return (PlatformUtil.isMac());
    }

    @Override public void setMenus(List<MenuBase> menus) {
        Application app = Application.GetApplication();
        systemMenus = menus;
        if (glassSystemMenuBar != null) {
            
            /*
             * Remove existing menus
             */
            List<Menu> existingMenus = glassSystemMenuBar.getMenus();
            int existingSize = existingMenus.size();

            /*
             * Leave the Apple menu in place
             */
            for (int index = existingSize - 1; index >= 1; index--) {
                glassSystemMenuBar.remove(index);
            }
            
            for (MenuBase menu : menus) {
                addMenu(null, menu);
            }
        }
    }

    private void addMenu(Menu parent, final MenuBase mb) {
        Application app = Application.GetApplication();
        Menu glassMenu = app.createMenu(parseText(mb), ! mb.isDisable());
        glassMenu.setEventHandler(new GlassMenuEventHandler(mb));

        // There is no way of knowing if listener was already added.
        mb.visibleProperty().removeListener(visibilityListener);
        mb.visibleProperty().addListener(visibilityListener);

        if (!mb.isVisible()) {
            return;
        }

        ObservableList<MenuItemBase> items = mb.getItemsBase();

        for (MenuItemBase item : items) {
            if (item instanceof MenuBase) {
                // submenu
                addMenu(glassMenu, (MenuBase)item);
            } else {
                // menu item
                addMenuItem(glassMenu, item);
            }
        }
        glassMenu.setPixels(getPixels(mb));

        setMenuBindings(glassMenu, mb);

        if (parent != null) {
            parent.add(glassMenu);
        } else {
            glassSystemMenuBar.add(glassMenu);
        }
    }

    private void setMenuBindings(final Menu glassMenu, final MenuBase mb) {
        mb.textProperty().addListener(new InvalidationListener() {
            @Override public void invalidated(Observable valueModel) {
                glassMenu.setTitle(parseText(mb));
            }
        });
        mb.disableProperty().addListener(new InvalidationListener() {
            @Override public void invalidated(Observable valueModel) {
                glassMenu.setEnabled(!mb.isDisable());
            }
        });
        mb.mnemonicParsingProperty().addListener(new InvalidationListener() {
            @Override public void invalidated(Observable valueModel) {
                glassMenu.setTitle(parseText(mb));
            }
        });
    }

    private void addMenuItem(Menu parent, final MenuItemBase menuitem) {
        final Node graphic = menuitem.getGraphic();
        Application app = Application.GetApplication();
        //System.err.println(menuitem.getText() + " : " + menuitem.getClass().getName());

        // There is no way of knowing if listener was already added.
        menuitem.visibleProperty().removeListener(visibilityListener);
        menuitem.visibleProperty().addListener(visibilityListener);

        if (!menuitem.isVisible()) {
            return;
        }

        if (menuitem instanceof SeparatorMenuItemBase) {
            if (menuitem.isVisible()) {
                parent.add(MenuItem.Separator);
            }
        } else {
            MenuItem.Callback callback = new MenuItem.Callback() {
                @Override public void action() {
                    // toggle state of check or radio items (from ContextMenuContent.java)
                    if (menuitem instanceof CheckMenuItemBase) {
                        CheckMenuItemBase checkItem = (CheckMenuItemBase)menuitem;
                        checkItem.setSelected(!checkItem.isSelected());
                    } else if (menuitem instanceof RadioMenuItemBase) {
                        // this is a radio button. If there is a toggleGroup specified, we
                        // simply set selected to true. If no toggleGroup is specified, we
                        // toggle the selected state, as there is no assumption of mutual
                        // exclusivity when no toggleGroup is set.
                        RadioMenuItemBase radioItem = (RadioMenuItemBase)menuitem;
                        // Note: The ToggleGroup is not exposed for RadioMenuItemBase,
                        // so we just assume that one has been set at this point.
                        //radioItem.setSelected(radioItem.getToggleGroup() != null ? true : !radioItem.isSelected());
                        radioItem.setSelected(true);
                    }
                    menuitem.fire();
                }
            };

            final MenuItem glassSubMenuItem = app.createMenuItem(parseText(menuitem), callback);

            menuitem.textProperty().addListener(new InvalidationListener() {
                @Override public void invalidated(Observable valueModel) {
                    glassSubMenuItem.setTitle(parseText(menuitem));
                }
            });

            glassSubMenuItem.setPixels(getPixels(menuitem));
            menuitem.graphicProperty().addListener(new InvalidationListener() {
                @Override public void invalidated(Observable valueModel) {
                    glassSubMenuItem.setPixels(getPixels(menuitem));
                }
            });

            glassSubMenuItem.setEnabled(! menuitem.isDisable());
            menuitem.disableProperty().addListener(new InvalidationListener() {
                @Override public void invalidated(Observable valueModel) {
                    glassSubMenuItem.setEnabled(!menuitem.isDisable());
                }
            });

            setShortcut(glassSubMenuItem, menuitem);
            menuitem.acceleratorProperty().addListener(new InvalidationListener() {
                @Override public void invalidated(Observable valueModel) {
                    setShortcut(glassSubMenuItem, menuitem);
                }
            });

            menuitem.mnemonicParsingProperty().addListener(new InvalidationListener() {
                @Override public void invalidated(Observable valueModel) {
                    glassSubMenuItem.setTitle(parseText(menuitem));
                }
            });

            if (menuitem instanceof CheckMenuItemBase) {
                final CheckMenuItemBase checkItem = (CheckMenuItemBase)menuitem;
                glassSubMenuItem.setChecked(checkItem.isSelected());
                checkItem.selectedProperty().addListener(new InvalidationListener() {
                    @Override public void invalidated(Observable valueModel) {
                        glassSubMenuItem.setChecked(checkItem.isSelected());
                    }
                });
            } else if (menuitem instanceof RadioMenuItemBase) {
                final RadioMenuItemBase radioItem = (RadioMenuItemBase)menuitem;
                glassSubMenuItem.setChecked(radioItem.isSelected());
                radioItem.selectedProperty().addListener(new InvalidationListener() {
                    @Override public void invalidated(Observable valueModel) {
                        glassSubMenuItem.setChecked(radioItem.isSelected());
                    }
                });
            }

            parent.add(glassSubMenuItem);
        }
    }

    private String parseText(MenuItemBase menuItem) {
        String text = menuItem.getText();
        if (text == null) {
            // don't pass null strings to Glass
            return "";
        } else if (!text.isEmpty() && menuItem.isMnemonicParsing()) {
            // \ufffc is a placeholder character
            //return text.replace("__", "\ufffc").replace("_", "").replace("\ufffc", "_");
            return text.replaceFirst("_([^_])", "$1");
        } else {
            return text;
        }
    }

    private Pixels getPixels(MenuItemBase menuItem) {
        if (menuItem.getGraphic() instanceof ImageView) {
            ImageView iv           = (ImageView)menuItem.getGraphic();
            Image     im           = iv.getImage();
            String    url          = im.impl_getUrl();

            if (url == null || PixelUtils.supportedFormatType(url)) {
                com.sun.prism.Image pi = (com.sun.prism.Image)im.impl_getPlatformImage();

                return pi == null ? null : PixelUtils.imageToPixels(pi);
            }
        }
        return (null);
    }

    private void setShortcut(MenuItem glassSubMenuItem, MenuItemBase menuItem) {
        final KeyCombination accelerator = menuItem.getAccelerator();

        if (accelerator == null) {
            // FIXME - Verify that this is how to clear the shortcut
            glassSubMenuItem.setShortcut(0, 0);
        } else if (accelerator instanceof KeyCodeCombination) {
            KeyCodeCombination kcc  = (KeyCodeCombination)accelerator;
            KeyCode            code = kcc.getCode();

            assert PlatformUtil.isMac() || PlatformUtil.isLinux();

            int modifier = glassModifiers(kcc);
            String lower = code.impl_getChar().toLowerCase();

            if (PlatformUtil.isMac()) {
                glassSubMenuItem.setShortcut(lower.charAt(0), modifier);
            } else if (PlatformUtil.isLinux()) {
                if ((modifier & KeyEvent.MODIFIER_CONTROL) != 0) {
                    glassSubMenuItem.setShortcut(lower.charAt(0), modifier);
                } else {
                    glassSubMenuItem.setShortcut(0, 0);
                }
            } else {
                glassSubMenuItem.setShortcut(0, 0);
            }
        } else if (accelerator instanceof KeyCharacterCombination) {
            KeyCharacterCombination kcc = (KeyCharacterCombination)accelerator;
            String kchar = kcc.getCharacter();
            glassSubMenuItem.setShortcut(kchar.charAt(0), glassModifiers(kcc));
        }
    }

    private int glassModifiers(KeyCombination kcc) {
        int ret = 0;
        if (kcc.getShift() == KeyCombination.ModifierValue.DOWN) {
            ret += KeyEvent.MODIFIER_SHIFT;
        }
        if (kcc.getControl() == KeyCombination.ModifierValue.DOWN) {
            ret += KeyEvent.MODIFIER_CONTROL;
        }
        if (kcc.getAlt() == KeyCombination.ModifierValue.DOWN) {
            ret += KeyEvent.MODIFIER_ALT;
        }
        if (kcc.getShortcut() == KeyCombination.ModifierValue.DOWN) {
            if (PlatformUtil.isLinux()) {
                ret += KeyEvent.MODIFIER_CONTROL;
            } else if (PlatformUtil.isMac()) {
                ret += KeyEvent.MODIFIER_COMMAND;
            }
        }
        if (kcc.getMeta() == KeyCombination.ModifierValue.DOWN) {
            if (PlatformUtil.isLinux()) {
                ret += KeyEvent.MODIFIER_WINDOWS;   // RT-19326 - Linux shortcut support
            } else if (PlatformUtil.isMac()) {
                ret += KeyEvent.MODIFIER_COMMAND;
            }
        }

        if (kcc instanceof KeyCodeCombination) {
            KeyCode kcode = ((KeyCodeCombination)kcc).getCode();
            int     code  = kcode.impl_getCode();

            if (((code >= KeyCode.F1.impl_getCode())  && (code <= KeyCode.F12.impl_getCode())) ||
                ((code >= KeyCode.F13.impl_getCode()) && (code <= KeyCode.F24.impl_getCode()))) {
                ret += KeyEvent.MODIFIER_FUNCTION;
            }
        }
                       
        return (ret);
    }
        
}
