/**
 * File:    PropertyDescriptorExtensionPoint.java
 * Created: 11.07.2008
 *
 *
/*******************************************************************************
 * Copyright (c) 2005-2011
 * University of Augsburg, Germany <www.ds-lab.org>
 *
 * 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
 *
 *    Mickael Istria, Open Wide, France
 *      - Creation and Implementation
 *******************************************************************************/

package org.eclipse.jwt.we.editors.properties.extension.internal;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
import org.eclipse.emf.edit.provider.ItemPropertyDescriptor;
import org.eclipse.jwt.we.PluginProperties;
import org.eclipse.jwt.we.editors.properties.extension.FeatureFullDescription;
import org.eclipse.jwt.we.editors.properties.extension.PropertyDescriptorFactory;
import org.eclipse.jwt.we.misc.extensions.ExtensionsHelper;
import org.eclipse.jwt.we.misc.logging.Logger;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.views.properties.IPropertyDescriptor;

/**
 * 
 * Extension point related code to get custom property descriptors defined in
 * extension plugins
 * 
 */
public class PropertyDescriptorExtensionPoint {

	// Contains the entries of the extension point
	private static Map<FeatureFullDescription, PropertyDescriptorFactory> extensionPointEntries = new HashMap<FeatureFullDescription, PropertyDescriptorFactory>();
	// Contains the PropertyDescriptor associated to a feature (with inheritance
	// resolved)
	private static Map<FeatureFullDescription, PropertyDescriptorFactory> propertyDescriptors = new HashMap<FeatureFullDescription, PropertyDescriptorFactory>();

	private static Logger log = Logger.getLogger(PropertyDescriptorExtensionPoint.class);

	private static boolean initialized = false;

	private synchronized static void init() {
		if (!initialized) {
			try {
				processExtensionPoint();
			} catch (WorkbenchException ex) {
				Logger.getLogger(PropertyDescriptorExtensionPoint.class).severe("Could not process extension point for custom PropertyDescriptor"); //$NON-NLS-1$
			}
			initialized = true;
		}
	}

	/**
	 * 
	 * @param eObject
	 *            the eObject whom properties are displayed
	 * @param itemPropertyDescriptor
	 *            the itemPropertyDescriptor for current property of the object
	 * @return the property descriptor associated to the EFeature deduced from
	 *         {@link eObject} and {@link ItemPropertyDescriptor}
	 */
	public static IPropertyDescriptor getPropertyDescriptor(Object eObject, IItemPropertyDescriptor itemPropertyDescriptor) {
		init();
		FeatureFullDescription currentFeature = new FeatureFullDescription(eObject, itemPropertyDescriptor);
		if (!propertyDescriptors.containsKey(currentFeature)) {
			computePropertyDescriptorFor(currentFeature);
		}
		PropertyDescriptorFactory propertyDescriptorFactory = propertyDescriptors.get(currentFeature);
		if (propertyDescriptorFactory != null) {
			return propertyDescriptorFactory.getPropertyDescriptor(eObject, itemPropertyDescriptor);
		} else {
			return null;
		}
	}

	/**
	 * Computes the propertyDescrptor for specified feature and adds the
	 * association Feature->PropertyDescriptor to map
	 * 
	 * @param currentFeature
	 */
	private static void computePropertyDescriptorFor(FeatureFullDescription currentFeature) {
		FeatureFullDescription bestFeature = null;
		// the "more child" feature description available which is compatible
		// with
		// specified feature
		for (FeatureFullDescription availableFeature : extensionPointEntries.keySet()) {
			if (currentFeature.isFeatureFromSuperclass(availableFeature)) {
				// if specified feature is the same as described in
				// availableFeature
				if (bestFeature == null || availableFeature.isFeatureFromSuperclass(bestFeature)) {
					// if the best feature is from a supertype of
					// availableFeature
					bestFeature = availableFeature;
					// then uses the feature that
				}
			}
		}
		if (bestFeature == null) {
			// if nothing found, adds the key feature to the list to avoid
			// re-computing it
			propertyDescriptors.put(currentFeature, null);
		} else {
			// Associates feature with the "Best" property descriptor found
			propertyDescriptors.put(currentFeature, extensionPointEntries.get(bestFeature));
		}
	}

	/********************************
	 * Extension point related code *
	 ********************************/

	/**
	 * The extension point, red from the property file
	 */
	public static final String EXTENSION_POINT = PluginProperties.extension_point_propertydescriptor;
	private static final String PACKAGE_NAME = "packageName"; //$NON-NLS-1$
	private static final String CLASS_NAME = "className"; //$NON-NLS-1$
	private static final String FEATURE_NAME = "featureName"; //$NON-NLS-1$
	private static final String ITEMPROVIDERFACTORY_CLASS = "PropertyDescriptorFactoryImpl"; //$NON-NLS-1$

	/**
	 * Process the extension point ie adds all entries of extension point into
	 * the map Fills extensionPointEntries
	 * 
	 * @throws WorkbenchException
	 */
	private static void processExtensionPoint() throws WorkbenchException {
		log.debug("Processing extension point " + EXTENSION_POINT); //$NON-NLS-1$

		IConfigurationElement[] confElements = ExtensionsHelper.findConfigurationElements(EXTENSION_POINT);

		// For each service:
		for (int m = 0; m < confElements.length; m++) {
			IConfigurationElement member = confElements[m];
			String featureName = member.getAttribute(FEATURE_NAME);
			String className = member.getAttribute(CLASS_NAME);
			String packageName = member.getAttribute(PACKAGE_NAME);

			// create log entry
			log.info("JWT Extension - found PropertyDescriptor at " + EXTENSION_POINT //$NON-NLS-1$
					+ ": {" + packageName + "}" + className + "/" + featureName); //$NON-NLS-1$  //$NON-NLS-2$ //$NON-NLS-3$

			PropertyDescriptorFactory proxy = new PropertyDescriptorFactoryProxy(member);
			extensionPointEntries.put(new FeatureFullDescription(featureName, className, packageName), proxy);
		}
	}

	private static class PropertyDescriptorFactoryProxy implements PropertyDescriptorFactory {

		private PropertyDescriptorFactory delegate = null; // The real callback.
		private IConfigurationElement element; // Function's configuration.
		private boolean invoked = false; // Called already.

		public PropertyDescriptorFactoryProxy(IConfigurationElement element) {
			this.element = element;
		}

		private final PropertyDescriptorFactory getDelegate() throws Exception {
			if (invoked) {
				return delegate;
			}
			invoked = true;
			try {
				Object callback = element.createExecutableExtension(ITEMPROVIDERFACTORY_CLASS);
				if (!(callback instanceof PropertyDescriptorFactory)) {
					throw new ClassCastException("Class [" //$NON-NLS-1$
							+ callback.getClass().getName() + "] is not an instance of PropertyDescriptorFactory"); //$NON-NLS-1$
				}
				delegate = (PropertyDescriptorFactory) callback;
			} catch (CoreException ex) {
				throw ex;
			}
			return delegate;
		}

		public IPropertyDescriptor getPropertyDescriptor(Object object, IItemPropertyDescriptor itemPropertyDescriptor) {
			PropertyDescriptorFactory propertyDescriptorFactory = null;
			try {
				propertyDescriptorFactory = getDelegate();
			} catch (Exception ex) {
				log.warning("Could not get implementation for a PropertyDescriptor Extension point"); //$NON-NLS-1$
			}
			return propertyDescriptorFactory.getPropertyDescriptor(object, itemPropertyDescriptor);
		}
	}
}
