//------------------------------------------------------------------------------
// 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.persistence.migration;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.util.FeatureMap.Entry;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.BasicResourceHandler;
import org.eclipse.emf.ecore.xml.type.AnyType;
import org.eclipse.epf.persistence.MultiFileResourceSetImpl;
import org.eclipse.epf.uma.Activity;
import org.eclipse.epf.uma.ContentDescription;
import org.eclipse.epf.uma.ContentPackage;
import org.eclipse.epf.uma.DescribableElement;
import org.eclipse.epf.uma.ProcessElement;
import org.eclipse.epf.uma.ProcessPackage;
import org.eclipse.epf.uma.UmaFactory;
import org.eclipse.epf.uma.UmaPackage;
import org.eclipse.epf.uma.WorkBreakdownElement;
import org.eclipse.epf.uma.WorkOrder;
import org.eclipse.epf.uma.WorkOrderType;

/**
 * Resource handler for migrating UMA library from one version to another.
 * 
 * @author Phong Nguyen Le
 * @since 1.0
 */
public class UMA2UMAResourceHandler extends BasicResourceHandler {

	private static final boolean DEBUG = true;

	private List moveInfos;

	private Set procMovedFeatureNames;

	private Map guidToPresentationMap;

	private Set classNamesToSavePresentationURI;

	public UMA2UMAResourceHandler() {
		moveInfos = new ArrayList();

		guidToPresentationMap = new HashMap();

		procMovedFeatureNames = new HashSet(Arrays.asList(new String[] {
				"purpose" //$NON-NLS-1$
				, "keyConsiderations" //$NON-NLS-1$
				, "alternatives" //$NON-NLS-1$
				, "howtoStaff" //$NON-NLS-1$
				, "usageGuidance" //$NON-NLS-1$
				, "externalId" //$NON-NLS-1$
				, "scope" //$NON-NLS-1$
				, "usageNotes" //$NON-NLS-1$
				, "scale" //$NON-NLS-1$
				, "projectCharacteristics" //$NON-NLS-1$
				, "riskLevel" //$NON-NLS-1$
				, "estimatingTechnique" //$NON-NLS-1$
				, "projectMemberExpertise" //$NON-NLS-1$
				, "typeOfContract" //$NON-NLS-1$
				, "techniques" //$NON-NLS-1$
				, "mainDescription" //$NON-NLS-1$
		}));

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.ecore.xmi.impl.BasicResourceHandler#postLoad(org.eclipse.emf.ecore.xmi.XMLResource,
	 *      java.io.InputStream, java.util.Map)
	 */
	public void postLoad(XMLResource resource, InputStream inputStream,
			Map options) {
		if (!resource.getEObjectToExtensionMap().isEmpty()) {
			if (DEBUG) {
				System.out
						.println("UMA2UMAResourceHandler.postLoad(): " + resource.getURI()); //$NON-NLS-1$
				System.out.println("---- Start unknown features ----"); //$NON-NLS-1$
			}
			// System.out.println(resource.getEObjectToExtensionMap());
			for (Iterator iter = resource.getEObjectToExtensionMap().entrySet()
					.iterator(); iter.hasNext();) {
				Map.Entry entry = (Map.Entry) iter.next();
				if (entry.getKey() instanceof EObject) {
					// handle moved data
					//
					handleMovedData((EObject) entry.getKey(), entry.getValue());
				}

				// String str;
				// if(entry.getKey() instanceof MethodElement) {
				// MethodElement e = (MethodElement)entry.getKey();
				// str = e.getGuid() + " - " + e.getName() + "(" +
				// e.getClass().getName() + ")";
				// }
				// else {
				// str = entry.getKey().toString();
				// }
				// str += " = " + entry.getValue();
				// System.out.println(str);
			}
			if (DEBUG)
				System.out.println("---- End unknown features ----"); //$NON-NLS-1$
		}
	}

	private static String getText(AnyType value) {
		try {
			if (value == null)
				return null;
			FeatureMap.Entry entry = (FeatureMap.Entry) value.getMixed().get(0);
			return (String) entry.getValue();
		} catch (RuntimeException e) {
			throw e;
		}
	}

	protected static EAttribute getEAttribute(EClass eClass, String name) {
		for (Iterator iterator = eClass.getEAllAttributes().iterator(); iterator
				.hasNext();) {
			EAttribute attrib = (EAttribute) iterator.next();
			if (attrib.getName().equals(name)) {
				return attrib;
			}
		}
		return null;
	}

	/**
	 * Subclass can override this method to customize rules to move data
	 * 
	 * @param owner
	 * @param featureName
	 * @return
	 */
	protected EStructuralFeature getNewFeature(EObject owner, String featureName) {
		if ("presentationName".equals(featureName) && owner instanceof ContentDescription) { //$NON-NLS-1$
			return UmaPackage.eINSTANCE
					.getDescribableElement_PresentationName();
		}
		if (procMovedFeatureNames.contains(featureName)) {
			if (owner instanceof ProcessElement) {
				ContentDescription content = ((ProcessElement) owner)
						.getPresentation();
				EAttribute attrib = getEAttribute(content.eClass(), featureName);
				if (attrib != null) {
					return attrib;
				}
			}
		}
		if ("WorkOrder".equals(featureName) && owner instanceof ProcessPackage) { //$NON-NLS-1$
			return UmaPackage.eINSTANCE.getProcessPackage_ProcessElements();
		}
		return null;
	}

	/**
	 * Subclass can override this method to customize rules to move data
	 * 
	 * @param oldOwner
	 * @param featureName
	 * @return
	 */
	protected EObject getNewOwner(EObject oldOwner, String featureName) {
		if ("presentationName".equals(featureName) && oldOwner instanceof ContentDescription) { //$NON-NLS-1$
			DescribableElement de = ((DescribableElement) oldOwner.eContainer());
			if (de instanceof Activity) {
				return null;
			}
			return de;
		}
		if (procMovedFeatureNames.contains(featureName)
				&& oldOwner instanceof ProcessElement) {
			return ((ProcessElement) oldOwner).getPresentation();
		}
		if ("WorkOrder".equals(featureName) && oldOwner instanceof ProcessPackage) { //$NON-NLS-1$
			return oldOwner;
		}

		return null;
	}

	protected Object getNewValue(EObject oldOwner, String featureName,
			AnyType value) {
		if ("WorkOrder".equals(featureName) && oldOwner instanceof ProcessPackage) { //$NON-NLS-1$
			WorkOrder workOrder = UmaFactory.eINSTANCE.createWorkOrder();
			workOrder.setGuid((String) getSingleValue(value.getAnyAttribute(),
					"guid")); //$NON-NLS-1$
			String predGuid = (String) getSingleValue(value.getAnyAttribute(),
					"pred"); //$NON-NLS-1$
			if (predGuid == null) {
				InternalEObject obj = (InternalEObject) getSingleValue(value
						.getMixed(), "pred"); //$NON-NLS-1$
				predGuid = obj.eProxyURI().authority();
			}
			MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) oldOwner
					.eResource().getResourceSet();
			WorkBreakdownElement pred = (WorkBreakdownElement) resourceSet
					.getEObject(predGuid);
			workOrder.setPred(pred);
			workOrder.setLinkType(WorkOrderType.FINISH_TO_FINISH_LITERAL);
			return workOrder;
		}
		return getText(value);
	}

	public void moveData() {
		for (Iterator iter = moveInfos.iterator(); iter.hasNext();) {
			MoveInfo info = (MoveInfo) iter.next();
			EObject newOwner = getNewOwner(info.oldOwner, info.oldFeatureName);
			if (newOwner != null) {
				setValue(newOwner, info.newFeature, info.value);
			}
		}
	}

	public static void setValue(EObject eObject, EStructuralFeature feature,
			Object value) {
		if (!feature.isMany()) {
			eObject.eSet(feature, value);
		} else {
			Collection values = (Collection) eObject.eGet(feature);
			if (value instanceof Collection) {
				values.addAll((Collection) value);
			} else {
				values.add(value);
			}
		}

	}

	private static class MoveInfo {
		EObject oldOwner;

		String oldFeatureName;

		EStructuralFeature newFeature;

		Object value;

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#toString()
		 */
		public String toString() {
			return new StringBuffer()
					.append(getClass().getName())
					.append("(\n") //$NON-NLS-1$
					.append("  oldFeatureName: ").append(oldFeatureName).append('\n') //$NON-NLS-1$
					.append("  newFeature: ").append(newFeature).append('\n') //$NON-NLS-1$
					.append("  value: ").append(value).append('\n') //$NON-NLS-1$
					.append(")").toString(); //$NON-NLS-1$
		}
	}

	private void handleMovedData(EObject owner, FeatureMap featureMap) {
		for (Iterator iter = featureMap.iterator(); iter.hasNext();) {
			FeatureMap.Entry entry = (FeatureMap.Entry) iter.next();
			EStructuralFeature f = entry.getEStructuralFeature();
			EStructuralFeature newFeature = getNewFeature(owner, f.getName());
			// System.out.println("UMA2UMAResourceHandler.handleMovedData():");
			// System.out.println(" feature: " + f.getName());
			// System.out.println(" owner: " + owner);
			if (newFeature != null) {
				MoveInfo moveInfo = new MoveInfo();
				moveInfo.oldOwner = owner;
				moveInfo.oldFeatureName = f.getName();
				moveInfo.newFeature = newFeature;
				moveInfo.value = entry.getValue() instanceof AnyType ? getNewValue(
						owner, f.getName(), (AnyType) entry.getValue())
						: entry.getValue();
				moveInfos.add(moveInfo);
				if (DEBUG) {
					System.out.println(moveInfo);
					System.out.println();
				}
				iter.remove();
			}
		}
	}

	/**
	 * @param key
	 * @param value
	 */
	protected void handleMovedData(EObject e, Object movedData) {
		AnyType anyType = (AnyType) movedData;

		handleMovedData(e, anyType.getMixed());

		handleMovedData(e, anyType.getAnyAttribute());

		if (classNamesToSavePresentationURI != null
				&& e instanceof ContentPackage) {
			for (Iterator iter = getValue(anyType.getMixed(), "contentElements").iterator(); iter.hasNext();) { //$NON-NLS-1$
				AnyType value = (AnyType) iter.next();
				if (value != null
						&& classNamesToSavePresentationURI.contains(value
								.eClass().getName())) {
					Object presentation = getSingleValue(value.getMixed(),
							"presentation"); //$NON-NLS-1$
					if (presentation != null) {
						Object guid = getSingleValue(value.getAnyAttribute(),
								"guid"); //$NON-NLS-1$
						URI uri = ((InternalEObject) presentation).eProxyURI();
						guidToPresentationMap.put(guid, uri);
					}
				}
			}
		}
	}

	public void savePresentationURIFor(Set classNames) {
		classNamesToSavePresentationURI = classNames;
	}

	public URI getPresentationURI(String guid) {
		return (URI) guidToPresentationMap.get(guid);
	}

	public static Object getSingleValue(FeatureMap featureMap,
			String featureName) {
		for (Iterator iter = featureMap.iterator(); iter.hasNext();) {
			FeatureMap.Entry entry = (Entry) iter.next();
			if (entry.getEStructuralFeature().getName().equals(featureName)) {
				return entry.getValue();
			}
		}
		return null;
	}

	protected static Collection getValue(FeatureMap featureMap,
			String featureName) {
		Collection list = new ArrayList();
		for (Iterator iter = featureMap.iterator(); iter.hasNext();) {
			FeatureMap.Entry entry = (Entry) iter.next();
			if (entry.getEStructuralFeature().getName().equals(featureName)) {
				list.add(entry.getValue());
			}
		}
		return list;
	}

	public void clearMoveInfos() {
		moveInfos.clear();
	}

}
