/**
 * File:    ExtensionPointNotifier.java
 * Created: 22.09.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
 *
 * Contributors:
 *    Mickael Istria, Open Wide, Lyon, France 
 *    	- Creation and implementation
 *******************************************************************************/

package org.eclipse.jwt.we.misc.extensions.externalNotificationListener;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.provider.INotifyChangedListener;
import org.eclipse.jwt.we.PluginProperties;
import org.eclipse.jwt.we.editors.properties.extension.FeatureFullDescription;
import org.eclipse.jwt.we.misc.extensions.ExtensionsHelper;
import org.eclipse.jwt.we.misc.logging.Logger;
import org.eclipse.ui.WorkbenchException;


/**
 * This class is a {@link INotifyChangedListener} that is responsible of loading
 * extensions that are defined on extension point, and to call any of the extending
 * {@link INotifyChangedListener}.notifyChanged() that matches current EMF class and
 * feature
 * 
 * TODO could be implemented at plugin loading by directly associating listeners to
 * concrete feature
 */
public class ExtensionPointNotifyChangedListener
		implements INotifyChangedListener
{

	/**
	 * A Logger.
	 */
	private static Logger log = Logger
			.getLogger(ExtensionPointNotifyChangedListener.class);

	/**
	 * Extension point entries, does not resolve inheritance
	 */
	private static Map<FeatureFullDescription, List<INotifyChangedListener>> extensionPointEntries;

	/**
	 * Feature->List<NotifyChangedListener> associations (inheritance resolved)
	 */
	private static Map<FeatureFullDescription, List<INotifyChangedListener>> listeners;


	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.emf.edit.provider.INotifyChangedListener#notifyChanged(org.eclipse.
	 * emf.common.notify.Notification)
	 */
	public void notifyChanged(Notification notification)
	{
		init();

		// fix: there may be other notifiers, e.g. XMIResource
		if (notification.getNotifier() instanceof EObject)
		{
			EObject eObject = (EObject) notification.getNotifier();
			Object featureFound = notification.getFeature();
			// guard against non-feature events (ex. REMOVING_ADAPTER). Patches Bug 257698
			if (featureFound instanceof EStructuralFeature) {
				String featureName = ((EStructuralFeature) featureFound).getName();
	
				FeatureFullDescription currentFeature = new FeatureFullDescription(eObject,
						featureName);
				if (!listeners.containsKey(currentFeature)) {
					computeListenersFor(currentFeature);
				}
				for (INotifyChangedListener listener : listeners.get(currentFeature)) {
					listener.notifyChanged(notification);
				}
			}
		}
	}


	/**
	 * Find all listeners registered on extension point that are compatible with feature
	 * 
	 * @param feature
	 */
	private void computeListenersFor(FeatureFullDescription feature)
	{
		List<INotifyChangedListener> featureListeners = new ArrayList<INotifyChangedListener>();
		for (FeatureFullDescription availableFeature : extensionPointEntries.keySet())
		{
			if (feature.isFeatureFromSuperclass(availableFeature))
				featureListeners.addAll(extensionPointEntries.get(availableFeature));
		}
		listeners.put(feature, featureListeners);
	}


	private static synchronized void init()
	{
		if (extensionPointEntries == null)
		{
			extensionPointEntries = new HashMap<FeatureFullDescription, List<INotifyChangedListener>>();
			listeners = new HashMap<FeatureFullDescription, List<INotifyChangedListener>>();
			try
			{
				processExtensionPoint();
			}
			catch (Exception e)
			{
				log.severe("An error occured while processing extension point"); //$NON-NLS-1$
				e.printStackTrace();
			}
		}
	}

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

	/**
	 * The extension point, red from the property file
	 */
	public static final String EXTENSION_POINT = PluginProperties
			.extension_point_notifychangedlistener;
	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 NOTIFYCHANGED_CLASS = "NotifyChangedListener"; //$NON-NLS-1$


	/**
	 * Process the extension point ie adds all entries of extension point into the map
	 * 
	 * @throws WorkbenchException
	 */
	@SuppressWarnings("nls")
	private static void processExtensionPoint() throws WorkbenchException
	{
		log.debug("Processing extension point " + EXTENSION_POINT);

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

		// For each extension on this extension poing:
		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);

			log.info("JWT Extension - found NotifyChangedListener at " + EXTENSION_POINT
					+ ": {" + packageName + "}" + className + "/" + featureName);

			FeatureFullDescription featureFullDescription = new FeatureFullDescription(
					featureName, className, packageName);
			INotifyChangedListener proxy = new INotifyChangedListenerProxy(member);
			if (!extensionPointEntries.containsKey(featureFullDescription)
					|| extensionPointEntries.get(featureFullDescription) == null)
				extensionPointEntries.put(featureFullDescription,
						new ArrayList<INotifyChangedListener>());
			extensionPointEntries.get(featureFullDescription).add(proxy);
		}
	}

	/**
	 * @version $Id: ExtensionPointNotifyChangedListener.java,v 1.1 2008/09/29 09:56:26
	 *          chsaad Exp $
	 * @author Programming distributed Systems Lab, University of Augsburg, Germany,
	 *         www.ds-lab.org
	 */
	private static class INotifyChangedListenerProxy
			implements INotifyChangedListener
	{

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


		/**
		 * @param element
		 */
		public INotifyChangedListenerProxy(IConfigurationElement element)
		{
			this.element = element;
		}


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


		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.emf.edit.provider.INotifyChangedListener#notifyChanged(org.eclipse
		 * .emf.common.notify.Notification)
		 */
		public void notifyChanged(Notification notification)
		{
			try
			{
				getDelegate().notifyChanged(notification);
			}
			catch (Exception ex)
			{
				log.severe("An error occured while trying to retrieve delegate"); //$NON-NLS-1$
				ex.printStackTrace();
			}
		}

	}
}
