/*******************************************************************************
 * Copyright (c) 2004, 2010 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
 *     Chris Austin (IBM) - Fix for bug 296042
 *******************************************************************************/
package org.eclipse.ui.internal.help;

import java.net.URL;
import java.util.Hashtable;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;
import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
import org.eclipse.help.HelpSystem;
import org.eclipse.help.IContext;
import org.eclipse.help.IContext2;
import org.eclipse.help.IHelp;
import org.eclipse.help.IHelpResource;
import org.eclipse.help.IToc;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.LegacyActionTools;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.HelpEvent;
import org.eclipse.swt.events.HelpListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommand;
import org.eclipse.ui.help.AbstractHelpUI;
import org.eclipse.ui.help.IContextComputer;
import org.eclipse.ui.help.IWorkbenchHelpSystem;
import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;

/**
 * This class represents a refactoring of the functionality previously contained
 * in <code>WorkbenchHelp</code>.
 * 
 * @since 3.1
 */
public final class WorkbenchHelpSystem implements IWorkbenchHelpSystem {

	/**
	 * Key used for stashing help-related data on SWT widgets.
	 * 
	 * @see org.eclipse.swt.widgets.Widget#getData(java.lang.String)
	 */
	public static final String HELP_KEY = "org.eclipse.ui.help";//$NON-NLS-1$

	/**
	 * Id of extension point where the help UI is contributed.
	 */
	private static final String HELP_SYSTEM_EXTENSION_ID = PlatformUI.PLUGIN_ID + '.' + IWorkbenchRegistryConstants.PL_HELPSUPPORT;

	/**
	 * Attribute id for class attribute of help UI extension point.
	 */
	private static final String HELP_SYSTEM_CLASS_ATTRIBUTE = "class";//$NON-NLS-1$

	/**
	 * Singleton.
	 */
	private static WorkbenchHelpSystem instance;

	/**
	 * The help listener.
	 */
	private static class WorkbenchHelpListener implements HelpListener {
		public void helpRequested(HelpEvent event) {

			if (getInstance().getHelpUI() == null) {
				return;
			}

			// get the help context from the widget
			Object object = event.widget.getData(HELP_KEY);

			// Since 2.0 we can expect that object is a String, however
			// for backward compatability we handle context computers and
			// arrays.
			IContext context = null;
			if (object instanceof String) {
				// context id - this is the norm
				context = HelpSystem.getContext((String) object);
			} else if (object instanceof IContext) {
				// already resolved context (pre 2.0)
				context = (IContext) object;
			} else if (object instanceof IContextComputer) {
				// a computed context (pre 2.0) - compute it now
				Object[] helpContexts = ((IContextComputer) object)
						.computeContexts(event);
				// extract the first entry
				if (helpContexts != null && helpContexts.length > 0) {
					Object primaryEntry = helpContexts[0];
					if (primaryEntry instanceof String) {
						context = HelpSystem.getContext((String) primaryEntry);
					} else if (primaryEntry instanceof IContext) {
						context = (IContext) primaryEntry;
					}
				}
			} else if (object instanceof Object[]) {
				// mixed array of String or IContext (pre 2.0) - extract the
				// first entry
				Object[] helpContexts = (Object[]) object;
				// extract the first entry
				if (helpContexts.length > 0) {
					Object primaryEntry = helpContexts[0];
					if (primaryEntry instanceof String) {
						context = HelpSystem.getContext((String) primaryEntry);
					} else if (primaryEntry instanceof IContext) {
						context = (IContext) primaryEntry;
					}
				}
			}
			
			/*
			 * If can't find it, show the "context is missing" context.
			 */
			if (context == null) {
				context = HelpSystem.getContext(IWorkbenchHelpContextIds.MISSING);
			}
			
			if (context != null) {
				// determine a location in the upper right corner of the
				// widget
				Point point = computePopUpLocation(event.widget.getDisplay());
				// display the help
				getInstance().displayContext(context, point.x, point.y);
			}
		}
	}

	/**
	 * Whether the help system has been initialized.
	 */
	private boolean isInitialized;

	/**
	 * Pluggable help UI, or <code>null</code> if none (or unknown).
	 */
	private AbstractHelpUI pluggableHelpUI = null;

	/**
	 * The id of the help extension that should be used. This is used only for
	 * debugging purposes.
	 */
	private String desiredHelpSystemId;

	/**
	 * Table for tracing registered context ids. This is used only for debugging
	 * purposes.
	 * 
	 */
	private Hashtable registeredIDTable;

	/**
	 * Handles dynamic removal of the help system.
	 * 
	 * @since 3.1
	 */
    /**
     * Handles dynamic removal of the help system.
     * 
     * @since 3.1
     */
    private IExtensionChangeHandler handler = new IExtensionChangeHandler() {
        
        /* (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
        }
        
        /* (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] == pluggableHelpUI) {
                    isInitialized = false;
                    pluggableHelpUI = null;
                    helpCompatibilityWrapper = null;
                    // remove ourselves - we'll be added again in initalize if
                    // needed
                    PlatformUI.getWorkbench().getExtensionTracker()
							.unregisterHandler(handler);
                }
            }
        }
    };
    
	/**
	 * Compatibility implementation of old IHelp interface.
	 * WorkbenchHelp.getHelpSupport and IHelp were deprecated in 3.0.
	 */
	private class CompatibilityIHelpImplementation implements IHelp {

		/** @deprecated */
		public void displayHelp() {
			// real method - forward to help UI if available
			AbstractHelpUI helpUI = getHelpUI();
			if (helpUI != null) {
				helpUI.displayHelp();
			}
		}

		/** @deprecated */
		public void displayContext(IContext context, int x, int y) {
			// real method - forward to help UI if available
			AbstractHelpUI helpUI = getHelpUI();
			if (helpUI != null) {
				helpUI.displayContext(context, x, y);
			}
		}

		/** @deprecated */
		public void displayContext(String contextId, int x, int y) {
			// convenience method - funnel through the real method
			IContext context = HelpSystem.getContext(contextId);
			if (context != null) {
				displayContext(context, x, y);
			}
		}

		/** @deprecated */
		public void displayHelpResource(String href) {
			// real method - forward to help UI if available
			AbstractHelpUI helpUI = getHelpUI();
			if (helpUI != null) {
				helpUI.displayHelpResource(href);
			}
		}

		/** @deprecated */
		public void displayHelpResource(IHelpResource helpResource) {
			// convenience method - funnel through the real method
			displayHelpResource(helpResource.getHref());
		}

		/** @deprecated */
		public void displayHelp(String toc) {
			// deprecated method - funnel through the real method
			displayHelpResource(toc);
		}

		/** @deprecated */
		public void displayHelp(String toc, String selectedTopic) {
			// deprecated method - funnel through the real method
			displayHelpResource(selectedTopic);
		}

		/** @deprecated */
		public void displayHelp(String contextId, int x, int y) {
			// deprecated method - funnel through the real method
			displayContext(contextId, x, y);
		}

		/** @deprecated */
		public void displayHelp(IContext context, int x, int y) {
			// deprecated method - funnel through the real method
			displayContext(context, x, y);
		}

		/** @deprecated */
		public IContext getContext(String contextId) {
			// non-UI method - forward to HelpSystem
			return HelpSystem.getContext(contextId);
		}

		/** @deprecated */
		public IToc[] getTocs() {
			// non-UI method - forward to HelpSystem
			return HelpSystem.getTocs();
		}

		/** @deprecated */
		public boolean isContextHelpDisplayed() {
			// real method - forward to pluggedhelp UI
			return isContextHelpDisplayed();
		}
	}

	/**
	 * A wrapper for action help context that passes the action
	 * text to be used as a title. 
	 * @since 3.1
	 */
	private static class ContextWithTitle implements IContext2 {
		private IContext context;
		private String title;

		ContextWithTitle(IContext context, String title) {
			this.context = context;
			this.title = title;
		}

		public String getTitle() {
			if (context instanceof IContext2) {
				String ctitle = ((IContext2)context).getTitle();
				if (ctitle!=null) {
					return ctitle;
				}
			}
			return title;
		}

		public String getStyledText() {
			if (context instanceof IContext2) {
				return ((IContext2)context).getStyledText();
			}
			return context.getText();
		}

		public String getCategory(IHelpResource topic) {
			if (context instanceof IContext2) {
				return ((IContext2)context).getCategory(topic);
			}
			return null;
		}

		public IHelpResource[] getRelatedTopics() {
			return context.getRelatedTopics();
		}

		public String getText() {
			return context.getText();
		}
	}
	
	/**
	 * Compatibility wrapper, or <code>null</code> if none. Do not access
	 * directly; see getHelpSupport().
	 */
	private IHelp helpCompatibilityWrapper = null;

	/**
	 * The listener to attach to various widgets.
	 */
	private static HelpListener helpListener;

	/**
	 * For debug purposes only.  
	 * 
	 * @return the desired help system id
	 */
	public String getDesiredHelpSystemId() {
		return desiredHelpSystemId;
	}
	
	/**
	 * For debug purposes only.
	 * 
	 * @param desiredHelpSystemId the desired help system id
	 */
	public void setDesiredHelpSystemId(String desiredHelpSystemId) {
		dispose(); // prep for a new help system
		this.desiredHelpSystemId = desiredHelpSystemId;
	}
	
	/**
	 * Singleton Constructor.
	 */
	private WorkbenchHelpSystem() {
	}

	/**
	 * Return the singleton instance of this class.
	 * 
	 * @return the singleton instance
	 */
	public static WorkbenchHelpSystem getInstance() {
		if (instance == null) {
			instance = new WorkbenchHelpSystem();
		}

		return instance;
	}

	/**
	 * Disposed of the singleton of this class if it has been created.
	 */
	public static void disposeIfNecessary() {
		if (instance != null) {
			instance.dispose();
			instance = null;
		}
	}

	/**
	 * Dispose of any resources allocated by this instance.
	 */
	public void dispose() {
		pluggableHelpUI = null;
		helpCompatibilityWrapper = null;
		isInitialized = false;
		PlatformUI.getWorkbench().getExtensionTracker()
				.unregisterHandler(handler);
	}

	/**
	 * Returns the help UI for the platform, if available. This method will
	 * initialize the help UI if necessary.
	 * 
	 * @return the help UI, or <code>null</code> if none
	 */
	private AbstractHelpUI getHelpUI() {
		if (!isInitialized) {
			isInitialized = initializePluggableHelpUI();
		}
		return pluggableHelpUI;
	}

	/**
	 * Initializes the pluggable help UI by getting an instance via the
	 * extension point.
	 */
	private boolean initializePluggableHelpUI() {
		final boolean[] ret = new boolean[] { false };

		BusyIndicator.showWhile(Display.getCurrent(), new Runnable() {

			/*
			 * (non-Javadoc)
			 * 
			 * @see java.lang.Runnable#run()
			 */
			public void run() {
				// get the help UI extension from the registry
				IExtensionPoint point = Platform.getExtensionRegistry()
						.getExtensionPoint(HELP_SYSTEM_EXTENSION_ID);
				if (point == null) {
					// our extension point is missing (!) - act like there was
					// no help UI
					return;
				}
				IExtension[] extensions = point.getExtensions();
				if (extensions.length == 0) {
					// no help UI present
					return;
				}

				IConfigurationElement elementToUse = null;
				if (desiredHelpSystemId == null) {
					elementToUse = getFirstElement(extensions);
				} else {
					elementToUse = findElement(desiredHelpSystemId, extensions);
				}

				if (elementToUse != null) {
					ret[0] = initializePluggableHelpUI(elementToUse);
				}
			}

			private IConfigurationElement findElement(
					String desiredHelpSystemId, IExtension[] extensions) {
				for (int i = 0; i < extensions.length; i++) {
					IExtension extension = extensions[i];
					if (desiredHelpSystemId.equals(extension.getUniqueIdentifier())) {
						IConfigurationElement[] elements = extension
								.getConfigurationElements();
						if (elements.length == 0) {
							// help UI present but mangled - act like there was
							// no help
							// UI
							return null;
						}
						return elements[0];
					}

				}
				return null;
			}

			private IConfigurationElement getFirstElement(
					IExtension[] extensions) {
				// There should only be one extension/config element so we just
				// take the first
				IConfigurationElement[] elements = extensions[0]
						.getConfigurationElements();
				if (elements.length == 0) {
					// help UI present but mangled - act like there was no help
					// UI
					return null;
				}
				return elements[0];
			}

			private boolean initializePluggableHelpUI(
					IConfigurationElement element) {
				// Instantiate the help UI
				try {
					pluggableHelpUI = (AbstractHelpUI) WorkbenchPlugin
							.createExtension(element,
									HELP_SYSTEM_CLASS_ATTRIBUTE);
					// start listening for removals
					PlatformUI.getWorkbench().getExtensionTracker()
							.registerHandler(handler, null);
					// register the new help UI for removal notification
					PlatformUI
							.getWorkbench()
							.getExtensionTracker()
							.registerObject(element.getDeclaringExtension(),
									pluggableHelpUI, IExtensionTracker.REF_WEAK);
					return true;
				} catch (CoreException e) {
					WorkbenchPlugin.log(
							"Unable to instantiate help UI" + e.getStatus(), e);//$NON-NLS-1$
				}
				return false;
			}

		});
		return ret[0];
	}

	/**
	 * Determines the location for the help popup shell given the widget which
	 * orginated the request for help.
	 * 
	 * @param display
	 *            the display where the help will appear
	 */
	private static Point computePopUpLocation(Display display) {
		Point point = display.getCursorLocation();
		return new Point(point.x + 15, point.y);
	}

	/**
	 * Returns the help listener which activates the help support system.
	 * 
	 * @return the help listener
	 */
	private HelpListener getHelpListener() {
		if (helpListener == null) {
			helpListener = new WorkbenchHelpListener();
		}
		return helpListener;
	}

	/**
	 * Returns the help support system for the platform, if available.
	 * 
	 * @return the help support system, or <code>null</code> if none
	 * @deprecated Use the static methods on this class and on
	 *             {@link org.eclipse.help.HelpSystem HelpSystem}instead of the
	 *             IHelp methods on the object returned by this method.
	 */
	public IHelp getHelpSupport() {
		AbstractHelpUI helpUI = getHelpUI();
		if (helpUI != null && helpCompatibilityWrapper == null) {
			// create instance only once, and only if needed
			helpCompatibilityWrapper = new CompatibilityIHelpImplementation();
		}
		return helpCompatibilityWrapper;

	}

	/**
	 * Sets the given help contexts on the given action.
	 * <p>
	 * Use this method when the list of help contexts is known in advance. Help
	 * contexts can either supplied as a static list, or calculated with a
	 * context computer (but not both).
	 * </p>
	 * 
	 * @param action
	 *            the action on which to register the computer
	 * @param contexts
	 *            the contexts to use when F1 help is invoked; a mixed-type
	 *            array of context ids (type <code>String</code>) and/or help
	 *            contexts (type <code>IContext</code>)
	 * @deprecated use setHelp with a single context id parameter
	 */
	public void setHelp(IAction action, final Object[] contexts) {
		for (int i = 0; i < contexts.length; i++) {
			Assert.isTrue(contexts[i] instanceof String
					|| contexts[i] instanceof IContext);
		}
		action.setHelpListener(new HelpListener() {
			public void helpRequested(HelpEvent event) {
				if (contexts != null && contexts.length > 0
						&& getHelpUI() != null) {
					// determine the context
					IContext context = null;
					if (contexts[0] instanceof String) {
						context = HelpSystem.getContext((String) contexts[0]);
					} else if (contexts[0] instanceof IContext) {
						context = (IContext) contexts[0];
					}
					if (context != null) {
						Point point = computePopUpLocation(event.widget
								.getDisplay());
						displayContext(context, point.x, point.y);
					}
				}
			}
		});
	}

	/**
	 * Sets the given help context computer on the given action.
	 * <p>
	 * Use this method when the help contexts cannot be computed in advance.
	 * Help contexts can either supplied as a static list, or calculated with a
	 * context computer (but not both).
	 * </p>
	 * 
	 * @param action
	 *            the action on which to register the computer
	 * @param computer
	 *            the computer to determine the help contexts for the control
	 *            when F1 help is invoked
	 * @deprecated context computers are no longer supported, clients should
	 *             implement their own help listener
	 */
	public void setHelp(IAction action, final IContextComputer computer) {
		action.setHelpListener(new HelpListener() {
			public void helpRequested(HelpEvent event) {
				Object[] helpContexts = computer.computeContexts(event);
				if (helpContexts != null && helpContexts.length > 0
						&& getHelpUI() != null) {
					// determine the context
					IContext context = null;
					if (helpContexts[0] instanceof String) {
						context = HelpSystem
								.getContext((String) helpContexts[0]);
					} else if (helpContexts[0] instanceof IContext) {
						context = (IContext) helpContexts[0];
					}
					if (context != null) {
						Point point = computePopUpLocation(event.widget
								.getDisplay());
						displayContext(context, point.x, point.y);
					}
				}
			}
		});
	}

	/**
	 * Sets the given help contexts on the given control.
	 * <p>
	 * Use this method when the list of help contexts is known in advance. Help
	 * contexts can either supplied as a static list, or calculated with a
	 * context computer (but not both).
	 * </p>
	 * 
	 * @param control
	 *            the control on which to register the contexts
	 * @param contexts
	 *            the contexts to use when F1 help is invoked; a mixed-type
	 *            array of context ids (type <code>String</code>) and/or help
	 *            contexts (type <code>IContext</code>)
	 * @deprecated use setHelp with single context id parameter
	 */
	public void setHelp(Control control, Object[] contexts) {
		for (int i = 0; i < contexts.length; i++) {
			Assert.isTrue(contexts[i] instanceof String
					|| contexts[i] instanceof IContext);
		}

		control.setData(HELP_KEY, contexts);
		// ensure that the listener is only registered once
		control.removeHelpListener(getHelpListener());
		control.addHelpListener(getHelpListener());
	}

	/**
	 * Sets the given help context computer on the given control.
	 * <p>
	 * Use this method when the help contexts cannot be computed in advance.
	 * Help contexts can either supplied as a static list, or calculated with a
	 * context computer (but not both).
	 * </p>
	 * 
	 * @param control
	 *            the control on which to register the computer
	 * @param computer
	 *            the computer to determine the help contexts for the control
	 *            when F1 help is invoked
	 * @deprecated context computers are no longer supported, clients should
	 *             implement their own help listener
	 */
	public void setHelp(Control control, IContextComputer computer) {
		control.setData(HELP_KEY, computer);
		// ensure that the listener is only registered once
		control.removeHelpListener(getHelpListener());
		control.addHelpListener(getHelpListener());
	}

	/**
	 * Sets the given help contexts on the given menu.
	 * <p>
	 * Use this method when the list of help contexts is known in advance. Help
	 * contexts can either supplied as a static list, or calculated with a
	 * context computer (but not both).
	 * </p>
	 * 
	 * @param menu
	 *            the menu on which to register the context
	 * @param contexts
	 *            the contexts to use when F1 help is invoked; a mixed-type
	 *            array of context ids (type <code>String</code>) and/or help
	 *            contexts (type <code>IContext</code>)
	 * @deprecated use setHelp with single context id parameter
	 */
	public void setHelp(Menu menu, Object[] contexts) {
		for (int i = 0; i < contexts.length; i++) {
			Assert.isTrue(contexts[i] instanceof String
					|| contexts[i] instanceof IContext);
		}
		menu.setData(HELP_KEY, contexts);
		// ensure that the listener is only registered once
		menu.removeHelpListener(getHelpListener());
		menu.addHelpListener(getHelpListener());
	}

	/**
	 * Sets the given help context computer on the given menu.
	 * <p>
	 * Use this method when the help contexts cannot be computed in advance.
	 * Help contexts can either supplied as a static list, or calculated with a
	 * context computer (but not both).
	 * </p>
	 * 
	 * @param menu
	 *            the menu on which to register the computer
	 * @param computer
	 *            the computer to determine the help contexts for the control
	 *            when F1 help is invoked
	 * @deprecated context computers are no longer supported, clients should
	 *             implement their own help listener
	 */
	public void setHelp(Menu menu, IContextComputer computer) {
		menu.setData(HELP_KEY, computer);
		// ensure that the listener is only registered once
		menu.removeHelpListener(getHelpListener());
		menu.addHelpListener(getHelpListener());
	}

	/**
	 * Sets the given help contexts on the given menu item.
	 * <p>
	 * Use this method when the list of help contexts is known in advance. Help
	 * contexts can either supplied as a static list, or calculated with a
	 * context computer (but not both).
	 * </p>
	 * 
	 * @param item
	 *            the menu item on which to register the context
	 * @param contexts
	 *            the contexts to use when F1 help is invoked; a mixed-type
	 *            array of context ids (type <code>String</code>) and/or help
	 *            contexts (type <code>IContext</code>)
	 * @deprecated use setHelp with single context id parameter
	 */
	public void setHelp(MenuItem item, Object[] contexts) {
		for (int i = 0; i < contexts.length; i++) {
			Assert.isTrue(contexts[i] instanceof String
					|| contexts[i] instanceof IContext);
		}
		item.setData(HELP_KEY, contexts);
		// ensure that the listener is only registered once
		item.removeHelpListener(getHelpListener());
		item.addHelpListener(getHelpListener());
	}

	/**
	 * Sets the given help context computer on the given menu item.
	 * <p>
	 * Use this method when the help contexts cannot be computed in advance.
	 * Help contexts can either supplied as a static list, or calculated with a
	 * context computer (but not both).
	 * </p>
	 * 
	 * @param item
	 *            the menu item on which to register the computer
	 * @param computer
	 *            the computer to determine the help contexts for the control
	 *            when F1 help is invoked
	 * @deprecated context computers are no longer supported, clients should
	 *             implement their own help listener
	 */
	public void setHelp(MenuItem item, IContextComputer computer) {
		item.setData(HELP_KEY, computer);
		// ensure that the listener is only registered once
		item.removeHelpListener(getHelpListener());
		item.addHelpListener(getHelpListener());
	}
	
    /**
     * Creates a new help listener for the given command. This retrieves the
     * help context ID from the command, and creates an appropriate listener
     * based on this.
     * 
     * @param command
     *            The command for which the listener should be created; must
     *            not be <code>null</code>.
     * @return A help listener; never <code>null</code>.
     */
	public HelpListener createHelpListener(ICommand command) {
		// TODO Need a help ID from the context
		// final String contextId = command.getHelpId();
		final String contextId = ""; //$NON-NLS-1$
		return new HelpListener() {
			public void helpRequested(HelpEvent event) {
				if (getHelpUI() != null) {
					IContext context = HelpSystem.getContext(contextId);
					if (context != null) {
						Point point = computePopUpLocation(event.widget
								.getDisplay());
						displayContext(context, point.x, point.y);
					}
				}
			}
		};
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#displayHelp()
	 */
	public void displayHelp() {
		AbstractHelpUI helpUI = getHelpUI();
		if (helpUI != null) {
			helpUI.displayHelp();
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#displaySearch()
	 */
	public void displaySearch() {
		AbstractHelpUI helpUI = getHelpUI();
		if (helpUI != null) {
			helpUI.displaySearch();
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#displaySearch()
	 */
	public void displayDynamicHelp() {
		AbstractHelpUI helpUI = getHelpUI();
		if (helpUI != null) {
			helpUI.displayDynamicHelp();
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#search(java.lang.String)
	 */
	public void search(String expression) {
		AbstractHelpUI helpUI = getHelpUI();
		if (helpUI != null) {
			helpUI.search(expression);
		}
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#resolve(java.lang.String, boolean)
	 */
	public URL resolve(String href, boolean documentOnly) {
		AbstractHelpUI helpUI = getHelpUI();
		if (helpUI != null) {
			return helpUI.resolve(href, documentOnly);
		}
		return null;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#displayContext(org.eclipse.help.IContext,
	 *      int, int)
	 */
	public void displayContext(IContext context, int x, int y) {
		if (context == null) {
			throw new IllegalArgumentException();
		}
		AbstractHelpUI helpUI = getHelpUI();
		if (helpUI != null) {
			helpUI.displayContext(context, x, y);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#displayHelpResource(java.lang.String)
	 */
	public void displayHelpResource(String href) {
		if (href == null) {
			throw new IllegalArgumentException();
		}
		AbstractHelpUI helpUI = getHelpUI();
		if (helpUI != null) {
			helpUI.displayHelpResource(href);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#displayHelp(java.lang.String)
	 */
	public void displayHelp(String contextId) {
		IContext context = HelpSystem.getContext(contextId);
		if (context != null) {
			Point point = computePopUpLocation(Display.getCurrent());
			displayContext(context, point.x, point.y);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#displayHelp(org.eclipse.help.IContext)
	 */
	public void displayHelp(IContext context) {
		Point point = computePopUpLocation(Display.getCurrent());
		AbstractHelpUI helpUI = getHelpUI();
		if (helpUI != null) {
			helpUI.displayContext(context, point.x, point.y);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#isContextHelpDisplayed()
	 */
	public boolean isContextHelpDisplayed() {
		if (!isInitialized) {
			return false;
		}
		AbstractHelpUI helpUI = getHelpUI();
		return helpUI != null && helpUI.isContextHelpDisplayed();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#setHelp(org.eclipse.jface.action.IAction,
	 *      java.lang.String)
	 */
	public void setHelp(final IAction action, final String contextId) {
		if (WorkbenchPlugin.DEBUG)
			setHelpTrace(contextId);
		action.setHelpListener(new HelpListener() {
			public void helpRequested(HelpEvent event) {
				if (getHelpUI() != null) {
					IContext context = HelpSystem.getContext(contextId);
					if (context != null) {
						Point point = computePopUpLocation(event.widget
								.getDisplay());
						String title = LegacyActionTools.removeMnemonics(action.getText());
						displayContext(new ContextWithTitle(context, title), point.x, point.y);
					}
				}
			}
		});
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#setHelp(org.eclipse.swt.widgets.Control,
	 *      java.lang.String)
	 */
	public void setHelp(Control control, String contextId) {
		if (WorkbenchPlugin.DEBUG)
			setHelpTrace(contextId);
		control.setData(HELP_KEY, contextId);
		// ensure that the listener is only registered once
		control.removeHelpListener(getHelpListener());
		control.addHelpListener(getHelpListener());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#setHelp(org.eclipse.swt.widgets.Menu,
	 *      java.lang.String)
	 */
	public void setHelp(Menu menu, String contextId) {
		if (WorkbenchPlugin.DEBUG)
			setHelpTrace(contextId);
		menu.setData(HELP_KEY, contextId);
		// ensure that the listener is only registered once
		menu.removeHelpListener(getHelpListener());
		menu.addHelpListener(getHelpListener());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#setHelp(org.eclipse.swt.widgets.MenuItem,
	 *      java.lang.String)
	 */
	public void setHelp(MenuItem item, String contextId) {

		if (WorkbenchPlugin.DEBUG)
			setHelpTrace(contextId);

		item.setData(HELP_KEY, contextId);
		// ensure that the listener is only registered once
		item.removeHelpListener(getHelpListener());
		item.addHelpListener(getHelpListener());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * Traces all calls to the setHelp method in an attempt to find and report
	 * duplicated context IDs.
	 */
	private void setHelpTrace(String contextId) {
		// Create an unthrown exception to capture the stack trace
		RuntimeException e = new RuntimeException();
		StackTraceElement[] stackTrace = e.getStackTrace();
		StackTraceElement currentElement = null;
		for (int s = 0; s < stackTrace.length; s++) {
			if (stackTrace[s].getMethodName().equals("setHelp") && s + 1 < stackTrace.length) //$NON-NLS-1$
			{
				currentElement = stackTrace[s + 1];
				break;
			}
		}

		if (registeredIDTable == null)
			registeredIDTable = new Hashtable();

		if (!registeredIDTable.containsKey(contextId))
			registeredIDTable.put(contextId, currentElement);
		else if (!registeredIDTable.get(contextId).equals(currentElement)) {
			StackTraceElement initialElement = (StackTraceElement) registeredIDTable
					.get(contextId);
			String error = "UI Duplicate Context ID found: '" + contextId + "'\n" + //$NON-NLS-1$ //$NON-NLS-2$
					" 1 at " + initialElement + '\n' + //$NON-NLS-1$
					" 2 at " + currentElement; //$NON-NLS-1$

			System.out.println(error);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.help.IWorkbenchHelpSystem#hasHelpUI()
	 */
	public boolean hasHelpUI() {
		return getHelpUI() != null;
	}
}
