/*******************************************************************************
 * Copyright (c) 2001, 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
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     
 *******************************************************************************/
package org.eclipse.wst.sse.ui.internal.extension;



import com.ibm.icu.util.StringTokenizer;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
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.swt.custom.BusyIndicator;
import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.sse.ui.internal.IActionValidator;
import org.eclipse.wst.sse.ui.internal.Logger;
import org.osgi.framework.Bundle;


/**
 * When 'action' tag is found in the registry, an object of this class is
 * created. It creates the appropriate action object and captures information
 * that is later used to add this action object into menu/tool bar. This class
 * is reused for global (workbench) menu/tool bar, popup menu actions, as well
 * as view's pulldown and local tool bar.
 */
public class ActionDescriptor {
	public static final String ATT_ACCELERATOR = "accelerator"; //$NON-NLS-1$
	public static final String ATT_CLASS = "class"; //$NON-NLS-1$
	public static final String ATT_DEFINITION_ID = "definitionId"; //$NON-NLS-1$
	public static final String ATT_DESCRIPTION = "description"; //$NON-NLS-1$
	public static final String ATT_DISABLEDICON = "disabledIcon"; //$NON-NLS-1$
	public static final String ATT_HELP_CONTEXT_ID = "helpContextId"; //$NON-NLS-1$
	public static final String ATT_HOVERICON = "hoverIcon"; //$NON-NLS-1$
	public static final String ATT_ICON = "icon"; //$NON-NLS-1$

	public static final String ATT_ID = "id"; //$NON-NLS-1$
	public static final String ATT_LABEL = "label"; //$NON-NLS-1$
	public static final String ATT_MENUBAR_PATH = "menubarPath"; //$NON-NLS-1$
	public static final String ATT_POPUPMENU_PATH = "popupmenuPath"; //$NON-NLS-1$
	public static final String ATT_STATE = "state"; //$NON-NLS-1$
	public static final String ATT_TOOLBAR_PATH = "toolbarPath"; //$NON-NLS-1$
	public static final String ATT_TOOLTIP = "tooltip"; //$NON-NLS-1$

	/**
	 * Creates an extension. If the extension plugin has not been loaded a
	 * busy cursor will be activated during the duration of the load.
	 * 
	 * @param element
	 *            the config element defining the extension
	 * @param classAttribute
	 *            the name of the attribute carrying the class
	 * @returns the extension object if successful. If an error occurs when
	 *          createing executable extension, the exception is logged, and
	 *          null returned.
	 */
	public static Object createExtension(final IConfigurationElement element, final String classAttribute) {
		final Object[] result = new Object[1];
		// If plugin has been loaded create extension.
		// Otherwise, show busy cursor then create extension.
		String pluginId = element.getDeclaringExtension().getNamespace();
		Bundle bundle = Platform.getBundle(pluginId);
		if (bundle.getState() == Bundle.ACTIVE) {
			try {
				result[0] = element.createExecutableExtension(classAttribute);
			} catch (Exception e) {
				// catch and log ANY exception from extension point
				handleCreateExecutableException(result, e);
			}
		} else {
			BusyIndicator.showWhile(null, new Runnable() {
				public void run() {
					try {
						result[0] = element.createExecutableExtension(classAttribute);
					} catch (Exception e) {
						// catch and log ANY exception from extension point
						handleCreateExecutableException(result, e);
					}
				}
			});
		}
		return result[0];
	}

	private static void handleCreateExecutableException(final Object[] result, Throwable e) {
		Logger.logException(e);
		result[0] = null;
	}

	private String id;

	private ActionContributionItem item;
	private String menuGroup;
	private String menuPath;
	private String popupmenuGroup;
	private String popupmenuPath;
	private String toolbarGroup;
	private String toolbarPath;

	/**
	 * Creates a new descriptor with the targetType
	 */
	public ActionDescriptor(IConfigurationElement actionElement) throws CoreException {

		// Calculate menu and toolbar paths.
		String mpath = actionElement.getAttribute(ATT_MENUBAR_PATH);
		String mgroup = null;
		if (mpath != null) {
			int loc = mpath.lastIndexOf('/');
			if (loc != -1) {
				mgroup = mpath.substring(loc + 1);
				mpath = mpath.substring(0, loc);
			} else {
				mgroup = mpath;
				mpath = null;
			}
		}
		menuPath = mpath;
		menuGroup = mgroup;

		String ppath = actionElement.getAttribute(ATT_POPUPMENU_PATH);
		String pgroup = null;
		if (ppath != null) {
			int loc = ppath.lastIndexOf('/');
			if (loc != -1) {
				pgroup = ppath.substring(loc + 1);
				ppath = ppath.substring(0, loc);
			} else {
				pgroup = ppath;
				ppath = null;
			}
		}
		popupmenuPath = ppath;
		popupmenuGroup = pgroup;

		String tpath = actionElement.getAttribute(ATT_TOOLBAR_PATH);
		String tgroup = null;
		if (tpath != null) {
			int loc = tpath.lastIndexOf('/');
			if (loc != -1) {
				tgroup = tpath.substring(loc + 1);
				tpath = tpath.substring(0, loc);
			} else {
				tgroup = tpath;
				tpath = null;
			}
		}
		toolbarPath = tpath;
		toolbarGroup = tgroup;

		// Create action.
		IAction action = createAction(actionElement);
		if (action == null)
			return;

		String label = actionElement.getAttribute(ATT_LABEL);
		if (label != null)
			action.setText(label);

		id = actionElement.getAttribute(ATT_ID);
		if (id == null) {
			id = actionElement.getAttribute(ATT_CLASS);
		}
		if (id != null)
			action.setId(id);

		String defId = actionElement.getAttribute(ATT_DEFINITION_ID);
		if (defId != null && defId.length() != 0) {
			action.setActionDefinitionId(defId);
		}

		String tooltip = actionElement.getAttribute(ATT_TOOLTIP);
		if (tooltip != null)
			action.setToolTipText(tooltip);

		String helpContextId = actionElement.getAttribute(ATT_HELP_CONTEXT_ID);
		if (helpContextId != null) {
			String fullID = helpContextId;
			if (helpContextId.indexOf(".") == -1) //$NON-NLS-1$
				// For backward compatibility we auto qualify the id if it is
				// not qualified)
				fullID = actionElement.getDeclaringExtension().getNamespace() + "." + helpContextId; //$NON-NLS-1$
			PlatformUI.getWorkbench().getHelpSystem().setHelp(action, fullID);
		}

		String description = actionElement.getAttribute(ATT_DESCRIPTION);
		if (description != null)
			action.setDescription(description);

		String state = actionElement.getAttribute(ATT_STATE);
		if (state != null) {
			action.setChecked(state.equals("true")); //$NON-NLS-1$
		}

		String icon = actionElement.getAttribute(ATT_ICON);
		if (icon != null) {
			action.setImageDescriptor(ImageUtil.getImageDescriptorFromExtension(actionElement.getDeclaringExtension(), icon));
		}

		String hoverIcon = actionElement.getAttribute(ATT_HOVERICON);
		if (hoverIcon != null) {
			action.setHoverImageDescriptor(ImageUtil.getImageDescriptorFromExtension(actionElement.getDeclaringExtension(), hoverIcon));
		}

		String disabledIcon = actionElement.getAttribute(ATT_DISABLEDICON);
		if (disabledIcon != null) {
			action.setDisabledImageDescriptor(ImageUtil.getImageDescriptorFromExtension(actionElement.getDeclaringExtension(), disabledIcon));
		}

		String accelerator = actionElement.getAttribute(ATT_ACCELERATOR);
		if (accelerator != null)
			processAccelerator(action, accelerator);

		item = new ActionContributionItem(action);
	}

	/**
	 * Parses the given accelerator text, and converts it to an accelerator
	 * key code.
	 * 
	 * @param acceleratorText
	 *            the accelerator text
	 * @result the SWT key code, or 0 if there is no accelerator
	 */
	private int convertAccelerator(String acceleratorText) {
		int accelerator = 0;
		StringTokenizer stok = new StringTokenizer(acceleratorText, "+"); //$NON-NLS-1$

		int keyCode = -1;

		boolean hasMoreTokens = stok.hasMoreTokens();
		while (hasMoreTokens) {
			String token = stok.nextToken();
			hasMoreTokens = stok.hasMoreTokens();
			// Every token except the last must be one of the modifiers
			// Ctrl, Shift, or Alt.
			if (hasMoreTokens) {
				int modifier = Action.findModifier(token);
				if (modifier != 0) {
					accelerator |= modifier;
				} else { //Leave if there are none
					return 0;
				}
			} else {
				keyCode = Action.findKeyCode(token);
			}
		}
		if (keyCode != -1) {
			accelerator |= keyCode;
		}
		return accelerator;
	}

	/**
	 */
	private IAction createAction(IConfigurationElement actionElement) {
		Object action = new ExtendedEditorActionProxyForDelayLoading(actionElement, ATT_CLASS);
		if (action == null)
			return null;
		if (action instanceof IActionValidator) {
			if (!((IActionValidator) action).isValidAction())
				return null;
		}
		return (action instanceof IAction ? (IAction) ExtendedEditorActionProxy.newInstance(action) : null);
	}

	/**
	 * Returns the action object held in this descriptor.
	 */
	public IAction getAction() {
		return (item != null ? item.getAction() : null);
	}

	/**
	 * Returns the IContributionItem object held in this descriptor.
	 */
	public IContributionItem getContributionItem() {
		return item;
	}

	/**
	 * Returns action's id as defined in the registry.
	 */
	public String getId() {
		return id;
	}

	/**
	 * Returns named slot (group) in the menu where this action should be
	 * added.
	 */
	public String getMenuGroup() {
		return menuGroup;
	}

	/**
	 * Returns menu path where this action should be added. If null, the
	 * action will not be added into the menu.
	 */

	public String getMenuPath() {
		return menuPath;
	}

	/**
	 * Returns named slot (group) in the popup menu where this action should
	 * be added.
	 */
	public String getPopupMenuGroup() {
		return popupmenuGroup;
	}

	/**
	 * Returns popup menu path where this action should be added. If null, the
	 * action will not be added into the popup menu.
	 */

	public String getPopupMenuPath() {
		return popupmenuPath;
	}

	/**
	 * Returns the named slot (group) in the tool bar where this action should
	 * be added.
	 */

	public String getToolbarGroup() {
		return toolbarGroup;
	}

	/**
	 * Returns path in the tool bar where this action should be added. If
	 * null, action will not be added to the tool bar.
	 */
	public String getToolbarPath() {
		return toolbarPath;
	}

	/**
	 * Process the accelerator definition. If it is a number then process the
	 * code directly - if not then parse it and create the code
	 */
	private void processAccelerator(IAction action, String acceleratorText) {

		if (acceleratorText.length() == 0)
			return;

		//Is it a numeric definition?
		if (Character.isDigit(acceleratorText.charAt(0))) {
			try {
				action.setAccelerator(Integer.valueOf(acceleratorText).intValue());
			} catch (NumberFormatException exception) {
				Logger.log(Logger.ERROR, "Invalid accelerator declaration: " + id); //$NON-NLS-1$
			}
		} else
			action.setAccelerator(convertAccelerator(acceleratorText));
	}

	/**
	 * For debugging only.
	 */
	public String toString() {
		return "ActionDescriptor(" + id + ")"; //$NON-NLS-2$//$NON-NLS-1$
	}
}
