/*******************************************************************************
 * 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.editors.text.templates;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;

import org.eclipse.jface.text.Assert;
import org.eclipse.jface.text.templates.ContextTypeRegistry;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateVariableResolver;

import org.eclipse.ui.editors.text.EditorsUI;

import org.eclipse.ui.internal.editors.text.EditorsPlugin;


/**
 * A registry for context types. Editor implementors will usually instantiate a
 * registry and configure the context types available in their editor.
 * <code>ContextType</code>s can be added either directly using
 * {@link #addContextType(TemplateContextType)} or by instantiating and adding a
 * contributed context type using {@link #addContextType(String)}.
 *
 * @since 3.0
 */
public class ContributionContextTypeRegistry extends ContextTypeRegistry {

	/* extension point string literals */
	private static final String TEMPLATES_EXTENSION_POINT= "org.eclipse.ui.editors.templates"; //$NON-NLS-1$

	private static final String CONTEXT_TYPE= "contextType"; //$NON-NLS-1$
	private static final String ID= "id"; //$NON-NLS-1$
	private static final String NAME= "name"; //$NON-NLS-1$
	private static final String CLASS= "class"; //$NON-NLS-1$

	private static final String RESOLVER= "resolver"; //$NON-NLS-1$
	private static final String CONTEXT_TYPE_ID= "contextTypeId"; //$NON-NLS-1$
	private static final String DESCRIPTION= "description"; //$NON-NLS-1$
	private static final String TYPE= "type"; //$NON-NLS-1$

	/**
	 * Tries to create a context type given an id. If there is already a context
	 * type registered under the given id, nothing happens. Otherwise,
	 * contributions to the <code>org.eclipse.ui.editors.templates</code>
	 * extension point are searched for the given identifier and the specified
	 * context type instantiated if it is found.
	 *
	 * @param id the id for the context type as specified in XML
	 */
	public void addContextType(String id) {
		Assert.isNotNull(id);
		if (getContextType(id) != null)
			return;

		TemplateContextType type= createContextType(id);
		if (type != null)
			addContextType(type);

	}

	/**
	 * Tries to create a context type given an id. Contributions to the
	 * <code>org.eclipse.ui.editors.templates</code> extension point are
	 * searched for the given identifier and the specified context type
	 * instantiated if it is found. Any contributed
	 * {@link org.eclipse.jface.text.templates.TemplateVariableResolver}s
	 * are also instantiated and added to the context type.
	 *
	 * @param id the id for the context type as specified in XML
	 * @return the instantiated and configured context type, or
	 *         <code>null</code> if it is not found or cannot be instantiated
	 */
	public static TemplateContextType createContextType(String id) {
		Assert.isNotNull(id);

		IConfigurationElement[] extensions= getTemplateExtensions();
		TemplateContextType type;
		try {
			type= createContextType(extensions, id);
			if (type != null) {
				TemplateVariableResolver[] resolvers= createResolvers(extensions, id);
				for (int i= 0; i < resolvers.length; i++)
					type.addResolver(resolvers[i]);
			}
		} catch (CoreException e) {
			EditorsPlugin.log(e);
			type= null;
		}

		return type;
	}

	private static TemplateContextType createContextType(IConfigurationElement[] extensions, String contextTypeId) throws CoreException {
		for (int i= 0; i < extensions.length; i++) {
			// TODO create half-order over contributions
			if (extensions[i].getName().equals(CONTEXT_TYPE)) {
				String id= extensions[i].getAttributeAsIs(ID);
				if (contextTypeId.equals(id))
					return createContextType(extensions[i]);
			}
		}

		return null;
	}

	/**
	 * Instantiates the resolvers contributed to the context type with id
	 * <code>contextTypeId</code>. If instantiation of one resolver fails,
	 * the exception are logged and operation continues.
	 *
	 * @param extensions the configuration elements to parse
	 * @param contextTypeId the id of the context type for which resolvers are
	 *        instantiated
	 * @return the instantiated resolvers
	 */
	private static TemplateVariableResolver[] createResolvers(IConfigurationElement[] extensions, String contextTypeId) {
		List resolvers= new ArrayList();
		for (int i= 0; i < extensions.length; i++) {
			if (extensions[i].getName().equals(RESOLVER)) {
				String declaredId= extensions[i].getAttributeAsIs(CONTEXT_TYPE_ID);
				if (contextTypeId.equals(declaredId)) {
					try {
						TemplateVariableResolver resolver= createResolver(extensions[i]);
						if (resolver != null)
							resolvers.add(resolver);
					} catch (CoreException e) {
						EditorsPlugin.log(e);
					}
				}
			}
		}

		return (TemplateVariableResolver[]) resolvers.toArray(new TemplateVariableResolver[resolvers.size()]);

	}

	private static IConfigurationElement[] getTemplateExtensions() {
		return Platform.getExtensionRegistry().getConfigurationElementsFor(TEMPLATES_EXTENSION_POINT);
	}

	private static TemplateContextType createContextType(IConfigurationElement element) throws CoreException {
		String id= element.getAttributeAsIs(ID);
		try {
			TemplateContextType contextType= (TemplateContextType) element.createExecutableExtension(CLASS);
			String name= element.getAttribute(NAME);
			if (name == null)
				name= id;

			contextType.setId(id);
			contextType.setName(name);

			return contextType;
		} catch (ClassCastException e) {
			throw new CoreException(new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IStatus.OK, "extension does not implement " + TemplateContextType.class.getName(), e)); //$NON-NLS-1$
		}
	}

	private static TemplateVariableResolver createResolver(IConfigurationElement element) throws CoreException {
		try {
			String type= element.getAttributeAsIs(TYPE);
			if (type != null) {

				TemplateVariableResolver resolver= (TemplateVariableResolver) element.createExecutableExtension(CLASS);
				resolver.setType(type);

				String desc= element.getAttribute(DESCRIPTION);
				resolver.setDescription(desc == null ? "" : desc); //$NON-NLS-1$

				return resolver;
			}
		} catch (ClassCastException e) {
			throw new CoreException(new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IStatus.OK, "extension does not implement " + TemplateVariableResolver.class.getName(), e)); //$NON-NLS-1$
		}

		return null;
	}
}

