/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy 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.
 * 
 * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.gui.menu;

import icy.action.GeneralActions;
import icy.action.PreferencesActions;
import icy.action.WindowActions;
import icy.gui.component.button.IcyCommandButton;
import icy.gui.component.button.IcyCommandMenuButton;
import icy.gui.component.button.IcyCommandToggleButton;
import icy.gui.component.button.IcyCommandToggleMenuButton;
import icy.gui.frame.IcyFrame;
import icy.gui.frame.progress.TaskFrame;
import icy.gui.main.ActiveSequenceListener;
import icy.gui.main.MainFrame;
import icy.gui.menu.search.SearchBar;
import icy.gui.plugin.PluginCommandButton;
import icy.gui.util.LookAndFeelUtil;
import icy.gui.util.RibbonUtil;
import icy.gui.viewer.Viewer;
import icy.imagej.ImageJWrapper;
import icy.main.Icy;
import icy.plugin.PluginDescriptor;
import icy.plugin.PluginDescriptor.PluginClassNameSorter;
import icy.plugin.PluginLoader;
import icy.plugin.PluginLoader.PluginLoaderEvent;
import icy.plugin.PluginLoader.PluginLoaderListener;
import icy.preferences.GeneralPreferences;
import icy.preferences.WorkspaceLocalPreferences;
import icy.resource.ResourceUtil;
import icy.resource.icon.IcyIcon;
import icy.sequence.Sequence;
import icy.sequence.SequenceEvent;
import icy.sequence.SequenceEvent.SequenceEventSourceType;
import icy.system.thread.ThreadUtil;
import icy.workspace.Workspace;
import icy.workspace.Workspace.TaskDefinition;
import icy.workspace.Workspace.TaskDefinition.BandDefinition;
import icy.workspace.Workspace.TaskDefinition.BandDefinition.ItemDefinition;
import icy.workspace.WorkspaceInstaller;
import icy.workspace.WorkspaceLoader;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;

import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
import org.pushingpixels.flamingo.api.common.CommandToggleButtonGroup;
import org.pushingpixels.flamingo.api.common.JCommandButton;
import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
import org.pushingpixels.flamingo.api.common.RichTooltip;
import org.pushingpixels.flamingo.api.common.popup.JCommandPopupMenu;
import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
import org.pushingpixels.flamingo.api.common.popup.PopupPanelCallback;
import org.pushingpixels.flamingo.api.ribbon.JRibbon;
import org.pushingpixels.flamingo.api.ribbon.JRibbonBand;
import org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority;
import org.pushingpixels.flamingo.api.ribbon.RibbonTask;
import org.pushingpixels.flamingo.api.ribbon.resize.CoreRibbonResizeSequencingPolicies;

/**
 * This class is used to separate ribbon construction from the ribbon frame
 * 
 * @author Stephane
 */
public class MainRibbon implements PluginLoaderListener, ActiveSequenceListener
{
    /**
     * TASK / BAND NAMES
     */
    public static final String TASK_PLUGINS = "Plugins";
    public static final String BAND_SETUP = "Setup";
    public static final String BAND_NEW = "New";
    public static final String BAND_OTHERS = "Others";

    /**
     * loaded workspaces
     */
    private final ArrayList<Workspace> workspaces;
    private Workspace systemWorkspace;

    /**
     * Search bar object
     */
    SearchBar searchBar;

    /**
     * internals
     */
    BandDefinition setupPluginsBandDef;
    BandDefinition newPluginsBandDef;
    BandDefinition othersPluginsBandDef;

    private final JRibbon ribbon;
    private final ApplicationMenu applicationMenu;
    // private final JRibbonBand othersPluginsBand;
    private final JRibbonBand setupPluginsBand;
    private final JRibbonBand newPluginsBand;
    // private final ImageRibbonTask imageTask;
    private final SequenceOperationTask sequenceOperationTask;
    private final ToolRibbonTask toolRibbonTask;
    private final ImageJTask ijTask;
    final JMenu othersPluginsMenu;

    CommandToggleButtonGroup multiWindowGroup;
    IcyCommandToggleButton multiWindowButton;

    /**
     * @param ribbon
     */
    public MainRibbon(JRibbon ribbon)
    {
        super();

        this.ribbon = ribbon;

        workspaces = new ArrayList<Workspace>();
        othersPluginsMenu = new JMenu("Plugins");

        // APPLICATION MENU & MISC

        applicationMenu = new ApplicationMenu();
        ribbon.setApplicationMenu(applicationMenu);
        final RichTooltip toolTip = new RichTooltip("ICY Application menu", "Load, close and save Sequence from there.");
        ribbon.setApplicationMenuRichTooltip(toolTip);
        // ribbon.configureHelp(new ICYResizableIcon(new IcyIcon("lightbulb"),
        // new
        // Help("Main_Page"));

        // TASKBAR

        buidlTaskBar();

        // FIXED TASKS

        // load image task first as tools task need all plugins loaded...
        // imageTask = new ImageRibbonTask();
        sequenceOperationTask = new SequenceOperationTask();
        toolRibbonTask = new ToolRibbonTask();
        ijTask = new ImageJTask();
        // we want tools task to be the first task
        ribbon.addTask(toolRibbonTask);
        // ribbon.addTask(imageTask);
        ribbon.addTask(sequenceOperationTask);
        ribbon.addTask(ijTask);

        // WORKSPACES

        // load workspace from files
        loadWorkspaces();

        // store system band definition
        setupPluginsBandDef = systemWorkspace.findBand(TASK_PLUGINS, BAND_SETUP);
        newPluginsBandDef = systemWorkspace.findBand(TASK_PLUGINS, BAND_NEW);
        othersPluginsBandDef = systemWorkspace.findBand(TASK_PLUGINS, BAND_OTHERS);

        // build workspaces menu
        buildWorkspaces();

        // store system band
        final RibbonTask pluginTask = RibbonUtil.getTask(ribbon, TASK_PLUGINS);
        setupPluginsBand = RibbonUtil.getBand(pluginTask, BAND_SETUP);
        newPluginsBand = RibbonUtil.getBand(pluginTask, BAND_NEW);
        // othersPluginsBand = RibbonUtil.getBand(pluginTask, BAND_OTHERS);

        // build plugin setup band
        buildSetupPluginBand();

        // save workspaces back so removed stuff are cleaned
        // saveWorkspaces();

        PluginLoader.addListener(this);
        Icy.getMainInterface().addActiveSequenceListener(this);
    }

    // some stuff which need to be initialized after ribbon creation
    public void init()
    {
        ijTask.init();

        final MainFrame mainFrame = Icy.getMainInterface().getMainFrame();
        mainFrame.addPropertyChangeListener(MainFrame.PROPERTY_DETACHEDMODE, new PropertyChangeListener()
        {
            @Override
            public void propertyChange(PropertyChangeEvent evt)
            {
                multiWindowGroup.setSelected(multiWindowButton, Icy.getMainInterface().isDetachedMode());
            }
        });
    }

    public ToolRibbonTask getToolRibbon()
    {
        return toolRibbonTask;
    }

    public SequenceOperationTask getSequenceOperationTask()
    {
        return sequenceOperationTask;
    }

    public ImageJTask getImageJTask()
    {
        return ijTask;
    }

    public ImageJWrapper getImageJ()
    {
        return ijTask.getImageJ();
    }

    public SearchBar getSearchBar()
    {
        return searchBar;
    }

    private void loadWorkspaces()
    {
        ArrayList<String> workspacesName;

        // load names from preference
        workspacesName = WorkspaceLocalPreferences.getActivesWorkspace();

        // wait for workspace loader is ready
        WorkspaceLoader.waitWhileLoading();

        // load workspaces
        workspaces.clear();
        for (String name : workspacesName)
        {
            // get workspace from loader
            final Workspace ws = WorkspaceLoader.getWorkspace(name);

            // add to active workspace list if not empty
            if (ws == null)
                System.err.println("Workspace " + name + " not found !");
            else if (isInConflict(ws, true))
                System.err.println("Workspace '" + name + "' is discarded (conflict detected)");
            else
                workspaces.add(ws);
        }

        // clean up invalid entries
        workspacesName.clear();
        for (Workspace ws : workspaces)
            workspacesName.add(ws.getName());

        // save back cleaned names to preference
        WorkspaceLocalPreferences.setActivesWorkspace(workspacesName);

        // always add the system workspace
        systemWorkspace = new Workspace(Workspace.WORKSPACE_SYSTEM_NAME);

        // recreate system workspace manually if needed
        systemWorkspace.setDescription("System workspace");
        // this actually add only missing tasks and bands
        systemWorkspace.addBand(MainRibbon.TASK_PLUGINS, MainRibbon.BAND_SETUP);
        systemWorkspace.addBand(MainRibbon.TASK_PLUGINS, MainRibbon.BAND_NEW);
        systemWorkspace.addBand(MainRibbon.TASK_PLUGINS, MainRibbon.BAND_OTHERS);

        workspaces.add(systemWorkspace);
    }

    /**
     * return true if specified workspace is in conflict with current actives workspace
     */
    private boolean isInConflict(Workspace ws, boolean showAsError)
    {
        for (TaskDefinition task : ws.getTasks())
        {
            for (BandDefinition band : task.getBands())
            {
                // conflict : same task and same band in 2 different workspaces
                if (findBand(task.getName(), band.getName()) != null)
                {
                    if (showAsError)
                        System.err.println("Duplicated band : " + task.getName() + "/" + band.getName());

                    return true;
                }
            }
        }

        return false;
    }

    /**
     * get all tasks from actives workspace
     */
    private List<TaskDefinition> getTasks()
    {
        final List<TaskDefinition> result = new ArrayList<TaskDefinition>();

        for (Workspace ws : workspaces)
            for (TaskDefinition task : ws.getTasks())
                if (!Workspace.contains(result, task))
                    result.add(task);

        return result;
    }

    /**
     * get all bands for a task from actives workspace
     */
    private List<BandDefinition> getBands(String taskName)
    {
        final List<BandDefinition> result = new ArrayList<BandDefinition>();

        for (Workspace ws : workspaces)
            for (TaskDefinition task : ws.getTasks())
                if (task.getName().equals(taskName))
                    for (BandDefinition band : task.getBands())
                        if (!Workspace.contains(result, band))
                            result.add(band);

        return result;
    }

    // private Workspace findWorkspace(String workspaceName)
    // {
    // for (Workspace ws : workspaces)
    // if (ws.getName().equals(workspaceName))
    // return ws;
    //
    // return null;
    // }

    private BandDefinition findBand(String taskName, String bandName)
    {
        for (Workspace ws : workspaces)
        {
            final TaskDefinition task = ws.findTask(taskName);
            if (task != null)
            {
                final BandDefinition band = task.findBand(bandName);
                if (band != null)
                    return band;
            }
        }

        return null;
    }

    /**
     * Get all items from all active workspace
     */
    private ArrayList<ItemDefinition> getAllItems()
    {
        final ArrayList<ItemDefinition> result = new ArrayList<ItemDefinition>();

        // get all items from active workspaces
        for (Workspace workspace : workspaces)
            result.addAll(workspace.getAllItems());

        return result;
    }

    ItemDefinition findItem(String className)
    {
        // search item in active workspaces
        for (Workspace workspace : workspaces)
        {
            final ItemDefinition item = workspace.findItem(className);
            if (item != null)
                return item;
        }

        return null;
    }

    void addItem(String className)
    {
        addItem(PluginLoader.getPlugin(className));
    }

    /**
     * Add an item for the specified plugin.
     */
    void addItem(PluginDescriptor plugin)
    {
        // check that plugin can be displayed in menu
        if ((plugin != null) && plugin.isActionable())
        {
            final IcyCommandButton pluginButton = PluginCommandButton.createButton(plugin);

            // add it to the new installed plugins workspace and save it
            newPluginsBandDef.addItem(plugin.getClassName(), RibbonElementPriority.TOP);
            systemWorkspace.save();
            // add it to the new installed plugins band
            newPluginsBand.addCommandButton(pluginButton, RibbonElementPriority.TOP);
        }
    }

    void updateItem(String className)
    {
        updateItem(PluginLoader.getPlugin(className), findItem(className));
    }

    void updateItem(PluginDescriptor plugin, ItemDefinition item)
    {
        // check that plugin can be displayed in menu
        if ((plugin != null) && plugin.isActionable())
        {
            if (item != null)
            {
                // find the corresponding button
                final AbstractCommandButton button = RibbonUtil.findButton(
                        RibbonUtil.getBand(RibbonUtil.getTask(ribbon, item.getTaskName()), item.getBandName()),
                        item.getClassName());

                // button found --> update it
                if (button != null)
                    PluginCommandButton.setButton(button, plugin);
            }
            else
            {
                final Workspace workspace = WorkspaceInstaller.getCurrentInstallingWorkspace();

                // check plugin wasn't installed from a workspace
                if ((workspace == null) || (workspace.findItem(plugin.getClassName()) == null))
                    // add a new item for this plugin
                    addItem(plugin);
            }
        }
    }

    void removeItem(String className)
    {
        removeItem(findItem(className));
    }

    void removeItem(ItemDefinition item)
    {
        if (item != null)
        {
            // FIXME : unsafe (ribbon component is not supposed to support that)
            RibbonUtil.removeButton(
                    RibbonUtil.getBand(RibbonUtil.getTask(ribbon, item.getTaskName()), item.getBandName()),
                    item.getClassName());
            // remove item and save workspace
            item.remove();
            // save system workspace only, we want to preserve plugins organization
            systemWorkspace.save();
        }
    }

    private void buildSetupPluginBand()
    {
        setupPluginsBand.addCommandButton(new IcyCommandButton(PreferencesActions.onlinePluginPreferencesAction),
                RibbonElementPriority.TOP);

        RibbonUtil.setFixedResizePolicies(setupPluginsBand);
    }

    // /**
    // * Add a new installed plugin
    // */
    // public void addNewPlugin(String className)
    // {
    // // get the corresponding plugin
    // final PluginDescriptor plugin = PluginLoader.getPlugin(className);
    //
    // // check that menu can be displayed in menu
    // if ((plugin == null) || !plugin.isActionable())
    // return;
    //
    // final IcyCommandButton pluginButton = buildPluginCommandButton(plugin);
    //
    // // add it to the new installed plugins workspace and save it
    // newPluginsBandDef.addItem(plugin.getClassName(), RibbonElementPriority.TOP);
    // systemWorkspace.save();
    // // add it to the new installed plugins band
    // newPluginsBand.addCommandButton(pluginButton, RibbonElementPriority.TOP);
    // }

    private JRibbonBand[] createRibbonBands(TaskDefinition task)
    {
        final List<BandDefinition> bands = getBands(task.getName());
        final int size = bands.size();
        final JRibbonBand[] result = new JRibbonBand[size];

        for (int i = 0; i < size; i++)
        {
            final BandDefinition band = bands.get(i);
            // TODO : get icon from BandDefinition
            result[i] = new JRibbonBand(band.getName(), new IcyIcon(ResourceUtil.ICON_DOC));
            // use restrictive resize policy by default
            RibbonUtil.setRestrictiveResizePolicies(result[i]);
        }

        return result;
    }

    private RibbonTask createRibbonTask(TaskDefinition task)
    {
        final String name = task.getName();

        final RibbonTask ribbonTask = RibbonUtil.getTask(ribbon, name);
        if (ribbonTask != null)
        {
            System.out.println("Ribbon task " + name + " already exists...");
            return ribbonTask;
        }

        final RibbonTask result;

        try
        {
            result = new RibbonTask(name, createRibbonBands(task));
        }
        catch (IllegalArgumentException e)
        {
            return null;
        }

        // use roundRobin collapse policy
        result.setResizeSequencingPolicy(new CoreRibbonResizeSequencingPolicies.RoundRobin(result));

        return result;
    }

    /**
     * build ribbon from workspaces
     */
    private void buildWorkspaces()
    {
        final ArrayList<PluginDescriptor> plugins = PluginLoader.getPlugins(false);

        // get all TaskDefinition from all active workspace
        for (TaskDefinition task : getTasks())
        {
            // create the task with all needed bands
            final RibbonTask ribbonTask = createRibbonTask(task);

            // empty task --> ignore
            if (ribbonTask == null)
                continue;

            for (BandDefinition band : getBands(task.getName()))
            {
                final JRibbonBand ribbonBand = RibbonUtil.getBand(ribbonTask, band.getName());

                if (ribbonBand != null)
                {
                    // special case of OTHER plugins
                    if (band == othersPluginsBandDef)
                    {
                        final IcyCommandButton btn = new IcyCommandButton("Other Plugins", new IcyIcon(
                                ResourceUtil.ICON_COG));
                        btn.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
                        btn.setPopupRichTooltip(new RichTooltip("Other plugins",
                                "You can find here all plugins which are not associated to a workspace"));
                        btn.setPopupCallback(new PopupPanelCallback()
                        {
                            @Override
                            public JPopupPanel getPopupPanel(JCommandButton commandButton)
                            {
                                final JPopupMenu popupMenu = othersPluginsMenu.getPopupMenu();

                                // FIXME : set as heavy weight component for VTK (doesn't work)
                                // popupMenu.setLightWeightPopupEnabled(false);
                                popupMenu.show(btn, 0, btn.getHeight());

                                return null;
                            }
                        });
                        ribbonBand.addCommandButton(btn, RibbonElementPriority.TOP);

                        // refresh unassigned list
                        refreshOthersPluginsList();

                        // adjust restrictive resize policy
                        RibbonUtil.setFixedResizePolicies(ribbonBand);
                    }
                    else
                    {
                        for (ItemDefinition item : band.getItems())
                        {
                            // simple separator
                            if (item.isSeparator())
                                ribbonBand.startGroup();
                            else
                            {
                                final String className = item.getClassName();
                                final PluginDescriptor plugin = PluginDescriptor.getPlugin(plugins, className);

                                // plugin found ?
                                if (plugin != null)
                                {
                                    // check that menu can be displayed in menu
                                    if (plugin.isActionable())
                                        ribbonBand.addCommandButton(PluginCommandButton.createButton(plugin),
                                                item.getPriority());
                                }
                            }
                        }

                        // adjust restrictive resize policy
                        RibbonUtil.setRestrictiveResizePolicies(ribbonBand);
                    }
                }
            }

            // add task to ribbon only if not empty
            if (ribbonTask.getBandCount() > 0)
                ribbon.addTask(ribbonTask);
        }
    }

    /**
     * clean workspace (remove absent plug-in)
     */
    public void cleanWorkspaces()
    {
        final ArrayList<PluginDescriptor> plugins = PluginLoader.getPlugins(false);

        // get all items TaskDefinition from all active workspace
        for (ItemDefinition item : getAllItems())
        {
            // avoid separator
            if (!item.isSeparator())
            {
                final String className = item.getClassName();
                final PluginDescriptor plugin = PluginDescriptor.getPlugin(plugins, className);

                // plugin not found --> remove from workspace
                if (plugin == null)
                    item.remove();
            }
        }

        // save cleaned workspaces
        saveWorkspaces();
    }

    void refreshOthersPluginsList()
    {
        // build others plugin list
        final ArrayList<PluginDescriptor> othersPlugins = new ArrayList<PluginDescriptor>();

        // scan all actionable plugins to find unassigned ones
        for (PluginDescriptor plugin : PluginLoader.getActionablePlugins())
        {
            final String className = plugin.getClassName();
            // search item in actives workspace
            final ItemDefinition item = findItem(className);

            // plugin not defined in active workspaces --> add it to the list
            if ((item == null) || (item.getBandDefinition() == othersPluginsBandDef))
                othersPlugins.add(plugin);
        }

        // refresh unassigned plugins menu
        builOthersPluginsMenu(othersPlugins);

        // rebuild the unassigned workspace
        othersPluginsBandDef.clear();
        for (PluginDescriptor plugin : othersPlugins)
            othersPluginsBandDef.addItem(plugin.getClassName());

        // save the system workspace
        systemWorkspace.save();
    }

    private void builOthersPluginsMenu(ArrayList<PluginDescriptor> plugins)
    {
        othersPluginsMenu.removeAll();

        for (PluginDescriptor pluginDescriptor : plugins)
        {
            String pluginEntry = pluginDescriptor.getSimplePackageName();
            JMenu menuToPutPlugin = othersPluginsMenu;

            while (pluginEntry != null)
            {
                final int index = pluginEntry.indexOf(".");
                final String pluginDir;

                if (index != -1)
                {
                    pluginDir = pluginEntry.substring(0, index);
                    pluginEntry = pluginEntry.substring(index + 1);
                }
                else
                {
                    pluginDir = pluginEntry;
                    pluginEntry = null;
                }

                // look if name is already a menu.
                boolean menuExist = false;
                for (Component component : menuToPutPlugin.getMenuComponents())
                {
                    if (component instanceof JMenu)
                    {
                        final JMenu menu = (JMenu) component;

                        if (menu.getText().equals(pluginDir))
                        {
                            menuToPutPlugin = menu;
                            menuExist = true;
                            break;
                        }
                    }
                }

                if (menuExist == false)
                {
                    // create Menu
                    final JMenu menu = new JMenu(pluginDir);
                    menuToPutPlugin.add(menu);
                    menuToPutPlugin = menu;
                }
            }

            menuToPutPlugin.add(new PluginMenuItem(pluginDescriptor));
        }

        if (plugins.isEmpty())
            othersPluginsMenu.add(new JMenuItem("No plugins"));

        othersPluginsMenu.validate();
    }

    /**
     * save workspaces from menu
     */
    void saveWorkspaces()
    {
        // save all active workspaces
        for (Workspace workspace : workspaces)
            workspace.save();
    }

    /**
     * build task bar (little bar with small icons over all at top)
     */
    private void buidlTaskBar()
    {
        // PREFERENCES
        ribbon.addTaskbarComponent(new IcyCommandButton(PreferencesActions.preferencesAction));

        // PLUGINS
        ribbon.addTaskbarComponent(new IcyCommandButton(PreferencesActions.onlinePluginPreferencesAction));

        // SEPARATOR
        ribbon.addTaskbarComponent(new JSeparator(SwingConstants.VERTICAL));

        // MULTI FRAME MODE
        multiWindowGroup = new CommandToggleButtonGroup();
        multiWindowButton = new IcyCommandToggleButton(GeneralActions.detachedModeAction);
        ribbon.addTaskbarComponent(multiWindowButton);

        multiWindowGroup.add(multiWindowButton);
        multiWindowGroup.setSelected(multiWindowButton, GeneralPreferences.getMultiWindowMode());

        // WINDOWS
        final IcyCommandButton windowsButton = new IcyCommandButton(new IcyIcon("app_window"));

        windowsButton.setPopupRichTooltip(new RichTooltip("Windows",
                "Show specific windows and general windows setting..."));
        windowsButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
        windowsButton.setPopupCallback(new PopupPanelCallback()
        {
            @Override
            public JPopupPanel getPopupPanel(JCommandButton commandButton)
            {
                final JCommandPopupMenu result = new JCommandPopupMenu();

                // ALWAYS ON TOP
                final CommandToggleButtonGroup aotGroup = new CommandToggleButtonGroup();
                final IcyCommandToggleMenuButton aotButton = new IcyCommandToggleMenuButton(
                        WindowActions.stayOnTopAction);
                result.addMenuButton(aotButton);

                aotGroup.add(aotButton);
                aotGroup.setSelected(aotButton, GeneralPreferences.getAlwaysOnTop());

                // SEPARATOR
                result.addMenuSeparator();

                // LOOK AND FEEL
                final IcyCommandMenuButton lafButton = new IcyCommandMenuButton("Appearance", new IcyIcon(
                        ResourceUtil.ICON_SMILEY_HAPPY));

                lafButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
                lafButton.setPopupRichTooltip(new RichTooltip("Look and feel", "Change appearance of the interface"));
                lafButton.setPopupCallback(new PopupPanelCallback()
                {
                    @Override
                    public JPopupPanel getPopupPanel(JCommandButton commandButton)
                    {
                        // better to build it on request as it takes a bit of time
                        // and we want to speed up the initial loading
                        return LookAndFeelUtil.getLookAndFeelMenu();
                    }
                });
                result.addMenuButton(lafButton);

                // SEPARATOR
                result.addMenuSeparator();

                // SWIMMING POOL
                final IcyCommandMenuButton spButton = new IcyCommandMenuButton(WindowActions.swimmingPoolAction);
                result.addMenuButton(spButton);

                // SCRIPT EDITOR
                // TODO : reactivate when done
                // final IcyCommandMenuButton seButton = new IcyCommandMenuButton("Script Editor",
                // new
                // ICYResizableIcon(new IcyIcon(
                // "lighting"));
                // seButton.addActionListener(new ActionListener()
                // {
                // @Override
                // public void actionPerformed(ActionEvent e)
                // {
                // new ScriptEditor();
                // }
                // });
                // result.addMenuButton(seButton);

                // SEPARATOR
                result.addMenuSeparator();

                // REORGANIZE TILE
                final IcyCommandMenuButton tileButton = new IcyCommandMenuButton("Tile", new IcyIcon("2x2_grid"));
                tileButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
                tileButton.setPopupCallback(new PopupPanelCallback()
                {
                    @Override
                    public JPopupPanel getPopupPanel(JCommandButton commandButton)
                    {
                        final JCommandPopupMenu result = new JCommandPopupMenu();

                        // grid
                        result.addMenuButton(new IcyCommandMenuButton(WindowActions.gridTileAction));
                        // horizontal
                        result.addMenuButton(new IcyCommandMenuButton(WindowActions.horizontalTileAction));
                        // vertical
                        result.addMenuButton(new IcyCommandMenuButton(WindowActions.verticalTileAction));

                        return result;
                    }
                });
                result.addMenuButton(tileButton);

                // REORGANIZE CASCADE
                result.addMenuButton(new IcyCommandMenuButton(WindowActions.cascadeAction));

                // SEPARATOR
                result.addMenuSeparator();

                // OPENED SEQUENCES
                final ArrayList<Viewer> allViewers = Icy.getMainInterface().getViewers();
                final IcyCommandMenuButton sequencesButton = new IcyCommandMenuButton("Opened sequences", new IcyIcon(
                        ResourceUtil.ICON_PICTURE));
                sequencesButton.setPopupRichTooltip(new RichTooltip("Opened sequences", "Show the selected sequence"));
                sequencesButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
                sequencesButton.setPopupCallback(new PopupPanelCallback()
                {
                    @Override
                    public JPopupPanel getPopupPanel(JCommandButton commandButton)
                    {
                        final JCommandPopupMenu result = new JCommandPopupMenu();

                        // SEQUENCES
                        for (Viewer viewer : allViewers)
                        {
                            final Viewer v = viewer;

                            final IcyCommandMenuButton seqButton = new IcyCommandMenuButton(viewer.getTitle());
                            seqButton.addActionListener(new ActionListener()
                            {
                                @Override
                                public void actionPerformed(ActionEvent e)
                                {
                                    ThreadUtil.invokeLater(new Runnable()
                                    {
                                        @Override
                                        public void run()
                                        {
                                            // remove minimized state
                                            if (v.isMinimized())
                                                v.setMinimized(false);
                                            // then grab focus
                                            v.requestFocus();
                                            v.toFront();
                                        }
                                    });
                                }
                            });
                            result.addMenuButton(seqButton);
                        }

                        return result;
                    }
                });
                sequencesButton.setEnabled(allViewers.size() > 0);
                result.addMenuButton(sequencesButton);

                // OPENED FRAMES
                final ArrayList<IcyFrame> allFrames = IcyFrame.getAllFrames();
                final IcyCommandMenuButton framesButton = new IcyCommandMenuButton("Opened frames", new IcyIcon(
                        ResourceUtil.ICON_WINDOW));
                framesButton.setPopupRichTooltip(new RichTooltip("Opened frames", "Show all frames"));
                framesButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
                framesButton.setPopupCallback(new PopupPanelCallback()
                {
                    @Override
                    public JPopupPanel getPopupPanel(JCommandButton commandButton)
                    {
                        final JCommandPopupMenu result = new JCommandPopupMenu();

                        // FRAMES
                        for (IcyFrame frame : allFrames)
                        {
                            if ((frame instanceof Viewer) || (frame instanceof TaskFrame) || !frame.isVisible())
                                continue;

                            final IcyFrame f = frame;

                            final IcyCommandMenuButton frameButton = new IcyCommandMenuButton(frame.getTitle());
                            frameButton.addActionListener(new ActionListener()
                            {
                                @Override
                                public void actionPerformed(ActionEvent e)
                                {
                                    ThreadUtil.invokeLater(new Runnable()
                                    {
                                        @Override
                                        public void run()
                                        {
                                            // remove minimized state
                                            if (f.isMinimized())
                                                f.setMinimized(false);
                                            // then grab focus
                                            f.requestFocus();
                                            f.toFront();
                                        }
                                    });
                                }
                            });
                            result.addMenuButton(frameButton);
                        }

                        return result;
                    }
                });

                // check if we have visible frame
                boolean hasVisibleFrame = false;
                for (IcyFrame frame : allFrames)
                    if (!((frame instanceof Viewer) || (frame instanceof TaskFrame) || !frame.isVisible()))
                        hasVisibleFrame = true;

                framesButton.setEnabled(hasVisibleFrame);
                result.addMenuButton(framesButton);

                return result;
            }
        });
        ribbon.addTaskbarComponent(windowsButton);

        // SEPARATOR
        ribbon.addTaskbarComponent(new JSeparator(SwingConstants.VERTICAL));

        // SEARCH BAR
        searchBar = new SearchBar();
        searchBar.setColumns(14);

        ribbon.addTaskbarComponent(searchBar);

        // HELP / INFOS
        final IcyCommandButton helpAndInfoButton = new IcyCommandButton(new IcyIcon(ResourceUtil.ICON_INFO));

        helpAndInfoButton.setPopupRichTooltip(new RichTooltip("General help and information",
                "Help, Updates and Informations about Icy."));
        helpAndInfoButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
        helpAndInfoButton.setPopupCallback(new PopupPanelCallback()
        {
            @Override
            public JPopupPanel getPopupPanel(JCommandButton commandButton)
            {
                final JCommandPopupMenu result = new JCommandPopupMenu();

                // HELP
                result.addMenuButton(new IcyCommandMenuButton(GeneralActions.onlineHelpAction));
                // WEB SITE
                result.addMenuButton(new IcyCommandMenuButton(GeneralActions.websiteAction));
                // CHECK FOR UPDATE
                result.addMenuButton(new IcyCommandMenuButton(GeneralActions.checkUpdateAction));
                // CHANGELOG
                result.addMenuButton(new IcyCommandMenuButton(GeneralActions.changeLogAction));
                // ABOUT
                result.addMenuButton(new IcyCommandMenuButton(GeneralActions.aboutAction));

                return result;
            }
        });
        ribbon.addTaskbarComponent(helpAndInfoButton);

        // LINK
        // ribbon.addTaskbarComponent(new IcyCommandButton(GeneralActions.linkAction));
    }

    private void checkPluginsMenuCoherence()
    {
        // get plugins we have to display in menu
        final ArrayList<PluginDescriptor> plugins = PluginLoader.getActionablePlugins();
        final ArrayList<ItemDefinition> items = getAllItems();
        final PluginClassNameSorter pluginsSorter = PluginClassNameSorter.instance;

        // sort plugins on classname
        Collections.sort(plugins, pluginsSorter);

        final PluginDescriptor keyPlugin = new PluginDescriptor();

        ThreadUtil.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                // find removed plugins
                for (ItemDefinition item : items)
                {
                    if (!item.isSeparator() && (item.getBandDefinition() != othersPluginsBandDef))
                    {
                        // set the className for searched element
                        keyPlugin.getIdent().setClassName(item.getClassName());

                        // not found in plugin list --> remove it
                        if (Collections.binarySearch(plugins, keyPlugin, pluginsSorter) < 0)
                            removeItem(item);
                    }
                }

                // update or add plugin button
                for (PluginDescriptor plugin : plugins)
                    updateItem(plugin, findItem(plugin.getClassName()));

                refreshOthersPluginsList();
            }
        });
    }

    @Override
    public void pluginLoaderChanged(PluginLoaderEvent e)
    {
        // update menu according to plugins change
        checkPluginsMenuCoherence();
    }

    @Override
    public void sequenceActivated(Sequence sequence)
    {
        toolRibbonTask.onSequenceActivationChange();
        ijTask.onSequenceActivationChange();
        applicationMenu.onSequenceActivationChange();
        sequenceOperationTask.onSequenceChange();
    }

    @Override
    public void sequenceDeactivated(Sequence sequence)
    {
        // nothing here
    }

    @Override
    public void activeSequenceChanged(SequenceEvent event)
    {
        final SequenceEventSourceType type = event.getSourceType();

        if ((type == SequenceEventSourceType.SEQUENCE_DATA) || (type == SequenceEventSourceType.SEQUENCE_TYPE))
            sequenceOperationTask.onSequenceChange();
        if (type == SequenceEventSourceType.SEQUENCE_ROI)
            toolRibbonTask.onSequenceChange();
    }
}