/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.actions;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.internal.runtime.Assert;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IRegistryChangeEvent;
import org.eclipse.core.runtime.IRegistryChangeListener;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;
import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.Separator;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.activities.WorkbenchActivityHelper;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.WorkbenchWindow;
import org.eclipse.ui.internal.actions.NewWizardShortcutAction;
import org.eclipse.ui.wizards.IWizardDescriptor;

/**
 * A <code>BaseNewWizardMenu</code> is used to populate a menu manager with
 * New Wizard actions for the current perspective's new wizard shortcuts,
 * including an Other... action to open the new wizard dialog.
 * 
 * @since 3.1
 */
public class BaseNewWizardMenu extends CompoundContributionItem {
    /*
     * @issue Should be possible to implement this class entirely using public
     * API. Cases to be fixed: WorkbenchPage, WorkbenchWizardElement,
     * NewWizardsRegistryReader. Suggestions:
     * - define API for accessing current perspective's wizard shortcuts from IWorkbenchPage 
     * - define API for a wizard registry and corresponding descriptors
     */

    private final Map actions = new HashMap(21);

    private final IExtensionChangeHandler configListener = new IExtensionChangeHandler() {

        /* (non-Javadoc)
         * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#removeExtension(org.eclipse.core.runtime.IExtension, java.lang.Object[])
         */
        public void removeExtension(IExtension source, Object[] objects) {
            for (int i = 0; i < objects.length; i++) {
                if (objects[i] instanceof NewWizardShortcutAction) {
                    actions.values().remove(objects[i]);
                }
            }
        }

        /* (non-Javadoc)
         * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#addExtension(org.eclipse.core.runtime.dynamicHelpers.IExtensionTracker, org.eclipse.core.runtime.IExtension)
         */
        public void addExtension(IExtensionTracker tracker, IExtension extension) {
            // Do nothing
        }
    };

    /**
     * TODO: should this be done with an addition listener?
     */
    private final IRegistryChangeListener registryListener = new IRegistryChangeListener() {

        public void registryChanged(IRegistryChangeEvent event) {
            // reset the reader.
            // TODO This is expensive.  Can we be more selective?
            if (getParent() != null) {
                getParent().markDirty();
            }
        }

    };

    private final IAction showDlgAction;

    private IWorkbenchWindow workbenchWindow;

    /**
     * Creates a new wizard shortcut menu for the IDE.
     * 
     * @param window
     *            the window containing the menu
     * @param id
     *            the contribution item identifier, or <code>null</code>
     */
    public BaseNewWizardMenu(IWorkbenchWindow window, String id) {
        super(id);
        Assert.isNotNull(window);
        this.workbenchWindow = window;
        showDlgAction = ActionFactory.NEW.create(window);
        registerListeners();
        // indicate that a new wizards submenu has been created
        ((WorkbenchWindow) window)
                .addSubmenu(WorkbenchWindow.NEW_WIZARD_SUBMENU);
    }

    /**
     * Adds the items to show to the given list.
     * 
     * @param list the list to add items to
     */
    protected void addItems(List list) {
        if (addShortcuts(list)) {
            list.add(new Separator());
        }
        list.add(new ActionContributionItem(getShowDialogAction()));
    }

    /**
     * Adds the new wizard shortcuts for the current perspective to the given list.
     * 
     * @param list the list to add items to
     * @return <code>true</code> if any items were added, <code>false</code> if none were added
     */
    protected boolean addShortcuts(List list) {
        boolean added = false;
        IWorkbenchPage page = workbenchWindow.getActivePage();
        if (page != null) {
            String[] wizardIds = page.getNewWizardShortcuts();
            for (int i = 0; i < wizardIds.length; i++) {
                IAction action = getAction(wizardIds[i]);
                if (action != null) {
                    if (!WorkbenchActivityHelper.filterItem(action)) {
                        list.add(new ActionContributionItem(action));
                        added = true;
                    }
                }
            }
        }
        return added;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.action.IContributionItem#dispose()
     */
    public void dispose() {
        if (workbenchWindow != null) {
            super.dispose();
            unregisterListeners();
            workbenchWindow = null;
        }
    }

    /*
     * (non-Javadoc) Returns the action for the given wizard id, or null if not
     * found.
     */
    private IAction getAction(String id) {
        // Keep a cache, rather than creating a new action each time,
        // so that image caching in ActionContributionItem works.
        IAction action = (IAction) actions.get(id);
        if (action == null) {
            IWizardDescriptor wizardDesc = WorkbenchPlugin.getDefault()
					.getNewWizardRegistry().findWizard(id);
            if (wizardDesc != null) {
                action = new NewWizardShortcutAction(workbenchWindow,
						wizardDesc);
				actions.put(id, action);
				IConfigurationElement element = (IConfigurationElement) wizardDesc
						.getAdapter(IConfigurationElement.class);
				if (element != null)
					workbenchWindow.getExtensionTracker().registerObject(
							element.getDeclaringExtension(), action,
							IExtensionTracker.REF_WEAK);
            }
        }
        return action;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.ui.actions.CompoundContributionItem#getContributionItems()
     */
    protected IContributionItem[] getContributionItems() {
        ArrayList list = new ArrayList();
        if (workbenchWindow != null && workbenchWindow.getActivePage() != null
                && workbenchWindow.getActivePage().getPerspective() != null) {
            addItems(list);
        } else {
            String text = WorkbenchMessages.Workbench_noApplicableItems;
            Action dummyAction = new Action(text) {
                // dummy inner class; no methods
            };
            dummyAction.setEnabled(false);
            list.add(new ActionContributionItem(dummyAction));
        }
        return (IContributionItem[]) list.toArray(new IContributionItem[list.size()]);
    }

    /**
     * Returns the "Other..." action, used to show the new wizards dialog.
     * 
     * @return the action used to show the new wizards dialog
     */
    protected IAction getShowDialogAction() {
        return showDlgAction;
    }

    /**
     * Returns the window in which this menu appears.
     * 
     * @return the window in which this menu appears
     */
    protected IWorkbenchWindow getWindow() {
        return workbenchWindow;
    }

    /**
     * Registers listeners.
     * 
     * @since 3.1
     */
    private void registerListeners() {
        Platform.getExtensionRegistry().addRegistryChangeListener(
                registryListener);
        workbenchWindow.getExtensionTracker().registerHandler(
				configListener,  null);
    }

    /**
     * Returns whether the new wizards registry has a non-empty category with
     * the given identifier.
     * 
     * @param categoryId
     *            the identifier for the category
     * @return <code>true</code> if there is a non-empty category with the
     *         given identifier, <code>false</code> otherwise
     */
    protected boolean registryHasCategory(String categoryId) {
    	return WorkbenchPlugin.getDefault().getNewWizardRegistry()
				.findCategory(categoryId) != null;
    }

    /**
     * Unregisters listeners.
     * 
     * @since 3.1
     */
    private void unregisterListeners() {
        Platform.getExtensionRegistry().removeRegistryChangeListener(
                registryListener);
        workbenchWindow.getExtensionTracker().unregisterHandler(configListener);
    }
}
