//------------------------------------------------------------------------------
// 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.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.EList;
import org.eclipse.epf.importing.ImportPlugin;
import org.eclipse.epf.library.edit.util.ModelStructure;
import org.eclipse.epf.library.edit.util.TngUtil;
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;


/**
 * Manages the differences between method elements in the current library and
 * those that will be imported.
 * 
 * @author Jinhua Xi
 * @since 1.0
 */
public class LibraryDiffManager {

	private MethodLibrary baseLibrary;

	private MethodLibrary importLibraty;

	// Map of uid to ElementDiffTree, the root of the ElementDiffTree is the
	// library.
	private Map diffElementMap = new HashMap();

	// Map of uid to element for import library configuration.
	private Map importElementMap = new HashMap();

	// id to element map of the current library.
	private Map currentElementMap = new HashMap();

	// The diff tree.
	ElementDiffTree rootDiffTree = null;

	private boolean debug = ImportPlugin.getDefault().isDebugging();

	public LibraryDiffManager(MethodLibrary baseLibrary,
			MethodLibrary importLibraty) {
		this.baseLibrary = baseLibrary;
		this.importLibraty = importLibraty;

		rootDiffTree = new ElementDiffTree(baseLibrary, importLibraty);
	}

	public MethodLibrary getImportingLibrary() {
		return this.importLibraty;
	}

	public ElementDiffTree buildDiffTree() {
		// Build the uid to element map for the import library.
		buildUIDMap(importLibraty.eContents(), importElementMap, true, false);

		buildUIDMap(baseLibrary.eContents(), currentElementMap, true, true);

		// Get all the system packages in each plug-in so we can filter out
		// those packages when comparing the libraries.

		// Iterate the element in the current library, and build a diff tree by
		// comparing to the import library.
		List plugins = baseLibrary.getMethodPlugins();
		for (Iterator it = plugins.iterator(); it.hasNext();) {
			MethodPlugin plugin = (MethodPlugin) it.next();
			List systemPackages = TngUtil.getAllSystemPackages(plugin);
			
			// content category packages should be included into the diff tree 
			// otherwise the category information will be lost
			List categoryList = TngUtil.getContentCategoryPackages(plugin);
			for ( Iterator itc = categoryList.iterator(); itc.hasNext(); ) {
				Object o = itc.next();
				if ( systemPackages.contains(o) ) {
					systemPackages.remove(o);
				}
			}
			
			// the root customcategoty package needs to be included into the diff tree
			int i = 0;
			while (i < systemPackages.size() ) {
				MethodPackage pkg = (MethodPackage)systemPackages.get(i);
				if ( TngUtil.isRootCutomCategoryPackage(pkg) ) {
					systemPackages.remove(pkg);
					break;
				} else {
					i++;
				}
			}
			
			// Build the diff tree by iterating the current library.
			iterateElement(plugin, rootDiffTree, systemPackages);
		}

		List configs = baseLibrary.getPredefinedConfigurations();
		for (Iterator it = configs.iterator(); it.hasNext();) {
			MethodConfiguration config = (MethodConfiguration) it.next();

			// Build the diff tree by iterating the current library.
			iterateElement(config, rootDiffTree, new ArrayList());
		}

		// Process the new elements in the import library and add them to the
		// diff tree.
		handleNewElements(importLibraty);

		return rootDiffTree;
	}

	public ElementDiffTree getDiffTree() {
		return rootDiffTree;
	}

	public Map getDiffTreeMap() {
		return diffElementMap;
	}

	/**
	 * Iterates the element in the current library and build a diff tree by
	 * comparing to the import library.
	 */
	private void iterateElement(MethodElement element,
			ElementDiffTree diffTree, List systemPackages) {
		if (!selectable(element)) {
			return;
		}

		// Update the diffelement UID map.
		if (!systemPackages.contains(element)
				&& !(element instanceof MethodLibrary)) {
			String uid = element.getGuid();
			ElementDiffTree diffChild = new ElementDiffTree(element,
					(MethodElement) importElementMap.get(uid));
			diffElementMap.put(uid, diffChild);
			diffTree.addChild(diffChild);
			diffTree = diffChild;
		}

		// Check with the importing element and create an ElementDiffInfo object
		EList children = element.eContents();
		if (children != null) {
			for (Iterator it = children.iterator(); it.hasNext();) {
				Object e = it.next();
				if (e instanceof MethodElement) {
					MethodElement child = (MethodElement) e;
					if (selectable(child)) {
						iterateElement(child, diffTree, systemPackages);
					}
				} else {
					if (debug ) {
						System.out
								.println("Error! " + e + " is not a MethodElement object"); //$NON-NLS-1$ //$NON-NLS-2$
					}
				}
			}
		}
	}

	/**
	 * If there are newly added elements in the importing library, add the new
	 * elements into the parent
	 */
	private void handleNewElements(MethodElement element) {
		if (!selectable(element)) {
			return;
		}

		if (isNewElement(element)) {
			// This is a new element, find the parent in the base library.
			MethodElement parent = (MethodElement) element.eContainer();
			while (isNewElement(parent)) {
				element = parent;
				parent = (MethodElement) parent.eContainer();
			}

			// Always merge libray plugins even it's from a different library.
			if (parent instanceof MethodLibrary) {
				parent = baseLibrary;
			}

			// Need to find the parent element in the current library so that we
			// know where to add the new element.
			MethodElement lib_parent = (MethodElement) currentElementMap
					.get(parent.getGuid());
			if (lib_parent == null) {
				lib_parent = baseLibrary;
			}
			ElementDiffTree elementDiffTree = new ElementDiffTree(null,
					element, lib_parent);

			// Find the parent diff tree.
			ElementDiffTree parentDiffTree = null;
			while ((parentDiffTree == null) && (parent != null)) {
				parentDiffTree = (ElementDiffTree) diffElementMap.get(parent
						.getGuid());
				parent = (MethodElement) parent.eContainer();
			}

			if (parentDiffTree == null) {
				parentDiffTree = rootDiffTree;
			}

			parentDiffTree.addChild(elementDiffTree);
		} else {
			// Iterate the children.
			List children = element.eContents();
			for (Iterator itc = children.iterator(); itc.hasNext();) {
				MethodElement e = (MethodElement) itc.next();
				handleNewElements(e);
			}

		}
	}

	private boolean isNewElement(MethodElement element) {
		if (element == null || element instanceof MethodLibrary) {
			return false;
		}
		return !currentElementMap.containsKey(element.getGuid());
	}

	private void buildUIDMap(List elements, Map elementUIDMap, boolean recursive, boolean full) {
		MethodElement element;
		for (Iterator it = elements.iterator(); it.hasNext();) {
			Object e = it.next();
			if (e instanceof MethodElement) {
				element = (MethodElement) e;
				if (selectable(element) || full) {
					String uid = element.getGuid();
					elementUIDMap.put(uid, element);

					if (recursive) {
						buildUIDMap(element.eContents(), elementUIDMap,
								recursive, full);
					}
				}
			} else {
				ImportPlugin.getDefault().getLogger().logError(
							"Import error. " + e + " is not a MethodElement object"); //$NON-NLS-1$ //$NON-NLS-2$
			}
		}

	}

	public boolean selectable(MethodElement element) {
		return (element instanceof MethodLibrary
				|| element instanceof MethodPlugin
				|| element instanceof MethodPackage
				|| element instanceof MethodConfiguration);
	}

	public MethodElement getExistingElement(String guid) {
		return (MethodElement)currentElementMap.get(guid);
	}

}
