//------------------------------------------------------------------------------
// 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.xmi;

import java.io.File;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.epf.library.ILibraryManager;
import org.eclipse.epf.library.InvalidMethodElementNameException;
import org.eclipse.epf.library.LibraryAlreadyExistsException;
import org.eclipse.epf.library.LibraryNotFoundException;
import org.eclipse.epf.library.LibraryServiceException;
import org.eclipse.epf.library.edit.util.Suppression;
import org.eclipse.epf.library.events.ILibraryChangeListener;
import org.eclipse.epf.library.xmi.internal.LibraryProcessor;
import org.eclipse.epf.persistence.MultiFileResourceSetImpl;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.MethodLibrary;
import org.eclipse.epf.uma.MethodPlugin;
import org.eclipse.ui.IPropertyListener;

/**
 * The default XMI Library Manager implementation.
 * 
 * @author Kelvin Low
 * @since 1.0
 */
public class XMILibraryManager implements ILibraryManager {

	/**
	 * The library XMI file name.
	 */
	public static final String LIBRARY_XMI = "library.xmi";

	/**
	 * The library name.
	 */
	public static final String ARG_LIBRARY_NAME = "library.name";

	/**
	 * The library path.
	 */
	public static final String ARG_LIBRARY_PATH = "library.path";

	// The name of the lock file for a library.
	protected static final String LIBRARY_LOCK_FILENAME = ".lock"; //$NON-NLS-1$

	// If true, generate debug traces.
	protected static boolean debug = XMILibraryPlugin.getDefault()
			.isDebugging();

	// The absolute path to the managed library.
	protected String path;

	// The managed library.
	protected MethodLibrary library;

	// The library processor instance.
	protected LibraryProcessor libraryProcessor;

	/**
	 * Creates a new instance.
	 */
	public XMILibraryManager() {
		init();
	}

	/**
	 * Performs the necessary initialization.
	 */
	protected void init() {
		libraryProcessor = LibraryProcessor.getInstance();
	}

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

		String path = (String) args.get(ARG_LIBRARY_PATH);
		if (path == null || path.length() == 0) {
			throw new IllegalArgumentException();
		}

		File libraryPath = new File(path);
		File libraryXMIFile = new File(libraryPath, LIBRARY_XMI);
		if (libraryXMIFile.exists()) {
			throw new LibraryAlreadyExistsException();
		}

		if (!libraryPath.exists()) {
			libraryPath.mkdirs();
		}

		try {
			libraryProcessor.newLibrary(name, libraryPath.getAbsolutePath());
			library = libraryProcessor.getLibrary();
			return library;
		} catch (Exception e) {
			throw new LibraryServiceException(e);
		}
	}

	/**
	 * Opens a method library.
	 * 
	 * @param args
	 *            Method library specific arguments.
	 * @return A <code>MethodLibrary</code>.
	 * @throw <code>LibraryServiceException</code> if an error occurred while
	 *        performing the operation.
	 */
	public MethodLibrary openMethodLibrary(Map args)
			throws LibraryServiceException {
		if (args == null) {
			throw new IllegalArgumentException();
		}

		String path = (String) args.get(ARG_LIBRARY_PATH);
		if (path == null || path.length() == 0) {
			throw new IllegalArgumentException();
		}

		File libraryPath = new File(path);
		File libraryXMIFile = new File(libraryPath, LIBRARY_XMI);
		if (!libraryXMIFile.exists()) {
			throw new LibraryNotFoundException();
		}

		try {
			libraryProcessor.openLibrary(libraryXMIFile.getAbsolutePath());
			library = libraryProcessor.getLibrary();
			return library;
		} catch (Exception e) {
			throw new LibraryServiceException(e);
		}
	}

	/**
	 * Reopens the managed method library.
	 * 
	 * @return A <code>MethodLibrary</code>.
	 * @throw <code>LibraryServiceException</code> if an error occurred while
	 *        performing the operation.
	 */
	public MethodLibrary reopenMethodLibrary() throws LibraryServiceException {
		String libraryPath = getMethodLibraryPath();
		File libraryXMIFile = new File(libraryPath, LIBRARY_XMI);
		if (!libraryXMIFile.exists()) {
			throw new LibraryNotFoundException();
		}

		try {
			libraryProcessor.openLibrary(libraryXMIFile.getAbsolutePath());
			library = libraryProcessor.getLibrary();
			return library;
		} catch (Exception e) {
			throw new LibraryServiceException(e);
		}
	}

	/**
	 * Saves a method library.
	 * 
	 * @throw <code>LibraryServiceException</code> if an error occurred while
	 *        performing the operation.
	 */
	public void saveMethodLibrary() throws LibraryServiceException {
		try {
			libraryProcessor.saveLibrary();
		} catch (Exception e) {
			throw new LibraryServiceException(e);
		}
	}

	/**
	 * Discards all changes made to the managed method library.
	 */
	public void discardMethodLibraryChanges() {
		for (Iterator it = getEditingDomain().getResourceSet().getResources()
				.iterator(); it.hasNext();) {
			Resource resource = (Resource) it.next();
			resource.setModified(false);
		}
	}

	/**
	 * Closes the method library that is managed by this library manager.
	 * 
	 * @return A <code>MethodLibrary</code>.
	 * @throw <code>LibraryServiceException</code> if an error occurred while
	 *        performing the operation.
	 */
	public void closeMethodLibrary() throws LibraryServiceException {
		libraryProcessor.closeOpenedLibrary();
	}

	/**
	 * Returns the method library that is managed by this library manager.
	 * 
	 * @return A <code>MethodLibrary</code>.
	 */
	public MethodLibrary getMethodLibrary() {
		return library;
	}

	/**
	 * Sets the managed method library.
	 * 
	 * @param library
	 *            A method library.
	 */
	public void setMethodLibrary(MethodLibrary library) {
		this.library = library;
	}

	/**
	 * Returns the absolute path to the method library that is managed by this
	 * library manager.
	 * 
	 * @return An absolute path to the method library.
	 */
	public String getMethodLibraryPath() {
		return libraryProcessor.getLibraryRootPath().getAbsolutePath();
	}

	/**
	 * Returns the adapter factory.
	 * 
	 * @return A <code>ComposedAdapterFactory</code>
	 */
	public ComposedAdapterFactory getAdapterFactory() {
		return (ComposedAdapterFactory) libraryProcessor.getEditingDomain()
				.getAdapterFactory();
	}

	/**
	 * Gets the editing domain for the managed method library.
	 * 
	 * @return An <code>AdapterFactoryEditingDomain</code>.
	 */
	public AdapterFactoryEditingDomain getEditingDomain() {
		return libraryProcessor.getEditingDomain();
	}

	/**
	 * Registers an editing domain with the managed method library.
	 * 
	 * @param domain
	 *            An editing domain.
	 */
	public void registerEditingDomain(AdapterFactoryEditingDomain domain) {
		libraryProcessor.registerEditingDomain(domain);
	}

	/**
	 * Attaches a changed listener to the managed method library.
	 * 
	 * @param listener
	 *            A library change listener.
	 */
	public void addListener(ILibraryChangeListener listener) {
		libraryProcessor.addListener(listener);
	}

	/**
	 * Detaches a changed listener from the managed method library.
	 * 
	 * @param listener
	 *            A library change listener.
	 */
	public void removeListener(ILibraryChangeListener listener) {
		libraryProcessor.removeListener(listener);
	}

	/**
	 * Attaches a property change listener to the managed method library.
	 * 
	 * @param listener
	 *            A property change listener.
	 */
	public void addPropertyListener(IPropertyListener listener) {
		libraryProcessor.addPropertyListener(listener);
	}

	/**
	 * Detaches a property change listener from the managed method library.
	 * 
	 * @param listener
	 *            A property change listener.
	 */
	public void removePropertyListener(IPropertyListener listener) {
		libraryProcessor.removePropertyListener(listener);
	}

	/**
	 * Starts listening to command processing on a command stack.
	 * 
	 * @param commandStack
	 *            A command stack.
	 */
	public void startListeningTo(CommandStack commandStack) {
		libraryProcessor.listenTo(commandStack);
	}

	/**
	 * Stops listening to command processing on a command stack.
	 * 
	 * @param commandStack
	 *            A command stack.
	 */
	public void stopListeningTo(CommandStack commandStack) {
		libraryProcessor.stopListeningTo(commandStack);
	}

	/**
	 * Starts listening to change notifications sent from an adapter factory.
	 * 
	 * @param adapterFactory
	 *            An adapter factory.
	 */
	public void startListeningTo(ComposedAdapterFactory adapterFactory) {
		libraryProcessor.listenTo(adapterFactory);
	}

	/**
	 * Stops listening to change notifications sent from an adapter factory.
	 * 
	 * @param adapterFactory
	 *            An adapter factory.
	 */
	public void stopListeningTo(ComposedAdapterFactory adapterFactory) {
		libraryProcessor.stopListeningTo(adapterFactory);
	}

	/**
	 * Returns a method element.
	 * 
	 * @param guid
	 *            The method element's GUID.
	 * 
	 * @return A <code>MethodElement</code> or <code>null</code>.
	 */
	public MethodElement getMethodElement(String guid) {
		try {
			MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) library
					.eResource().getResourceSet();
			if (resourceSet != null) {
				return (MethodElement) resourceSet.getEObject(guid);
			}
		} catch (Throwable th) {
		}
		return null;
	}

	/**
	 * Gets the relative URI of a method element in the managed method library.
	 * 
	 * @param element
	 *            A method element.
	 * @return A relative URI.
	 */
	public URI getElementRelativeURI(MethodElement element) {
		if (element != null) {
			Resource resource = library.eResource();
			if (resource != null) {
				URI libraryURI = resource.getURI();
				URI elementURI = element.eResource().getURI();
				return elementURI.deresolve(libraryURI);
			}
		}
		return null;
	}

	/**
	 * Checks whether the managed method library is locked.
	 * 
	 * @return <code>true</code> if the method library is locked.
	 */
	public boolean isMethodLibraryLocked() {
		return XMILibraryUtil.isMethodLibraryLocked(getMethodLibraryPath());
	}

	/**
	 * Checks whether the managed method library is read only.
	 * 
	 * @return <code>true</code> if the method library is read only.
	 */
	public boolean isMethodLibraryReadOnly() {
		URI libraryURI = library.eResource().getURI();
		File libraryXMIFile = new File(libraryURI.toFileString());
		return libraryXMIFile.exists() && !libraryXMIFile.canWrite();
	}

	/**
	 * Checks if the managed method library content has been modified.
	 * 
	 * @return <code>true</code> if the managed method library content has
	 *         been modified.
	 */
	public boolean isMethodLibraryModified() {
		for (Iterator it = getEditingDomain().getResourceSet().getResources()
				.iterator(); it.hasNext();) {
			Resource resource = (Resource) it.next();
			if (resource.isModified()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Reloads the given resources.
	 * 
	 * @param resources
	 *            A collection of resources.
	 * @return A collection of resources that have reloaded.
	 */
	public Collection reloadResources(Collection resources) {
		MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) library
				.eResource().getResourceSet();
		Collection reloadeResources = resourceSet.reloadResources(resources);
		if (!reloadeResources.isEmpty()) {
			// TODO: Review implementation.
			Suppression.cleanUp();
		}
		return reloadeResources;
	}

	/**
	 * Returns the options used for saving the managed method library.
	 * 
	 * @return A map of method library specific save options.
	 */
	public Map getSaveOptions() {
		return libraryProcessor.getSaveOptions();
	}

	/**
	 * Adds a new method plug-in to the managed method library.
	 * 
	 * @param plugin
	 *            A method plug-in.
	 * @return A <code>MethodPlugin</code>.
	 * @throw A <code>LibraryServiceException</code> if an error occurred
	 *        while performing the operation.
	 */
	public void addMethodPlugin(MethodPlugin plugin)
			throws LibraryServiceException {
		try {
			libraryProcessor.addMethodPlugin(plugin);
		} catch (Exception e) {
			throw new LibraryServiceException(e);
		}
	}

	/**
	 * Checks the arguments used ofr creating a new method element.
	 * 
	 * @param containingElement
	 *            A <code>MethodElement</code> instance.
	 * @param name
	 *            A name for the new method element.
	 * @throw A <code>LibraryServiceException</code> if an error occurred
	 *        while performing the operation.
	 */
	protected void checkElementCreationArguments(
			MethodElement containingElement, String name)
			throws LibraryServiceException {
		if (containingElement == null) {
			throw new IllegalArgumentException();
		}
		if (name == null || name.length() == 0) {
			throw new InvalidMethodElementNameException();
		}
		// TODO: Check for illegal characters.
	}

}
