//------------------------------------------------------------------------------
// 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.importing.services;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.epf.authoring.ui.views.LibraryView;
import org.eclipse.epf.common.serviceability.MsgDialog;
import org.eclipse.epf.importing.ImportPlugin;
import org.eclipse.epf.importing.ImportResources;
import org.eclipse.epf.library.edit.util.ModelStructure;
import org.eclipse.epf.library.edit.util.TngUtil;
import org.eclipse.epf.library.services.SafeUpdateController;
import org.eclipse.epf.library.util.LibraryUtil;
import org.eclipse.epf.uma.ContentDescription;
import org.eclipse.epf.uma.CustomCategory;
import org.eclipse.epf.uma.DescribableElement;
import org.eclipse.epf.uma.DiagramElement;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.MethodLibrary;
import org.eclipse.epf.uma.MethodPackage;
import org.eclipse.epf.uma.MethodPlugin;
import org.eclipse.epf.uma.ProcessComponent;
import org.eclipse.epf.uma.UmaPackage;
import org.eclipse.epf.uma.util.ContentDescriptionFactory;
import org.eclipse.epf.uma.util.IFileManager;
import org.eclipse.epf.uma.util.UmaUtil;


/**
 * Manages the importing of a library.
 * 
 * @author Jinhua Xi
 * @since 1.0
 */
public class LibraryImportManager {

	private Map renameElementMap = new HashMap();
	private Map setElementMap = null;

	private LibraryDiffManager diffMgr;
	private ElementDiffTree rootTree;

	//protected AdapterFactoryEditingDomain editingDomain = null;

	//protected AdapterFactoryContentProvider afcp = null;

	private MethodLibrary baseLibrary = null;
	private MethodLibrary importingLibrary = null;

	// guid of replaced element id to the element in the base library
	// we need this to redirect the reference of new elements from the importing
	// libraty to the base library
	private Map replacedElementMap = new HashMap();

	// keep the new elements, we need to iterate their feature value and
	// redirect the valeu element
	// from the importing library to the base library
	private List newElements = new ArrayList();
		
	// deleted elements,
	private List deletedElements = new ArrayList();
	
	private boolean debug = ImportPlugin.getDefault().isDebugging();
	
	// list of user selected items to be imported
	List checkedList = new ArrayList();
	
	// flag to indicate the file cheout status
	IStatus fileCheckedOutStatus = null;
	
	// resource file path for new resoruces
	List newResources = new ArrayList();
	
	ResourceScanner resScanner;
	
	public LibraryImportManager(LibraryDiffManager diffMgr,
			List checkedItems) {
		this.diffMgr = diffMgr;
		this.rootTree = diffMgr.getDiffTree();
		
		if ( checkedItems != null ) {
			// checkedList contains a list of ElementDiffTree objects
			// get the packages as well
			this.checkedList.addAll(checkedItems);
			for (Iterator it = checkedItems.iterator(); it.hasNext(); ) {
				ElementDiffTree diffTree = (ElementDiffTree) it.next();
				MethodElement e = diffTree.getBaseElement();
				if ( e != null ) {
					checkedList.add(e);
				}
								
				if ( e instanceof ProcessComponent ) {
					LibraryUtil.getAllChildPackages((MethodPackage)e, checkedList);
				}
				
				// also incldue the hidden root custom package if the package is the CustomCategories package
				// 150895 - Import Config: CustomCategory did not sync up by import
				if ( e instanceof MethodPackage ) {
					List children = ((MethodPackage)e).getChildPackages();
					if ( children.size() == 1 ) {
						MethodPackage pkg  = (MethodPackage)children.get(0);
						if ( TngUtil.isRootCutomCategoryPackage(pkg) ) {
							checkedList.add(pkg);
						}
					}
				}
				
				e = diffTree.getImportElement();
				if ( e != null ) {
					checkedList.add(e);
				}
				
				if ( e instanceof ProcessComponent ) {
					LibraryUtil.getAllChildPackages((MethodPackage)e, checkedList);
				}

				
				// for new item, add all the sub packages
				if ( diffTree.isNew() ) {
					if ( e instanceof MethodPlugin ) {
						checkedList.addAll(LibraryUtil.getAllPackages((MethodPlugin)e));
					} else if ( e instanceof MethodPackage ) {
						LibraryUtil.getAllChildPackages((MethodPackage)e, checkedList);
					} 
				}
			}
			
		} else {
			checkedList = null;
		}
		
		
		init();

		// Get the current library.
		baseLibrary = (MethodLibrary) this.rootTree.getBaseElement();
		importingLibrary = (MethodLibrary) this.rootTree
				.getImportElement();

		// create the resource scanner
		resScanner = new ResourceScanner(
				LibraryUtil.getLibraryRootPath(importingLibrary), 
				LibraryUtil.getLibraryRootPath(baseLibrary));
	}
		
	private void init() {
//		// Set the tree content.
//		List factories = new ArrayList();
//		factories.add(new ResourceItemProviderAdapterFactory());
//		factories.add(new UmaItemProviderAdapterFactory());
//		factories.add(new ReflectiveItemProviderAdapterFactory());
//
//		ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(
//				factories);
//		afcp = new AdapterFactoryContentProvider(adapterFactory);
//
//		BasicCommandStack commandStack = new BasicCommandStack();
//
//		// Create the editing domain with a special command stack.
//		editingDomain = new AdapterFactoryEditingDomain(adapterFactory,
//				commandStack, new HashMap());
//
	}

	private boolean isSelected(ElementDiffTree diffTree) {
		if ( checkedList == null || checkedList.contains(diffTree) ) {
			return true;
		}
		
		MethodElement e = diffTree.getBaseElement();		
		if ( e == null ) {
			e = diffTree.getImportElement();
		}
		
		return isSelected(e);
	}
	
	private boolean isSelected(MethodElement e) {
		
		if ( checkedList == null || 
				e instanceof ContentDescription || 
				e.eContainer() instanceof ContentDescription || 
				e instanceof MethodLibrary ) {
			return true;
		}
		
		if ( checkedList.size() == 0 ) {
			return false;
		}
		
		if ( e instanceof MethodConfiguration ) {
			return checkedList.contains(e);
		}
		
		MethodElement o = (MethodElement)LibraryUtil.getSelectable(e);
		if ( o == null ) {
			return false;
		} 
		
		if ( o instanceof MethodLibrary ) {
			return true;
		}
		
		return checkedList.contains(o);
	}
	
	public void doMerge(boolean replaceExisting, IProgressMonitor monitor) throws Exception {

		if ( debug ) {
			System.out.println("Merging configuration ..."); //$NON-NLS-1$
		}
			
		// Save the library path before detaching the resource.

		if ( debug ) {
			System.out.println("loading library ..."); //$NON-NLS-1$
		}

		LibraryUtil.loadAll(baseLibrary);
		LibraryUtil.loadAll(importingLibrary);

		// Detach the new library from the current resource so it can be
		// added to a new Library Processor instance.
		LibraryUtil.detachFromResource(importingLibrary);

		// for test
		importingLibrary.setName("ImportingLib: " + importingLibrary.getName()); //$NON-NLS-1$
		
		if ( debug ) {
			System.out.println("Process configuration diff tree ..."); //$NON-NLS-1$
		}

		// before processing, save the original resources 
		// so that we can find the newly added resources
		List oldResources = new ArrayList(
				baseLibrary.eResource().getResourceSet().getResources());
		
		// Iterate each element and the necessary merges.
		processDiffTree(rootTree, replaceExisting);

		handleSetElements();
		
		if ( debug ) {
			System.out.println("perform integrity checking ..."); //$NON-NLS-1$
		}

		// perform an integrity check of the library
		// fix any danfling elements due to package/element replacement
		doIntegrityCheck();

		// now process the new resources and mark them dirst so that 
		// the data will be saved again. 
		// This step is critical since the first round only created the data structure
		// some of the cross-referenced elements might be lost on the first saving
		// for example, when create a method configuration with references to a new plugin,
		// which are not saved yet, those references will be lost.
		// 145891 - Import Configuration: default config is loss after import
		handleNewResources(oldResources);
		
		handleNameReplace(renameElementMap);
		
		// get all the modified resources and resource files
		// check CM for file check-out
		if ( debug ) {
			System.out.println("check out files ..."); //$NON-NLS-1$
		}
		
		// clear resources for deleted elements
		deleteResoruces();
	
		final List modifiedFiles = getModifiedResources(baseLibrary);
		modifiedFiles.addAll(resScanner.getFilesTobeReplaced());

		SafeUpdateController.syncExec(new Runnable() {
			public void run() {
				fileCheckedOutStatus = FileModifyChecker.checkModify(modifiedFiles);
			}
		});
				
		if ( fileCheckedOutStatus.isOK() ) {
		 
			if ( debug ) {
				System.out.println("copying resource files ..."); //$NON-NLS-1$
			}
			resScanner.execute();
			
			
			if ( debug ) {
				System.out.println("saving library ..."); //$NON-NLS-1$
			}
			
			LibraryUtil.saveLibrary(baseLibrary, false, false);
		} 
		else {
			// log error
			SafeUpdateController.syncExec(new Runnable() {
				public void run() {
					String title = ImportResources.importConfigWizard_title; 
					String msg = ImportResources.ImportConfigurationWizard_ERR_Import_configuration; 
					new MsgDialog(ImportPlugin.getDefault())
						.displayError(title, msg, fileCheckedOutStatus);
					}
			});

			if ( debug ) {
				System.out.println("Checkout files failed ..."); //$NON-NLS-1$
			}
		}	
// if ( debug ) {
// System.out.println("copying resource files ..."); //$NON-NLS-1$
// }
//
// // Copy the resource files in the current library to the new library.
// // For simplicity sake, copy all resource files if the files do not
// // exist in the target library or if the files are newer.
// String includes = "resources/**, **/resources/**"; //$NON-NLS-1$
//
// LayoutResources.copyDir(srcDir, destDir, includes, null);

		if ( debug ) {
			System.out.println("Merging configuration done..."); //$NON-NLS-1$
		}

	}
		
	private void handleNewResources(List oldResources) {
		for (Iterator it = baseLibrary.eResource().getResourceSet()
				.getResources().iterator(); it.hasNext(); ) {
			Resource res = (Resource)it.next();
			if ( !oldResources.contains(res) ) {
				logNewResource(res);
			}
		}	
	}
	
	
	/**
	 * Processes the diff tree. Only need to do the first level since the
	 * replace or add is done at the method package level.
	 */
	private void processDiffTree(ElementDiffTree diffTree,
			boolean replaceExisting) throws Exception {
		if (diffTree == null || !isSelected(diffTree) ) {
			return;
		}

		// There is a change.
		int state = diffTree.getDiffState();
		MethodElement base = diffTree.getBaseElement();
		MethodElement imp = diffTree.getImportElement();
		if (replaceExisting && (state == ElementDiffTree.DIFF_STATE_CHANGED) ) {

			if ( (base instanceof MethodConfiguration)) {
				// replace the configuration by adding the new packages into the
				// configuration
				doReplaceConfiguration((MethodConfiguration) base, (MethodConfiguration) imp);
			}
			else if ( (base instanceof MethodPlugin)) {
				// Replace the plugin with the non-package features.
				doReplacePlugin((MethodPlugin) base, (MethodPlugin) imp);
			}
			else if ( (base instanceof MethodPackage)) {
				// Replace the package.
				doReplacePackage((MethodPackage) base, (MethodPackage) imp);
			}
			else
			{
				if (debug ) {
					System.out.println("What is this: " + LibraryUtil.getTypeName(base)); //$NON-NLS-1$
				}
			}
		} else if (state == ElementDiffTree.DIFF_STATE_NEW) {
			// Add the new package from import library.
			doAdd(diffTree.getBaseParentElement(), diffTree.getImportElement()); 
		}

		// Process the children recursively if it's not a new one.
		if (state != ElementDiffTree.DIFF_STATE_NEW) {
			List children = diffTree.getChildren();
			if (children != null && children.size() > 0) {
				for (Iterator it = children.iterator(); it.hasNext();) {
					processDiffTree((ElementDiffTree) it.next(),
							replaceExisting);
				}
			}
		}
	}

	/**
	 * update the configuration by replacing the non-package features, and
	 * adding the new packages into the configuration
	 * 
	 * @param oldObj
	 *            MethodConfiguration
	 * @param newObj
	 *            MethodConfiguration
	 */
	private void doReplaceConfiguration(MethodConfiguration oldObj, MethodConfiguration newObj)
	{
		if ( isReplaced(oldObj) ) {
			return;
		}
		setReplaced(oldObj);
		
		List properties = oldObj.getInstanceProperties();
		if (properties != null) {
			for (int i = 0; i < properties.size(); i++) {
				EStructuralFeature feature = (EStructuralFeature) properties.get(i);
				// don't delete old feature values
				Object newValue = newObj.eGet(feature);
				setFeatureValue(oldObj, feature, newValue, false);
			}
		}
	}

// private void mergeElementList(List source, List target)
// {
// List ids = new ArrayList();
// for ( Iterator it = target.iterator(); it.hasNext(); )
// {
// MethodElement e = (MethodElement)it.next();
// ids.add(e.getGuid());
// }
//
// for ( Iterator it = source.iterator(); it.hasNext(); )
// {
// MethodElement e = (MethodElement)it.next();
// if ( !ids.contains(e.getGuid()) )
// {
// target.add(e);
// }
// }
// }

	/**
	 * replace the plugin features, but not the plugin object and it's packages
	 * 
	 * @param oldObj
	 *            MethodPlugin
	 * @param newObj
	 *            MethodPlugin
	 */
	private void doReplacePlugin(MethodPlugin oldObj, MethodPlugin newObj) {
		if ( isReplaced(oldObj) ) {
			return;
		}
		setReplaced(oldObj);
		
		List properties = oldObj.getInstanceProperties();
		if (properties != null) {
			for (int i = 0; i < properties.size(); i++) {
				EStructuralFeature feature = (EStructuralFeature) properties.get(i);
				if ( feature != UmaPackage.eINSTANCE.getMethodPlugin_MethodPackages() )
				{
					Object newValue = newObj.eGet(feature);
					setFeatureValue(oldObj, feature, newValue, true);
				}
				/* No, don't do this, fix the diff manager to include the root package instead
				 * 150895 - Import Config: CustomCategory did not sync up by import
				else {
					// custom categories package needs special handing
					// we need to start with the root custom category otherwise
					// all child categories will be lost
					MethodPackage oldPkg = UmaUtil.findContentPackage(oldObj, ModelStructure.DEFAULT_CUSTOM_CATEGORY_PATH);
// ContentPackage newPkg = UmaUtil.findContentPackage(newObj,
// ModelStructure.DEFAULT.DEFAULT_CUSTOM_CATEGORY_PATH);
					setReplaced(oldPkg);
					
					// find the root custom category and start from there
					CustomCategory oldCC = TngUtil.getRootCustomCategory(oldObj);
					CustomCategory newCC = TngUtil.getRootCustomCategory(newObj);
					oldPkg = (MethodPackage)oldCC.eContainer();
					setReplaced(oldPkg);
					doReplaceElement(oldCC, newCC);
				}
				*/
			}
		}

	}



	/**
	 * Replaces the atrributes and contained elements, but not the child
	 * packages
	 */
	private void doReplacePackage(MethodPackage oldObj, MethodPackage newObj) {
		if ( isReplaced(oldObj) ) {
			return;
		}
		setReplaced(oldObj);

		if ( debug ) {
			System.out.println("Replacing package " + LibraryUtil.getTypeName(oldObj)); //$NON-NLS-1$
		}
		
		List properties = oldObj.getInstanceProperties();
		if (properties != null) {
			for (int i = 0; i < properties.size(); i++) {
				EStructuralFeature feature = (EStructuralFeature) properties.get(i);
				if ( feature == UmaPackage.eINSTANCE.getMethodPackage_ChildPackages() )
				{
					continue;
				}

				Object newValue = newObj.eGet(feature);
				setFeatureValue(oldObj, feature, newValue, true);
			}
		}

	}

// private MethodElement objOld = null;
// private MethodElement objNew = null;
// private void testIt() {
// if ( objOld != null ) {
// System.out.println("Old container =" + objOld.eContainer());
// System.out.println("New container =" + objNew.eContainer());
// if ( objOld.eContainer() == null ) {
// System.out.println("old is gone!");
// }
// if ( objNew.eContainer() == null ) {
// System.out.println("new is gone!");
// }
// }
// }
	
	/**
	 * replace the element by replacing the feature values, but don't replace
	 * the object if it is marked as not replaceable. recursive the feature
	 * value and replace it
	 * 
	 * @param oldObj
	 *            MethodPlugin
	 * @param newObj
	 *            MethodPlugin
	 */
	private void doReplaceElement(MethodElement oldObj, MethodElement newObj) {
		
		// check if the element is already replaced or not
		if ( isReplaced(oldObj) ) {
			return;
		}

		if ( diffMgr.selectable(oldObj) ) {
			return;
		}

		if ( !isSelected(oldObj) ) {
			return;
		}
		
		// if content description, check if it's changed or not
		if ( (oldObj instanceof ContentDescription) && (newObj instanceof ContentDescription) ) {
			if ( LibraryUtil.isIdentical((ContentDescription)oldObj, (ContentDescription)newObj) ) {
				if (debug ) {
					Resource res = oldObj.eResource();
					if ( res != null ) {
						System.out.println("Identical element not replaced: " + res.getURI().toFileString() );
					}
				}
				return;
			} 
		}
		
		// set repalced first to avoid calling into the same object recursively
		setReplaced(oldObj);
		
		if ( debug ) {
			System.out.println("Replacing element " + LibraryUtil.getTypeName(oldObj)); //$NON-NLS-1$
		}

		List properties = oldObj.getInstanceProperties();
		if (properties != null) {
			for (int i = 0; i < properties.size(); i++) {
				EStructuralFeature feature = (EStructuralFeature) properties.get(i);

				// package structure is handled by the diff tree
				// so we don't process the related feature
				if ( feature == UmaPackage.eINSTANCE.getMethodPlugin_MethodPackages()
						|| feature == UmaPackage.eINSTANCE.getMethodPackage_ChildPackages() 
						|| feature.isDerived() )
				{
					continue;
				}

				Object newValue = newObj.eGet(feature);
				setFeatureValue(oldObj, feature, newValue, true);
			}
		}

	}

	/**
	 * set the new value from the importing library to the old object in the
	 * base library. if the value or onle of values are not replaceable, suach
	 * as MethodLibrary, MethodPlugin, MethodPackage, etc, those values should
	 * be substitued with the one in the old library
	 * 
	 * @param element
	 * @param feature
	 * @param value
	 */
	

	static MethodElement newCont = null;
	static MethodElement newElem = null;
	private void setFeatureValue(MethodElement element, EStructuralFeature feature, Object newValue, boolean doDelete)
	{
		if ( canIgnore(feature) ) {
			return;
		}
		
		boolean oldNotify = element.eDeliver();
		try
		{
			// turn off notifications to avoid possible deadlock or thread issue
			element.eSetDeliver(false);
			
			if ( canReset(feature) ) {
				element.eSet(feature, newValue);
			}
			else if ( feature.isMany() && newValue instanceof List)
			{
				List oldValue = (List)element.eGet(feature);
				ElementListDiff diff = new ElementListDiff(oldValue, (List)newValue);
				if ( doDelete && diff.deletedItems.size() > 0 ) {
					oldValue.removeAll(diff.deletedItems);
					logRemovedElements(diff.deletedItems);
					if ( debug ) {
						System.out.println("  Deleting feature value, feature: " + feature.getName() + ",  element: "  //$NON-NLS-1$ //$NON-NLS-2$
								+ LibraryUtil.getTypeName(element) + ", values: " +  diff.deletedItems); //$NON-NLS-1$
					}
				}

				if ( diff.newItems.size() > 0 ) {					
					// save the new elements and fix the references later
					// note: the newly added feature values might not be REALLY
					// new element to the base library
					// it might be just a newly added reference, so check and
					// make sure
					// if it's old element, do replace again
					for ( Iterator itn = diff.newItems.iterator(); itn.hasNext(); ) {
						MethodElement newObj = (MethodElement)itn.next();
						
						// only process the new object if it's selected
						if ( isSelected(newObj) ) {
							MethodElement oldObj = diffMgr.getExistingElement(newObj.getGuid());
							if ( oldObj == null ) {
								oldValue.add(newObj);
								logNewElement(newObj);							
							} else {
								oldValue.add(oldObj);
								doReplaceElement(oldObj, newObj);
							}
						}
					}
					
					if ( debug ) {
						System.out.println("  Adding feature value, feature: " + feature.getName() + ",  element: "  //$NON-NLS-1$ //$NON-NLS-2$
								+ LibraryUtil.getTypeName(element) + ", values: " +  diff.newItems); //$NON-NLS-1$
					}
					
				}

				if ( diff.oldNewMap.size() > 0 ) {
					for ( Iterator it = diff.oldNewMap.entrySet().iterator(); it.hasNext(); ) {
						Map.Entry entry = (Map.Entry) it.next();
						MethodElement oldObj = (MethodElement)entry.getKey();
						
						// only process the new object if it's selected
						if ( !isSelected(oldObj) ) {
							continue;
						}
						
						MethodElement newObj = (MethodElement)entry.getValue();
						if ( canReset(oldObj) ) {
							if ( debug ) {
								System.out.println("  Resetting feature value, feature: " + feature.getName() + ",  element: "  //$NON-NLS-1$ //$NON-NLS-2$
										+ LibraryUtil.getTypeName(element) + ", value: " +  LibraryUtil.getTypeName(oldObj)); //$NON-NLS-1$
							}

							EcoreUtil.replace(element, feature, oldObj, newObj);
							logResetElement(newObj);
						} else {
							doReplaceElement(oldObj, newObj); 
						}
					}
				}
			}
			else if (newValue instanceof MethodElement)
			{
				// if the old and new element are the same,
				// only replace the feature value
				MethodElement o = (MethodElement)element.eGet(feature);
				MethodElement n = (MethodElement)newValue;
				
				// only process the new object if it's selected
				if ( (o == null || isSelected(o)) && isSelected(n) ) {

					if ( debug ) {
						System.out.println("  Replacinging feature value, feature: " + feature.getName() + ",  element: "  //$NON-NLS-1$ //$NON-NLS-2$
								+ LibraryUtil.getTypeName(element) + ", value: " +  LibraryUtil.getTypeName(o)); //$NON-NLS-1$
					}
	
					if ( newValue instanceof ContentDescription ) {
						// always replace content description (not reset) to
						// reserve the existing resource (xmi file)
						// since the contentDescription is unique the a method
						// element, so we don't need to compare the guid
												
						// if both object has no container, there is no such
						// description, ignore it
						if ( o.eContainer() != null || n.eContainer() != null ) {
							if ( o.eContainer() == null ) {
								element.eSet(feature, o);
							}
							doReplaceElement(o, n);						
						}
					}
					// if the element can be replaced, such as for
					// ContentDescription element
					// set the value directly
					else if ( canReset(n) ) {
						element.eSet(feature, newValue);
						logNewElement(n);
					}
					else
					{
						if ( o != null && n != null && o.getGuid().equals(n.getGuid()) ) {
							 doReplaceElement(o, n);
						}
						else {													
							registerSetElement(element, feature, newValue);
							
							element.eSet(feature, newValue);
							logNewElement(n);
						}
					}
				}
			}
			else if ( feature == UmaPackage.eINSTANCE.getNamedElement_Name() ) {
				// check difference name, if replace, need to rename resources if if has it's own resource
				// like content elements, plugins, configuirations, and process components					
				if (! (newValue instanceof String)) {
					throw new UnsupportedOperationException();
				}
				String oldName = (String)element.eGet(feature);
				if (!newValue.equals(oldName)) {
					Object[] entryVal = new Object[2];
					entryVal[0] = element;
					entryVal[1] = newValue;
					renameElementMap.put(element.getGuid(), entryVal);
				}
			} else {
				element.eSet(feature, newValue);
				
				// scan the resources
				if ( newValue instanceof String ) {
					resScanner.scan(element, newValue.toString());
				} else if (newValue instanceof URI ) {
					resScanner.copyResource ( ((URI)newValue).getPath());
				}
			}

		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
		finally
		{
			element.eSetDeliver(oldNotify);
		}
	}

	private void doAdd(EObject owner, MethodElement newObj) throws Exception {
		if ( debug ) {
			System.out.println("Adding element " + LibraryUtil.getTypeName((MethodElement)newObj)); //$NON-NLS-1$
		}

		// the owner can only be library and package
		if (owner == null) {
			return;
		}
		
		ensureUniqueName(owner, newObj, renameElementMap);
		
		boolean error = false;
		
		if (owner instanceof MethodLibrary) {			
			// can be configuration or plugin
			if (newObj instanceof MethodPlugin) {
				MethodPlugin plugin = (MethodPlugin)newObj;				
				
				// don't intitialize the storage since this will recreate the global packages
				// and such causing two set of global packages
				// 145850 - Import Configuration: CP/DPs did not shown in authering
				// ModelStorage.initialize(plugin);
				// NO, since the global packages are already created
				((MethodLibrary)owner).getMethodPlugins().add(plugin);
				LibraryUtil.saveLibrary((MethodLibrary)owner, false, false);
								
			} else if (newObj instanceof MethodConfiguration ) {
				((MethodLibrary)owner).getPredefinedConfigurations().add(newObj);
				LibraryUtil.saveLibrary((MethodLibrary)owner, false, false);
			} else {
				error = true;			
			}
		} else if ( (owner instanceof MethodPackage) 
				&& (owner.eContainer() != null) 
				&& (newObj instanceof MethodPackage) ) {
			((MethodPackage)owner).getChildPackages().add(newObj);
		} else {
			error = true;
		}
			
		if ( error ) {
			String msg = "can't add " + LibraryUtil.getTypeName(newObj) //$NON-NLS-1$
			+ " to " + LibraryUtil.getTypeName((MethodPackage)owner); //$NON-NLS-1$
			
			ImportPlugin.getDefault().getLogger().logError(msg); 
			if ( debug ) {
				System.out.println(msg);
			}
		} else {
			logNewElement(newObj);
		}
	}

	private void doIntegrityCheck()
	{		
		// now the newly added elements may have feature value element that
		// still pointing to the importing library
		// we need to replace those with the one in the base library
		while (newElements.size() > 0) {
			MethodElement newObj = (MethodElement)newElements.remove(0);
			try {
				if ( newObj instanceof DiagramElement ) {
					fixDiagram((DiagramElement)newObj);
				} else {
					fixNewElementReferences(newObj);
				}
			} catch (Exception e) {
				if (debug) {
					System.out.println("Exception while fixing new element " + LibraryUtil.getTypeName(newObj));
				}
				e.printStackTrace();
			}
		}
		
		// the configurations needs to be fixed
		List configs = baseLibrary.getPredefinedConfigurations();
		for ( Iterator it = configs.iterator(); it.hasNext(); )
		{
			MethodConfiguration config = (MethodConfiguration)it.next();
			LibraryUtil.validateMethodConfiguration(config);
		}
	}

	private List processedNewElements = new ArrayList();
	
	
	
	/**
	 * iterate all features, if the feature value references an element which is
	 * an old element, replace it with the old element. if the element is new,
	 * iterate it's containment elements
	 * 
	 * @param newObj
	 */
	private void fixNewElementReferences(MethodElement newObj) {
		if ( processedNewElements.contains(newObj) ) {
			return;
		}
		
		processedNewElements.add(newObj);
		
		if ( debug ) {
			System.out.println("Fixing element " + LibraryUtil.getTypeName(newObj)); //$NON-NLS-1$
		}
		
		List properties = newObj.getInstanceProperties();
		if (properties != null) {
			for (int i = 0; i < properties.size(); i++) {
				EStructuralFeature feature = (EStructuralFeature) properties.get(i);
				Object value = newObj.eGet(feature);

				if ( value instanceof List) {
					
					// fixing the feature value may cause the list to change
					// iterate on a new copy to avoid
					// ConcurrentModificationException
					for (Iterator it= new ArrayList(((List)value)).iterator(); it.hasNext(); ) {
						Object o = it.next();
						fixNewElementFeatureValue(newObj, feature, o);
					}
				} else {
					fixNewElementFeatureValue(newObj, feature, value);
				}
			}
		}	
	}
	
	
	private void fixDiagram(DiagramElement newObj) {		

		if ( processedNewElements.contains(newObj) ) {
			return;
		}
		
		processedNewElements.add(newObj);
		
		if ( debug ) {
			System.out.println("Fixing diagram element " + LibraryUtil.getTypeName(newObj)); //$NON-NLS-1$
		}
		
		List properties = newObj.getInstanceProperties();
		if (properties != null) {
			for (int i = 0; i < properties.size(); i++) {
				EStructuralFeature feature = (EStructuralFeature) properties.get(i);
				Object value = newObj.eGet(feature);

				if ( value instanceof List) {
					
					// fixing the feature value may cause the list to change
					// iterate on a new copy to avoid
					// ConcurrentModificationException
					for (Iterator it= new ArrayList(((List)value)).iterator(); it.hasNext(); ) {
						Object o = it.next();
						if ( o instanceof MethodElement ) {
							if ( feature == UmaPackage.eINSTANCE.getUMASemanticModelBridge_Element() ) {
								fixNewElementFeatureValue(newObj, feature, o);
							} else if ( (o instanceof DiagramElement) ) {
								fixDiagram((DiagramElement)o);
							} 
						}
					}
				} else if ( value instanceof MethodElement ) {
					if ( feature == UmaPackage.eINSTANCE.getUMASemanticModelBridge_Element() ) {
						fixNewElementFeatureValue(newObj, feature, value);
					} else if ( (value instanceof DiagramElement) ) {
						fixDiagram((DiagramElement)value);
					} 
				}
			}
		}	
	}
	
	/**
	 * check if the value is an old element in the base, if yes, replace it with
	 * the old one if it's new, iterate it's feature value
	 * 
	 * @param feature
	 * @param obj
	 */
	private void fixNewElementFeatureValue(MethodElement element, EStructuralFeature feature, Object obj) {
		if ( !(obj instanceof MethodElement) ) {
			return;
		}
		
		if ( debug ) {
			System.out.println("fixing element feature value " + LibraryUtil.getTypeName(element) + ", feature=" + feature.getName()); //$NON-NLS-1$
		}

		boolean oldNotify = element.eDeliver();
		try
		{
			element.eSetDeliver(false);
			
			MethodElement newObj = (MethodElement)obj;
			MethodElement oldObj = getReplaced(newObj.getGuid());
			if ( oldObj == null ) {
				// get existing element, for example, the global packages in the
				// configuration feature
				oldObj = diffMgr.getExistingElement(newObj.getGuid());
			}
			
			if ( oldObj == null ) {
				// it's a new one
				fixNewElementReferences(newObj);
			} else {
				
				try {
					// replace the newObj with the oldObj
					EcoreUtil.replace(element, feature, newObj, oldObj); 
				}
				catch (Exception ex) {
					// if replace failed, remove the feature
					EcoreUtil.remove(element, feature, newObj); 
					if (debug ) {
						System.out.println("Replaceing feature value failed for element [" 
								+ LibraryUtil.getTypeName(element) + "], feature [" 
								+ feature.getName() + "], value=" 
								+ newObj + ". The feature value is removed. ");
						
					}
				}
			}	
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
		finally
		{
			element.eSetDeliver(oldNotify);
		}
	}
	
	private boolean canReset(MethodElement e) {
		boolean reset = false;
		
		// an element can be reset if you are sure that no other element(s)
		// reference to it
		// reset elements may have references to other elements in the importing
		// library, that needs to be fixed later
		
		// for anything contained by ContentDescription, reset it
		if ( e == null /* || e.eContainer() instanceof ContentDescription */
				|| e instanceof DiagramElement) {
			reset = true;
		}
				
		return reset;
	}

	private boolean canReset(EStructuralFeature feature) {
		// for anything contained by ContentDescription, reset it
		// no don't reset the sections since it will cause the one in the
		// importing library removed
		// which caused problem for TaskDescription where the same feature
		// (Steps) can't find the steps from the importing library
		// which then will treat the steps in the base library being deteted
// if ( feature == UmaPackage.eINSTANCE.getContentDescription_Sections()) {
// return true;
// }
		return false;
	}

	private boolean canIgnore(EStructuralFeature feature) {
		// don't reset the plugin lock status
		if ( feature == UmaPackage.eINSTANCE.getMethodPlugin_UserChangeable()) {
			return true;
		}

		// 146144 - Import Configuration: some unchange elements disappear in authoring after import
		// this is caused by a bug in ArtifactImpl, when setContainerArtifact to null, 
		//  it set the eContainer to null, if the eContainer is a content package, 
		// the artifact is removed from the package
		//  this bug will be fixed in M4. 
		// for now, ignore this feature since this is an opposite feature, we don't need to handle it	
		if ( feature == UmaPackage.eINSTANCE.getArtifact_ContainerArtifact() ) {
			return true;
		}
		return false;
	}
	
	private boolean isReplaced(MethodElement oldObj) {
		return replacedElementMap.containsKey(oldObj.getGuid());
	}

	private void setReplaced(MethodElement oldObj) {
		String guid = oldObj.getGuid();
		if ( !replacedElementMap.containsKey(guid) ) {
			replacedElementMap.put(guid, oldObj);
			setModified(oldObj);
		}
	}
	
	/**
	 * get the old element been replaced
	 * 
	 * @param oldObj
	 * @return
	 */
	private MethodElement getReplaced(String guid) {
		return (MethodElement)replacedElementMap.get(guid);
	}
	
	/**
	 * when new elements are added into the library, the associated resource
	 * files must be copied over as well
	 * 
	 * @param newObj
	 *            MethodElement
	 */
	private void copyNewElementResources(MethodElement newObj) {
		
		List properties = newObj.getInstanceProperties();
		if (properties != null) {
			for (int i = 0; i < properties.size(); i++) {
				EStructuralFeature feature = (EStructuralFeature) properties.get(i);
				Object value = newObj.eGet(feature);
				if ( value instanceof URI ) {
					resScanner.copyResource(((URI)value).getPath());
				} else if ( value instanceof String ) {
					resScanner.scan(newObj, (String)value);
				}
			}
		}
		
		for ( Iterator it = newObj.eContents().iterator(); it.hasNext(); ) {
			Object o = it.next();
			if ( o instanceof MethodElement ) {
				copyNewElementResources((MethodElement)o);
			}
		}
	}
	
	private void logNewElement(MethodElement newObj) {
		
		// make sure the element is not in the original library
		// since we have the guid-element map build up in the diff manager,
		// check there
		if ( !newElements.contains(newObj) && diffMgr.getExistingElement(newObj.getGuid()) == null) {
			newElements.add(newObj);
			copyNewElementResources(newObj);
			setModified(newObj);
		}
	}
	
	private void logResetElement(MethodElement newObj) {
		if ( !newElements.contains(newObj) ) {
			newElements.add(newObj);
			setModified(newObj);
		}
	}

	private void logNewResource(Resource res ) {
		if ( res != null ) {
			
			// mark the resource as dirty so that we can save it again
			// the first save may lose cross references 
			res.setModified(true);
			String file = res.getURI().toFileString();
			if ( !newResources.contains(file) ) {
				newResources.add(file);
			}
		}
	}
	
	private void logRemovedElements(List items) {
		
		if ( items == null || items.size() == 0 ) {
			return;
		}
		
		// removed elements can be removed from any feature
		// check if the element's container is null
		// if is, then the element is deleted and the resource should be deleted
		// as well
		for (Iterator it = items.iterator(); it.hasNext(); ) {
			MethodElement e = (MethodElement)it.next();
			if ( (e.eContainer() == null) && !deletedElements.contains(e) ) {			
				deletedElements.add(e);
			}			
		}
	}
	
	private void setModified(EObject obj) {
		if ( obj != null ) {
			Resource res = obj.eResource();
			if ( res != null ) {
				if ( !res.isModified() ) {
					res.setModified(true);				
				}
// } else {
// System.out.println("No resource for " + obj);
				
				if (debug && res.isModified() ) {
					System.out.println("Modified: " + res.getURI().toFileString());
				}

			}
		}
	}

	private List getModifiedResources(MethodLibrary lib) {
		
		// avoid the newly added element resources
		List modifiedList = new ArrayList();
		Resource res = lib.eResource();
		if ( res != null ) {
			ResourceSet resSet = res.getResourceSet();
			for ( Iterator it = resSet.getResources().iterator(); it.hasNext(); ) {
				res = (Resource) it.next();
				if ( res != null && res.isModified() ) {
					String file = res.getURI().toFileString();
					if ( newResources.contains(file) ) {
						continue;
					}
					
					if ( debug ) {
						System.out.println("Resource modified: " + file);
					}		
					modifiedList.add(file);
				}
			}
		}
		return modifiedList;
	}

	// clear resources for deleted elements
	private void deleteResoruces() {
		if ( deletedElements.size() == 0 ) {
			return;
		}
		
		IFileManager fileMgr = ContentDescriptionFactory.getFileManager();

		for (Iterator it = deletedElements.iterator(); it.hasNext(); ) {
			MethodElement e = (MethodElement)it.next();
			
			EObject obj = null;
			if ( e instanceof DescribableElement ) {
				obj = ((DescribableElement)e).getPresentation();
			} else if ( e instanceof ContentDescription ) {
				obj = e;
			}
			if ( obj != null ) {
				Resource res = obj.eResource();
				if ( res != null ) {
					String file = res.getURI().toFileString();
					if ( debug ) {
						System.out.println("deleting resource: " + file);
					}
					
					if ( !fileMgr.delete(file) ) {
						if ( debug ) {
							System.out.println("unable to delete file: " + file);
						}
					}
				}								
			}
		}
	}

	
	public class ElementListDiff {

		List newItems = new ArrayList();
		List deletedItems = new ArrayList();
		
		// use LinkedHashMap to reserve the order of the values
		Map oldNewMap = new LinkedHashMap();

		// with the old and new list of MethodElements,
		// find the common ones, the deleted ones, and the new ones
		public ElementListDiff(List oldList, List newList) {
			// Map oldListGuidMap = getGuidMap(oldList);
			Map newListGuidMap = getGuidMap(newList);

			newItems.addAll(newList);

			for (Iterator it = oldList.iterator(); it.hasNext(); ) {
				MethodElement oldObj = (MethodElement)it.next();
				String guid = oldObj.getGuid();
				Object newObj = newListGuidMap.get(guid);
				if ( newObj != null ) {
					oldNewMap.put(oldObj, newObj);
					newItems.remove(newObj);
				}
				else
				{
					// it's been deleted
					deletedItems.add(oldObj);
				}
			}
		}

		private Map getGuidMap(List items) {
			Map m = new HashMap();
			for (Iterator it= items.iterator(); it.hasNext(); ) {
				MethodElement e = (MethodElement)it.next();
				m.put(e.getGuid(), e);
			}

			return m;
		}
	}	
	
	public static void handleNameReplace(Map renameElementMap) {
		Iterator it = renameElementMap.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry entry = (Map.Entry) it.next();
			Object[] val = (Object[])entry.getValue();
			
			MethodElement element = (MethodElement) val[0];
			String newName = val.length == 4 ? element.getGuid() : (String) val[1];
			rename(element, newName);
			if (val.length == 4) {
				element = (MethodElement) val[2];
				newName = (String) val[3];
				rename(element, newName);
				
				element = (MethodElement) val[0];
				newName = (String) val[1];
				rename(element, newName);
			}
		}
		
		//Make resouces dirty again, as rename process would mark resouce un-modified
		it = renameElementMap.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry entry = (Map.Entry) it.next();
			Object[] val = (Object[])entry.getValue();			
			MethodElement element = (MethodElement) val[0];
			element.eResource().setModified(true);
		}
	}
	
	private static void rename(MethodElement element, String newName) {
		if (newName.equals(element.getName())) {
			return;
		}
		if (element instanceof ContentDescription) {
			element.setName(newName);
		} else {
			LibraryView.runRename(element, newName);
		}
	}
		
	private void registerSetElement(MethodElement element, EStructuralFeature feature, Object newValue) {
		String guid0 = ((MethodElement)newValue).getGuid();
		String guid1 = element.getGuid();
		Object[] val = new Object[3];
		val[0] = feature;
		val[1] = newValue;
		val[2] = element;
		if (setElementMap == null) {
			setElementMap = new HashMap();
		}
		setElementMap.put(element.getName() + guid0 + guid1, val);
	}
	
	private void handleSetElements() {
		if (setElementMap == null) {
			return;
		}
		for (Iterator it = setElementMap.entrySet().iterator(); it.hasNext();) {
			Map.Entry entry = (Map.Entry) it.next();
			Object[] val = (Object[])entry.getValue();
			EStructuralFeature feature = (EStructuralFeature) val[0];
			MethodElement newValue = (MethodElement) val[1];
			MethodElement element = (MethodElement) val[2];
			String guid = newValue.getGuid();
			MethodElement mergedValue = (MethodElement) replacedElementMap.get(guid);
			if (mergedValue != null && mergedValue != newValue) {
				element.eSet(feature, mergedValue);
			}			
		}
	}

	private static void ensureUniqueName(EObject owner, MethodElement newObj, Map renameElementMap) {
		Class cls = newObj.getClass();
		Map nameMap = new HashMap();
		for (int i=0; i < owner.eContents().size(); i++) {
			Object oldObj = owner.eContents().get(i);
			if (oldObj.getClass() == cls) {
				MethodElement oldElem = (MethodElement) oldObj;
				nameMap.put(oldElem.getName(), oldElem);
			}
		}
		String name = newObj.getName();
		String renamed = name;
		while (nameMap.containsKey(renamed)) {
			renamed += "_renamed";
		}
		if (renamed != name) {
			newObj.setName(renamed);
			
			Object[] entryVal = new Object[4];
			entryVal[0] = newObj;
			entryVal[1] = name;
			entryVal[2] = nameMap.get(name);
			entryVal[3] = renamed;
			renameElementMap.put(newObj.getGuid(), entryVal);			
		}
	}

}
