/*******************************************************************************
 * Copyright (c) 2006 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.internal.menus;

import java.util.Iterator;
import java.util.List;

import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.NotEnabledException;
import org.eclipse.core.commands.NotHandledException;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IContributionManager;
import org.eclipse.jface.action.LegacyActionTools;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.SubMenuManager;
import org.eclipse.jface.menus.IWidget;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.CoolBar;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.internal.ActionSetContributionItem;
import org.eclipse.ui.internal.WorkbenchWindow;
import org.eclipse.ui.internal.misc.Policy;

/**
 * <p>
 * A wrapper around the new command-based menu services that speaks in terms of
 * the old menu manager class.
 * </p>
 * <p>
 * This class is not intended to be used outside of the
 * <code>org.eclipse.ui.workbench</code> plug-in.
 * </p>
 * 
 * @since 3.2
 */
public class LegacyMenuManager extends MenuManager {

	/**
	 * The window on which this menu manager exists; never <code>null</code>.
	 */
	private final WorkbenchWindow fWindow;

	private IMenuService fMenuService;

	private SMenuLayout fLayout;

	/**
	 * Constructs a new instance of <code>LegacyMenuManager</code>.
	 * 
	 * @param window
	 *            The window on which this menu manager exists; must not be
	 *            <code>null</code>.
	 */
	public LegacyMenuManager(final WorkbenchWindow window) {
		if (window == null) {
			throw new NullPointerException("The window cannot be null"); //$NON-NLS-1$
		}
		this.fWindow = window;
		fMenuService = (IMenuService) fWindow.getService(IMenuService.class);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.action.MenuManager#update(boolean, boolean)
	 */
	protected void update(boolean force, boolean recursive) {
		if (isDirty()) {
			generateMenus();
			fLayout = fMenuService.getLayout();
			Menu menu = getMenu();
			if (menu.getItemCount() > 0) {
				MenuItem[] items = menu.getItems();
				for (int i = 0; i < items.length; i++) {
					items[i].dispose();
				}
			}
			SMenuBuilder builder = new SMenuBuilder(fLayout, menu, fWindow);
			builder.build();
			setDirty(false);
		}
	}

	/**
	 * 
	 */
	private void generateMenus() {
		IContributionItem[] items = getItems();
		SLocation location = new SLocation(new SBar());
		processChildren(items, location);
	}

	/**
	 * @param item
	 * @param location
	 */
	private void createMenu(IContributionItem item, SLocation location) {
		if (item == null) {
			return;
		}
		if (item instanceof ActionContributionItem) {
			addActionContribution((ActionContributionItem) item, location);
		} else if (item instanceof ActionSetContributionItem) {
			addActionSetContribution((ActionSetContributionItem) item, location);
		} else if (item instanceof MenuManager) {
			addMenu((MenuManager) item, location);
		} else if (item instanceof SubMenuManager) {
			addSubMenuManager((SubMenuManager) item, location);
		} else if (item instanceof IContributionManager) {
			addContributionManager((IContributionManager) item, location);
		} else {
			if (Policy.EXPERIMENTAL_MENU) {
				System.err.println("createMenu: unknown: " //$NON-NLS-1$
						+ item.getClass().getName());
			}
			addWidget(item, location);
		}
	}

	/**
	 * @param manager
	 * @param location
	 */
	private void addSubMenuManager(SubMenuManager manager,
			SLocation parentLocation) {
		processChildren(manager.getItems(), parentLocation);
	}

	/**
	 * @param manager
	 * @param parentLocation
	 */
	private void addContributionManager(IContributionManager manager,
			SLocation parentLocation) {
		if (Policy.EXPERIMENTAL_MENU) {
			System.err.println("addContributionManager: unknown: " //$NON-NLS-1$
					+ manager.getClass().getName());
		}

		processChildren(manager.getItems(), parentLocation);
	}

	/**
	 * @param manager
	 * @param location
	 */
	private void addMenu(MenuManager menu, SLocation parentLocation) {
		String id = normalizeId(menu);
		SMenu smenu = fMenuService.getMenu(id);
		if (smenu.isDefined()) {
			smenu.addLocation(parentLocation);
		} else {
			smenu.define(LegacyActionTools.removeMnemonics(menu.getMenuText()),
					parentLocation);
		}
		final char mnemonic = LegacyActionTools.extractMnemonic(menu
				.getMenuText());
		SLocation location = new SLocation(parentLocation, id, mnemonic);

		processChildren(menu.getItems(), location);
	}

	/**
	 * @param items
	 * @param location
	 */
	private void processChildren(IContributionItem[] items, SLocation location) {
		for (int i = 0; i < items.length; i++) {
			if (items[i].isGroupMarker()) {
				addGroup(items[i], location);
			} else if (items[i].isSeparator()) {
				addWidget(items[i], location);
			} else {
				createMenu(items[i], location);
			}
		}
	}

	/**
	 * @param item
	 * @param location
	 */
	private void addWidget(final IContributionItem item, SLocation location) {
		String id = normalizeId(item);

		SWidget swidget = fMenuService.getWidget(id);
		if (swidget.isDefined()) {
			swidget.addLocation(location);
		} else {
			swidget.define(new IWidget() {

				public void dispose() {
					item.dispose();
				}

				public void fill(Composite parent) {
					item.fill(parent);
				}

				public void fill(Menu parent, int index) {
					item.fill(parent, index);
				}

				public void fill(ToolBar parent, int index) {
					item.fill(parent, index);
				}

				public void fill(CoolBar parent, int index) {
					item.fill(parent, index);
				}

			}, location);
		}
	}

	/**
	 * @param item
	 * @param id
	 * @return the ID, or a made up one.
	 */
	private String normalizeId(final IContributionItem item) {
		String id = item.getId();
		if (id == null || id.length() < 1) {
			id = item.getClass().getName() + "@" + item.hashCode(); //$NON-NLS-1$
		}
		return id;
	}

	/**
	 * @param marker
	 * @param location
	 * @return the group location for use as a parent location
	 */
	private SLocation addGroup(IContributionItem marker, SLocation location) {
		String id = normalizeId(marker);
		return addGroup(id, marker.isSeparator(), location);
	}

	/**
	 * @param manager
	 * @param location
	 * @return the group location for use as a parent location
	 */
	protected SLocation addGroup(IContributionManager manager,
			SLocation location) {
		String id = manager.getClass().getName() + "@" + manager.hashCode(); //$NON-NLS-1$
		return addGroup(id, false, location);
	}

	/**
	 * @param id
	 * @param isSeparator
	 * @param location
	 * @return the group location for use as a parent location
	 */
	private SLocation addGroup(String id, boolean isSeparator,
			SLocation location) {
		SGroup group = fMenuService.getGroup(id);
		if (group.isDefined()) {
			group.addLocation(location);
		} else {
			group.define(isSeparator, location);
		}
		return new SLocation(location, group.getId());
	}

	/**
	 * @param actionSetContribution
	 * @param parentLocation
	 */
	private void addActionSetContribution(
			ActionSetContributionItem actionSetContribution,
			SLocation parentLocation) {
		createMenu(actionSetContribution.getInnerItem(), parentLocation);
	}

	/**
	 * @param item
	 * @param location
	 */
	private void addActionContribution(
			ActionContributionItem actionContribution, SLocation parentLocation) {
		String id = normalizeId(actionContribution);
		String commandId = actionContribution.getAction()
				.getActionDefinitionId();
		if (commandId == null && Policy.EXPERIMENTAL_MENU) {
			System.err
					.println("addActionContribution: When is a command not a command! " //$NON-NLS-1$
							+ actionContribution.getId());
			return;
		}
		SItem sitem = fMenuService.getItem(id);
		if (sitem.isDefined()) {
			sitem.addLocation(parentLocation);
		} else {
			ICommandService commandService = (ICommandService) fWindow
					.getService(ICommandService.class);
			Command c = commandService.getCommand(commandId);
			if (c.isDefined()) {
				ParameterizedCommand pc = new ParameterizedCommand(c, null);
				sitem.define(pc, actionContribution.getAction().getText(),
						parentLocation);
			} else if (Policy.EXPERIMENTAL_MENU) {
				System.err
						.println("addActionContribution: undefined command " + commandId); //$NON-NLS-1$
			}
		}
	}

	private static class SMenuBuilder {

		private static class IndexManager {
			/**
			 * 
			 */
			public int index = 0;
		}

		private SMenuLayout fLayout;

		private Menu fRootMenu;

		private WorkbenchWindow fWindow;

		/**
		 * @param rootNode
		 * @param menu
		 * @param window
		 */
		public SMenuBuilder(SMenuLayout rootNode, Menu menu,
				WorkbenchWindow window) {
			fLayout = rootNode;
			fRootMenu = menu;
			fWindow = window;
		}

		/**
		 * 
		 */
		public void build() {
			ILayoutNode root = fLayout.getMenuBar();

			List children = root.getChildrenSorted();
			IndexManager index = new IndexManager();
			for (Iterator i = children.iterator(); i.hasNext();) {
				ILayoutNode child = (ILayoutNode) i.next();
				addNode(fRootMenu, child, index);
			}
		}

		/**
		 * @param menu
		 * @param node
		 * @param index
		 */
		public void addNode(Menu menu, ILayoutNode node, IndexManager index) {
			MenuElement element = node.getMenuElement();
			if (Policy.EXPERIMENTAL_MENU
					&& node.getLocation().getPath().toString().indexOf(
							LeafLocationElement.BREAKPOINT_PATH) > -1) {
				System.err
						.println("addNode: tree: " + node.getLocation() + "\n\t" //$NON-NLS-1$ //$NON-NLS-2$
								+ element);
			}
			// System.err.println("addNode: " + element); //$NON-NLS-1$
			if (element instanceof SMenu) {
				SMenu smenu = (SMenu) element;
				if (!smenu.isVisible(fWindow)) {
					return;
				}
				MenuItem item = new MenuItem(menu, SWT.CASCADE, index.index++);
				item.setData(smenu);

				try {
					item.setText(smenu.getLabel());
				} catch (NotDefinedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				Menu itemMenu = new Menu(menu);
				item.setMenu(itemMenu);

				List children = node.getChildrenSorted();
				IndexManager childIndex = new IndexManager();
				for (Iterator i = children.iterator(); i.hasNext();) {
					ILayoutNode child = (ILayoutNode) i.next();
					addNode(itemMenu, child, childIndex);
				}

			} else if (element instanceof SItem) {
				final SItem sitem = (SItem) element;
				if (!sitem.isVisible(fWindow)) {
					return;
				}
				final MenuItem item = new MenuItem(menu, SWT.PUSH,
						index.index++);
				item.setData(element);

				try {
					item.setText(sitem.getCommand().getName());
					item.addSelectionListener(new SelectionListener() {
						public void widgetSelected(SelectionEvent e) {
							try {
								sitem.getCommand()
										.executeWithChecks(null, null);
							} catch (ExecutionException e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
							} catch (NotDefinedException e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
							} catch (NotEnabledException e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
							} catch (NotHandledException e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
							}
						}

						public void widgetDefaultSelected(SelectionEvent e) {
						}
					});
				} catch (NotDefinedException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			} else if (element instanceof SWidget) {
				SWidget swidget = (SWidget) element;
				if (!swidget.isVisible(fWindow)) {
					return;
				}
				try {
					swidget.getWidget().fill(menu, index.index);
				} catch (NotDefinedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} else if (element instanceof SGroup) {
				SGroup sgroup = (SGroup) element;
				if (!sgroup.isVisible(fWindow)) {
					return;
				}
				try {
					if (sgroup.isSeparatorsVisible()) {
						MenuItem item = new MenuItem(menu, SWT.SEPARATOR,
								index.index++);
						item.setData(sgroup);
					}

					List children = node.getChildrenSorted();
					for (Iterator i = children.iterator(); i.hasNext();) {
						ILayoutNode child = (ILayoutNode) i.next();
						addNode(menu, child, index);
					}
				} catch (NotDefinedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} else {
				if (Policy.EXPERIMENTAL_MENU) {
					System.err.println("addNode: no element for " //$NON-NLS-1$
							+ node.getLocation());
				}
				if (node.isEmpty()) {
					return;
				}

				List children = node.getChildrenSorted();

				if (!children.isEmpty()) {

					MenuItem item = new MenuItem(menu, SWT.CASCADE,
							index.index++);
					item.setData(node);

					item
							.setText(node.getId() == null ? "node" + (index.index - 1) //$NON-NLS-1$
									: node.getId());

					Menu itemMenu = new Menu(menu);
					item.setMenu(itemMenu);

					IndexManager childIndex = new IndexManager();
					for (Iterator i = children.iterator(); i.hasNext();) {
						ILayoutNode child = (ILayoutNode) i.next();
						addNode(itemMenu, child, childIndex);
					}
				}
			}
		}
	}

}
