/*******************************************************************************
 * Copyright (c) 2006 Oracle 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:
 *     Oracle Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.bpel.model.adapters;

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

import org.eclipse.core.runtime.IAdapterFactory;
import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.NotificationImpl;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;

/**
 * This is the one place where EMF object adapters can be registered. 
 * 
 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
 * @date Jul 23, 2007
 *
 */

public class AdapterRegistry {

	/**
	 * The singleton instance of this registry.
	 */
	
	static final public AdapterRegistry INSTANCE = new AdapterRegistry();

	/** For every type of EClass or EPackage, register an Adapter Factory 
	 *   This means that a particular EClass or EPackage may have N
	 *    
	 *   Adapter Factories. 
	 */
	
	HashMap<Object,List<AdapterFactory>> fKeyToAdapterFactory ;
	
	/** The current adapter manager */
	IAdapterManager fAdapterManager;
	
	/**
	 * Private constructor.
	 */
	
	AdapterRegistry () {
		fKeyToAdapterFactory = new HashMap<Object,List<AdapterFactory>>();
		
		if (Platform.isRunning()) {
			fAdapterManager = Platform.getAdapterManager();
		} else {
			fAdapterManager = org.eclipse.core.internal.runtime.AdapterManager.getDefault();
		}		
	}
	
	/**
	 * Register adapter factory for the given EClass.
	 * 
	 * @param key
	 * @param factory
	 */
	
	public void registerAdapterFactory (EClass key, AdapterFactory factory) {
		registerFactory (key,factory);
	}

	/**
	 * Register adapter factory for the given EPackage.
	 * 
	 * @param key
	 * @param factory
	 */
	
	public void registerAdapterFactory (EPackage key, AdapterFactory factory) {
		registerFactory (key,factory);
	}

	
	
	/**
	 * Unregister adapter factory for the given object (EClass)
	 * @param key
	 * @param factory
	 */
	
	public void unregisterAdapterFactory (EClass key, AdapterFactory factory) {
		unregisterFactory (key,factory);
	}

	/**
	 * Unregister adapter factory for the given object (EPackage)
	 * @param key
	 * @param factory
	 */
	
	public void unregisterAdapterFactory (EPackage key, AdapterFactory factory) {
		unregisterFactory (key,factory);
	}

	
	/**
	 * Register the factory (internal method, synchronized).
	 * @param key
	 * @param factory
	 */
	synchronized void registerFactory ( Object key, AdapterFactory factory) {
		List<AdapterFactory> list = fKeyToAdapterFactory.get(key);
		
		if (list == null) {
			list = new ArrayList<AdapterFactory>( );
			fKeyToAdapterFactory.put( key, list );
			list.add (factory);
		} else {
			if (list.contains(factory) == false) {
				list.add (factory);
			}
		}
	}
	
	synchronized void unregisterFactory (Object key, AdapterFactory factory) {
		List<AdapterFactory> list = fKeyToAdapterFactory.get(key);
		
		if (list == null) {
			return ;
		}
		list.remove(factory);		
	}
	
	
	Class<?> adapterInterface ( Object type ) {
		
		if (type instanceof Class) {
			return (Class<?>) type;
		}
		
		if (type instanceof String) {
			try {
				return Class.forName((String)type);
			} catch (ClassNotFoundException e) {
				throw new RuntimeException(e);
			}				
		}
		
		throw new RuntimeException("Adapter type " + type + " is not understood.");		 //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	/**
	 * @param <T>
	 * @param target
	 * @param clazz
	 * @return the adapted interface or object
	 */
		
	
	public <T extends Object> T adapt ( Object target,  Class<T> clazz) {
		return adapt (target,clazz,true);
	}
	

	/**
	 * 
	 * @param <T>
	 * @param target
	 * @param clazz
	 * @param checkWSAdapters Check the Workspace adapters as well.
	 * 
	 * @return the adapted interface or object
	 */

	public <T extends Object> T adapt ( Object target,  Class<T> clazz, boolean checkWSAdapters ) {
	
		if (target == null) {
			return null;
		}
		
		if (clazz.isInstance(target)) {
			return clazz.cast(target);
		}
		
		Object adapter = null;		
		
		if (target instanceof EObject) {
			
			EObject eObj = (EObject) target;
			EClass effectiveClass = eObj.eClass();
			
			List<AdapterFactory> list = fKeyToAdapterFactory.get( effectiveClass );
			if (list != null) {
				for(AdapterFactory factory : list) {
					adapter = factory.adapt(target, clazz);
					if (adapter != null && clazz.isInstance(adapter)) {
						return clazz.cast(adapter);
					}
				}
			}
			
			list = fKeyToAdapterFactory.get( effectiveClass.getEPackage() );
			if (list != null) {
				for(AdapterFactory factory : list ) {
					adapter = factory.adapt(target, clazz);
					if (adapter != null && clazz.isInstance(adapter)) {
						return clazz.cast(adapter);
					}
				}					   
			}
		}
				
		if ( checkWSAdapters && fAdapterManager != null ) {
			// otherwise, the object we are adapting is not an EObject, try any other adapters.		
			adapter = fAdapterManager.getAdapter(target, clazz);
			if (adapter != null && clazz.isInstance(adapter)) {
				return clazz.cast(adapter);
			}
		}
		
	    return null;
	}
	
	
	
	/**
	 * This method tries the registered adapter factories one by one, returning
	 * the first non-null result it gets.  If none of the factories can adapt
	 * the result, it returns null.
	 * @param target target object 
	 * @param type type of the adapter to find
	 * @return the adapter for the target.
	 */
	
	public Object adapt (Object target, Object type) {

		if (target == null) {
			return null;
		}		 
		return adapt ( target, adapterInterface(type) );
	}
	
	
	/**
	 * Create an adapter for the given target of the given type. 
	 * In addition, pass a context object to the adapter(s) of the target. 
	 * 
	 * The idea is that some adapters can be stateful and depend not only 
	 * on the objects that they wrap, but also on some other context that is needed
	 * to completely and correctly implement the interface for which the adaptor is
	 * needed.
	 * 
	 * Adapters that are stateless, should ignore any notifications sent to them.
	 *  
	 * @param target the target object
	 * @param type the type it wants to adapt to
	 * @param context the context object
	 * 
	 * @return the adapter
	 */
	public Object adapt (Object target, Object type, Object context) {
		
		Object adapter = adapt (target,type);
		if (adapter == null) {
			return adapter;
		}
		
		if (target instanceof EObject) {				
			EObject eObject = (EObject) target;		
			Notification n = new NotificationImpl(AbstractAdapter.CONTEXT_UPDATE_EVENT_TYPE, null, context);		
			eObject.eNotify(n);
		}
		
		return adapter;
	}

	public void registerAdapterFactory (IAdapterFactory factory) {
		registerAdapterFactory(factory, Object.class);
	}
	
	/**
	 * @param factory
	 * @param class1
	 */
	public void registerAdapterFactory(IAdapterFactory factory,	Class<?> clazz) {
		fAdapterManager.registerAdapters(factory, clazz);		
	}


}
