//------------------------------------------------------------------------------
// Copyright (c) 2005, 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 implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.library;

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

import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.epf.library.services.SafeUpdateController;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.MethodLibrary;
import org.eclipse.epf.uma.UmaFactory;

/**
 * The default Library Service implementation.
 * 
 * @author Kelvin Low
 * @author Jinhua Xi
 * @since 1.0
 */
public class LibraryService implements ILibraryService {

	protected static final int EVENT_CREATE_LIBRARY = 1;

	protected static final int EVENT_OPEN_LIBRARY = 2;

	protected static final int EVENT_REOPEN_LIBRARY = 3;

	protected static final int EVENT_CLOSE_LIBRARY = 4;

	protected static final int EVENT_SET_CURRENT_LIBRARY = 5;

	protected static final int EVENT_SET_CURRENT_CONFIGURATION = 6;

	// The shared instance.
	protected static ILibraryService instance = null;

	// A map of method libraries to library managers.
	private Map libraryManagers = new HashMap();

	// A map of method configurations to configuration managers.
	protected Map configManagers = new HashMap();

	// The library service listeners.
	protected List listeners = new ArrayList();

	// The current method library.
	protected MethodLibrary currentLibrary;

	// The current method configuration.
	protected MethodConfiguration currentConfig;

	/**
	 * Returns the shared instance.
	 */
	public static ILibraryService getInstance() {
		if (instance == null) {
			synchronized (LibraryService.class) {
				if (instance == null) {
					// TODO: Instantiate using the libraryService.class
					// property.
					instance = new LibraryService();
				}
			}
		}
		return instance;
	}

	/**
	 * Creates a new instance.
	 */
	private LibraryService() {
		init();
	}

	/**
	 * Performs the necessary initialization.
	 */
	protected void init() {
		// Initialize the library manager factory to pre-process the
		// "org.eclipse.epf.library.libraryManagers" extension point
		LibraryManagerFactory.getInstance();
	}

	/**
	 * Creates a new method library.
	 * 
	 * @param name
	 *            a name for the new method library
	 * @param type
	 *            the method library type
	 * @param args
	 *            method library specific arguments
	 * @return a method library
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public MethodLibrary createMethodLibrary(String name, String type,
			Map params) throws LibraryServiceException {
		if (name == null || type == null || params == null) {
			throw new IllegalArgumentException();
		}

		try {
			ILibraryManager manager = LibraryManagerFactory.getInstance()
					.createLibraryManager(type);
			MethodLibrary library = manager.createMethodLibrary(name, params);
			if (library != null) {
				setLibraryManager(manager);
				
				// set as current library, do this before notify listeners
				setCurrentMethodLibrary(library);

				notifyListeners(library, EVENT_CREATE_LIBRARY);
			}
			return library;
		} catch (Exception e) {
			throw new LibraryServiceException(e);
		}
	}

	/**
	 * Opens an existing method library.
	 * 
	 * @param type
	 *            the method library type
	 * @param params
	 *            method library specific arguments
	 * @return a method library
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public MethodLibrary openMethodLibrary(String type, Map params)
			throws LibraryServiceException {
		if (params == null) {
			throw new IllegalArgumentException();
		}

		try {
			ILibraryManager manager = LibraryManagerFactory.getInstance()
					.createLibraryManager(type);
			MethodLibrary library = manager.openMethodLibrary(params);
			if (library != null) {
				setLibraryManager(manager);
				
				// set as current library, do this before notify listeners
				setCurrentMethodLibrary(library);

				notifyListeners(library, EVENT_OPEN_LIBRARY);
			}
			return library;
		} catch (CreateLibraryManagerException e) {
			throw e;
		} catch (Exception e) {
			throw new LibraryServiceException(e);
		}
	}

	/**
	 * Reopens a method library.
	 * 
	 * @param library
	 *            a method library
	 * @return a method library
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public MethodLibrary reopenMethodLibrary(MethodLibrary library)
			throws LibraryServiceException {
		ILibraryManager manager = getLibraryManager(library);
		if (manager != null) {
			try {
				removeLibraryManager(manager);
				library = manager.reopenMethodLibrary();				
				// the library instance changed, reset the lib manager
				setLibraryManager(manager);
				
				// set as current library, do this before notify listeners
				setCurrentMethodLibrary(library);

				notifyListeners(library, EVENT_REOPEN_LIBRARY);
			} catch (Exception e) {
				throw new LibraryServiceException(e);
			}
		}
		return null;
	}

	/**
	 * Reopens the current method library.
	 * 
	 * @return a method library
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public MethodLibrary reopenCurrentMethodLibrary()
			throws LibraryServiceException {
		return reopenMethodLibrary(currentLibrary);
	}

	/**
	 * Saves a method library.
	 * 
	 * @param library
	 *            a method library
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public void saveMethodLibrary(MethodLibrary library)
			throws LibraryServiceException {
		ILibraryManager manager = getLibraryManager(library);
		if (manager != null) {
			manager.saveMethodLibrary();
		}
	}

	/**
	 * Saves the current method library.
	 * 
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public void saveCurrentMethodLibrary() throws LibraryServiceException {
		saveMethodLibrary(currentLibrary);
	}

	/**
	 * Closes a method library.
	 * <p>
	 * This automatically disposes its library manager and the configuration managers 
	 * that manage the method configurations in the method library.
	 * 
	 * @param library
	 *            a method library
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public void closeMethodLibrary(MethodLibrary library)
			throws LibraryServiceException {
		ILibraryManager manager = getLibraryManager(library);
		if (manager != null) {
			notifyListeners(library, EVENT_CLOSE_LIBRARY);
			
//			// move this part to libraryManager.closeMethodLibrary()
//			// Jinhua Xi 07/13/06
//			MethodConfiguration[] configs = LibraryServiceUtil.getMethodConfigurations(library);
//			for (int i = 0; i < configs.length; i++) {
//				IConfigurationManager configManager = (IConfigurationManager) configManagers.get(configs[i]);
//				if (configManager != null) {
//					configManager.dispose();
//					configManagers.remove(configManager);
//				}
//			}
			manager.closeMethodLibrary();
			if (currentLibrary == library) {
				setCurrentMethodLibrary(null);
			}
			removeLibraryManager(manager);
			manager.dispose();
		}
	}

	/**
	 * Closes the current method library.
	 * 
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public void closeCurrentMethodLibrary() throws LibraryServiceException {
		if (currentLibrary != null) {
			closeMethodLibrary(currentLibrary);
		}
	}

	/**
	 * Replaces a the method library.
	 * 
	 * @param oldLibrary
	 *            the old method library
	 * @param newLibrary
	 *            the new method library
	 */
	public void replaceMethodLibrary(MethodLibrary oldLibrary,
			MethodLibrary newLibrary) {
		ILibraryManager manager = getLibraryManager(oldLibrary);
		if (manager != null) {
			removeLibraryManager(manager);
			
			// the library manager still hold the old library, need to reset it
			// Jinhua Xi, 07/12/06
			manager.setMethodLibrary(newLibrary);
			
			setLibraryManager(manager);
		}
	}

	/**
	 * Adds a listener to monitor Library Service events.
	 * 
	 * @param listener
	 *            a library service listener
	 */
	public void addListener(ILibraryServiceListener listener) {
		listeners.add(listener);
	}

	/**
	 * Removes a listener that was added to monitor Library Service events.
	 * 
	 * @param listener
	 *            a library service listener
	 */
	public void removeListener(ILibraryServiceListener listener) {
		listeners.remove(listener);
	}

	/**
	 * Gets the current method library.
	 * 
	 * @return a method library
	 */
	public MethodLibrary getCurrentMethodLibrary() {
		return currentLibrary;
	}

	/**
	 * Sets the current method library.
	 * 
	 * @param library
	 *            a method library
	 */
	public void setCurrentMethodLibrary(MethodLibrary library) {
		currentLibrary = library;
		notifyListeners(library, EVENT_SET_CURRENT_LIBRARY);
	}

	/**
	 * Gets the current method library path.
	 * <p>
	 * Note: A file-based method library may return <code>null</code>.
	 * 
	 * @return an absolute path to the current method library
	 */
	public String getCurrentMethodLibraryPath() {
		ILibraryManager manager = getLibraryManager(currentLibrary);
		if (manager != null) {
			return manager.getMethodLibraryPath();
		} else {
			return null;
		}
	}

	/**
	 * Gets the library manager for a method library.
	 * 
	 * @param library
	 *            a method library
	 * @return a library manager
	 */
	public ILibraryManager getLibraryManager(MethodLibrary library) {
		return (ILibraryManager) libraryManagers.get(library);
	}

	public void removeLibraryManager(ILibraryManager libMgr) {
		if ( libMgr != null ) {
			MethodLibrary lib = libMgr.getMethodLibrary();
			if ( lib != null ) {
				removeConfigurationManagers(lib);
				libraryManagers.remove(lib);
			}
		}
	}
	
	public void setLibraryManager(ILibraryManager libMgr) {
		MethodLibrary lib = libMgr.getMethodLibrary();
		if ( lib != null ) {
			libraryManagers.put(lib, libMgr);
		}
		
	}
	
	
	/**
	 * Gets the library manager for the current method library.
	 * 
	 * @return a library manager
	 */
	public ILibraryManager getCurrentLibraryManager() {
		return getLibraryManager(currentLibrary);
	}

	/**
	 * Creates a new method configuration.
	 * 
	 * @param name
	 *            a name for the new method configuration
	 * @param library
	 *            the containing method library
	 * @return a method configuration
	 * @throw <code>LibraryServiceException</code> if an error occurs while
	 *        performing the operation
	 */
	public MethodConfiguration createMethodConfiguration(String name,
			MethodLibrary library) throws LibraryServiceException {
		if (name == null || library == null) {
			throw new IllegalArgumentException();
		}

		MethodConfiguration config;
		List configs = library.getPredefinedConfigurations();
		for (Iterator it = configs.iterator(); it.hasNext();) {
			config = (MethodConfiguration) it.next();
			if (name.equals(config.getName())) {
				throw new ConfigurationAlreadyExistsException();
			}
		}

		config = UmaFactory.eINSTANCE.createMethodConfiguration();
		config.setName(name);
		configs.add(config);
		return config;
	}

	/**
	 * Gets the current method configuration.
	 * 
	 * @return a method configuration
	 */
	public MethodConfiguration getCurrentMethodConfiguration() {
		return currentConfig;
	}

	/**
	 * Sets the current method configuration.
	 * 
	 * @param config
	 *            a method configuration
	 */
	public void setCurrentMethodConfiguration(MethodConfiguration config) {
		currentConfig = config;
		notifyListeners(config, EVENT_SET_CURRENT_CONFIGURATION);
	}

	/**
	 * Gets the configuration manager for a method configuration.
	 * 
	 * @param config
	 *            a method configuration
	 * @return a configuration manager
	 */
	public IConfigurationManager getConfigurationManager(
			MethodConfiguration config) {
		if (config == null) {
			throw new IllegalArgumentException();
		}
		IConfigurationManager manager = (IConfigurationManager) configManagers
				.get(config);
		if (manager == null) {
			manager = new ConfigurationManager(config);
			configManagers.put(config, manager);
		}
		return manager;
	}

	public void removeConfigurationManager(
			MethodConfiguration config) {
		if (config == null) {
			throw new IllegalArgumentException();
		}

		IConfigurationManager mgr = (IConfigurationManager)configManagers.remove(config);
		if ( mgr != null ) {
			mgr.dispose();
		}
	}
	
	public void removeConfigurationManagers(
			MethodLibrary library) {
		if (library == null) {
			throw new IllegalArgumentException();
		}

		MethodConfiguration[] configs = LibraryServiceUtil.getMethodConfigurations(library);
		for (int i = 0; i < configs.length; i++) {
			removeConfigurationManager(configs[i]);
		}
	}
	
	/**
	 * Gets the configuration manager for the current method configuration.
	 * 
	 * @return a configuration manager
	 */
	public IConfigurationManager getCurrentConfigurationManager() {
		if (currentConfig != null) {
			return getConfigurationManager(currentConfig);
		}
		return null;
	}

	/**
	 * Sends a method library related event to all library service listeners.
	 */
	protected void notifyListeners(final MethodLibrary library, int eventId) {
		for (Iterator it = listeners.iterator(); it.hasNext();) {
			final ILibraryServiceListener listener = (ILibraryServiceListener) it
					.next();
			switch (eventId) {
			case EVENT_CREATE_LIBRARY:
				SafeUpdateController.syncExec(new Runnable() {
					public void run() {
						listener.libraryCreated(library);
					}
				});
				break;
			case EVENT_OPEN_LIBRARY:
				SafeUpdateController.syncExec(new Runnable() {
					public void run() {
						listener.libraryOpened(library);
					}
				});
				break;
			case EVENT_REOPEN_LIBRARY:
				SafeUpdateController.syncExec(new Runnable() {
					public void run() {
						listener.libraryReopened(library);
					}
				});
				break;
			case EVENT_CLOSE_LIBRARY:
				SafeUpdateController.syncExec(new Runnable() {
					public void run() {
						listener.libraryClosed(library);
					}
				});
				break;
			case EVENT_SET_CURRENT_LIBRARY:
				SafeUpdateController.syncExec(new Runnable() {
					public void run() {
						listener.librarySet(library);
					}
				});
				break;
			}
		}
	}

	/**
	 * Sends a method configuration related event to all library service
	 * listeners.
	 */
	protected void notifyListeners(final MethodConfiguration config, int eventId) {
		for (Iterator it = listeners.iterator(); it.hasNext();) {
			final ILibraryServiceListener listener = (ILibraryServiceListener) it
					.next();
			switch (eventId) {
			case EVENT_SET_CURRENT_CONFIGURATION:
				SafeUpdateController.syncExec(new Runnable() {
					public void run() {
						listener.configurationSet(config);
					}
				});
				break;
			}
		}
	}

}
