/*******************************************************************************
 * Copyright (c) 2003, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 * IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.common.internal.emf.utilities;

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

import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.ETypedElement;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;


/**
 * Insert the type's description here. Creation date: (12/13/2000 9:10:55 PM)
 * 
 * @author: Administrator
 */
public class EtoolsCopyUtility {
	protected static final String REFENUMLITERAL_TYPE_NAME = "EEnumLiteral"; //$NON-NLS-1$
	private final EcorePackage pkg = EcorePackage.eINSTANCE;

	private final ETypedElement METAENAME = pkg.getENamedElement_Name();

	protected HashMap copiedObjects;
	protected List deferredReferenceCopies;
	protected boolean preserveIds = false;
	protected boolean copyAdapters = false;
	protected HashMap copiedAdapters;

	protected class DeferredSingleReferenceCopyAction extends DeferredReferenceUtilityAction {
		public DeferredSingleReferenceCopyAction(EReference aReference, Object aValue, String aSuffix, EObject aCopyContainer) {
			super(aReference, aValue, aSuffix, aCopyContainer);
		}

		@Override
		public void performAction() {
			EObject value = (EObject) getReferenceValue();
			getCopyContainer().eSet(getReference(), getCopyIfFound(value));
		}
	}

	protected class DeferredManyReferenceCopyAction extends DeferredReferenceUtilityAction {
		public DeferredManyReferenceCopyAction(EReference aReference, List aValue, String aSuffix, EObject aCopyContainer) {
			super(aReference, aValue, aSuffix, aCopyContainer);
		}

		@Override
		public void performAction() {
			List copyList = (List) getCopyContainer().eGet(getReference());
			Iterator it = ((List) getReferenceValue()).iterator();
			EObject next;
			while (it.hasNext()) {
				next = (EObject) it.next();
				copyList.add(getCopyIfFound(next));
			}
		}
	}

	/**
	 * Insert the method's description here. Creation date: (12/13/2000 9:14:26 PM)
	 * 
	 * @param aRefObject
	 *            org.eclipse.emf.ecore.EObject
	 */
	public EtoolsCopyUtility() {
	}

	/**
	 * Create a <code>DeferredManyReferenceCopyAction</code> and add it to the list of deferred
	 * copy actions.
	 */
	protected void addDeferredManyReferenceCopy(EReference reference, List aValue, String idSuffix, EObject aCopyContainer) {
		getDeferredReferenceCopies().add(new DeferredManyReferenceCopyAction(reference, aValue, idSuffix, aCopyContainer));
	}

	/**
	 * Create a <code>DeferredSingleReferenceCopyAction</code> and add it to the list of deferred
	 * copy actions.
	 */
	protected void addDeferredSingleReferenceCopy(EReference reference, EObject aValue, String idSuffix, EObject aCopyContainer) {
		getDeferredReferenceCopies().add(new DeferredSingleReferenceCopyAction(reference, aValue, idSuffix, aCopyContainer));
	}

	/**
	 * Check for an already copied object first.
	 */
	protected EObject containmentCopy(EObject anObject, String idSuffix) {
		EObject copied = getCopy(anObject);
		if (copied == null)
			copied = primCopy(anObject, idSuffix);
		return copied;
	}

	/**
	 * Check for an already copied object first.
	 */
	protected EObject containmentCopyObject(EObject anObject, String idSuffix) {
		EObject copied = getCopy(anObject);
		if (copied == null)
			copied = primCopyObject(anObject, idSuffix);
		return copied;
	}

	protected Resource containmentCopy(Resource aResource, String newUri) {
		/* copied resources are never cached */
		return primCopy(aResource, newUri);
	}

	/**
	 * Copy all Resources and RefObjects within <code>aGroup</code> and add them to
	 * <code>aGroup</code>. Non composite references will be deferred until all objects are
	 * copied from <code>aGroup</code>.
	 * 
	 * Copy Resources first and then copy RefObjects.
	 */
	public void copy(CopyGroup aGroup) {
		if (aGroup != null) {
			EtoolsCopySession session = new EtoolsCopySession(this);
			session.setPreserveIds(aGroup.getPreserveIds());
			session.copy(aGroup);
			session.flush();
		}
	}

	public EObject copy(EObject aRefObject) {
		return copy(aRefObject, null);
	}

	/**
	 * This method should be used if you are only going to copy <code>aRefObject</code> in this
	 * copy execution.
	 */
	public EObject copy(EObject aRefObject, String idSuffix) {
		EtoolsCopySession session = new EtoolsCopySession(this);
		EObject copied = session.copy(aRefObject, idSuffix);
		session.flush();
		return copied;
	}

	/**
	 * This method should be used if you are only going to copy <code>aRefObject</code> in this
	 * copy execution. This method only copies <code>aRefObject</code> with its properties, not
	 * references.
	 */
	public EObject copyObject(EObject aRefObject, String idSuffix) {
		EtoolsCopySession session = new EtoolsCopySession(this);
		EObject copied = session.copyObject(aRefObject, idSuffix);
		session.flush();
		return copied;
	}

	/**
	 * This method should be used if you are only going to copy <code>aResource</code> in this
	 * copy execution. The copied Resource will have a URI equal to <code>newUri</code>.
	 */
	public Resource copy(Resource aResource, String newUri) {
		EtoolsCopySession session = new EtoolsCopySession(this);
		Resource copied = session.copy(aResource, newUri);
		session.flush();
		return copied;
	}

	/**
	 * If an attribute is set and its value is not <code>null</code>, then perform copy
	 * <code>attribute</code> value from <code>aRefObject</code> to <code>copyRef</code>.
	 * Null is a valid value if the attribute is set.
	 */

	protected void copyAttribute(EAttribute attribute, EObject aRefObject, String idSuffix, EObject copyRef) {
		if (!attribute.isChangeable())
			return; //ignore
		if (attribute.isMany()) {
			List value = (List) aRefObject.eGet(attribute);
			if (value != null)
				copyManyAttribute(attribute, value, aRefObject, idSuffix, copyRef);
		} else if (aRefObject.eIsSet(attribute)) {
			Object value = aRefObject.eGet(attribute);
			if (value == null)
				copyRef.eSet(attribute, value);
			else
				copySingleAttribute(attribute, value, aRefObject, idSuffix, copyRef);
		} else if (attribute == METAENAME) {
			//set name to the ID (this is computed as a default), only get here if isSetName is
			// false.
			copyRef.eSet(attribute, aRefObject.eGet(attribute));
		}
	}

	/**
	 * Iterate over the attributes of the receiver and copy each attribute.
	 */
	protected void copyCurrentAttributes(EObject aRefObject, String idSuffix, EObject copyRef) {
		List attributes = aRefObject.eClass().getEAllAttributes();
		if (attributes != null) {
			Iterator it = attributes.iterator();
			EAttribute ra;
			while (it.hasNext()) {
				ra = (EAttribute) it.next();
				copyAttribute(ra, aRefObject, idSuffix, copyRef);
			}
		}
	}

	/**
	 * This method will iterate over the references of the receiver. If a reference's value is not
	 * <code>null</code>, then a series of tests are done before setting the value with the
	 * copied object, <code>copyRef</code>.
	 * 
	 * Tests: 1. Do nothing if the reference equals the metaEContainer or metaEContains references.
	 */

	protected void copyCurrentReferences(EObject aRefObject, String idSuffix, EObject copyRef) {
		List references = aRefObject.eClass().getEAllReferences();
		if (references != null) {
			Iterator it = references.iterator();
			EReference rr;
			while (it.hasNext()) {
				rr = (EReference) it.next();
				if (shouldCopyReference(rr))
					copyReference(rr, aRefObject, idSuffix, copyRef);
			}
		}
	}

	/**
	 * Iterate over <code>anExtent</code> and copy each element to <code>copyExtent</code>.
	 */
	protected void copyExtent(EList anExtent, EList copyExtent) {
		Iterator it = anExtent.iterator();
		EObject refObject;
		while (it.hasNext()) {
			refObject = (EObject) it.next();
			copyExtent.add(containmentCopy(refObject, null));
		}
	}

	/**
	 * Copy a many value attribute which is treated as a many valued reference.
	 * 
	 * Tests:
	 * 
	 * 1. If the type is <bold>not </bold> <code>null</code>, then set the value on
	 * <code>copyRef</code> without making a copy. 2. If the type is <code>null</code>, then
	 * obtain the list from <code>copyRef</code> and add a copy of each value from the original
	 * list to it.
	 */

	protected void copyManyAttribute(EAttribute attribute, List aValue, EObject aRefObject, String idSuffix, EObject copyRef) {
		if (attribute.getEType() == null)
			copyRef.eSet(attribute, aValue);
		else {
			List copyList = (List) copyRef.eGet(attribute);
			Iterator it = aValue.iterator();
			Object next;
			while (it.hasNext()) {
				next = it.next();
				if (next instanceof EObject)
					copyList.add(containmentCopy((EObject) next, idSuffix));
				else
					copyList.add(next);
			}
		}
	}

	/**
	 * This method will iterate over the references of the receiver. If a reference's value is not
	 * <code>null</code>, then a series of tests are done before setting the value with the
	 * copied object, <code>copyRef</code>.
	 * 
	 * Tests: 1. If the reference is many, obtain the list from the receiving object and make a copy
	 * of each value if the reference is composite before adding to the list; otherwise, just add
	 * each value to the of the <code>copyRef</code>.
	 */

	protected void copyManyReference(EReference aReference, List aList, EObject aRefObject, String idSuffix, EObject copyRef) {
		if (!aList.isEmpty()) {
			if (aReference.isContainment()) {
				List copyList = (List) copyRef.eGet(aReference);
				Iterator it = aList.iterator();
				EObject targetValue;
				while (it.hasNext()) {
					targetValue = (EObject) it.next();
					copyList.add(containmentCopy(targetValue, idSuffix));
				}
			} else
				addDeferredManyReferenceCopy(aReference, aList, idSuffix, copyRef);
		}
	}

	/**
	 * This method will iterate over the references of the receiver. If a reference's value is not
	 * <code>null</code>, then perform the copy.
	 */

	protected void copyReference(EReference aReference, EObject aRefObject, String idSuffix, EObject copyRef) {
		if (aReference.isMany()) {
			List value = (List) aRefObject.eGet(aReference);
			if (value != null)
				copyManyReference(aReference, value, aRefObject, idSuffix, copyRef);
		} else if (aRefObject.eIsSet(aReference)) {
			Object value = aRefObject.eGet(aReference);
			copySingleReference(aReference, (EObject) value, aRefObject, idSuffix, copyRef);
		}
	}

	/**
	 * Copy all RefObjects from <code>aGroup</code> and add the copy back to <code>aGroup</code>.
	 */
	protected void copyRefObjects(CopyGroup aGroup) {
		Association association;
		EObject refObject;
		String idSuffix;
		if (aGroup.primGetRefObjects() != null) {
			Iterator it = aGroup.primGetRefObjects().iterator();
			while (it.hasNext()) {
				association = (Association) it.next();
				refObject = (EObject) association.getKey();
				idSuffix = (String) association.getValue();
				if (idSuffix == null)
					idSuffix = aGroup.getDefaultIdSuffix();
				aGroup.addCopied(containmentCopy(refObject, idSuffix));
			}
		}
	}

	/**
	 * Copy all Resources from <code>aGroup</code> and add the copy back to <code>aGroup</code>.
	 */
	protected void copyResources(CopyGroup aGroup) {
		Association association;
		Resource resource;
		String uri;
		if (aGroup.primGetResources() != null) {
			Iterator it = aGroup.primGetResources().iterator();
			while (it.hasNext()) {
				association = (Association) it.next();
				resource = (Resource) association.getKey();
				uri = (String) association.getValue();
				Resource copied = containmentCopy(resource, uri);
				copyModificationFlag(resource, copied);
				aGroup.addCopied(copied);
			}
		}
	}

	/**
	 * Copy a single value attribute.
	 * 
	 * Tests:
	 * 
	 * 1. If an attribute type is not <code>null</code> then it is an object type and it must be
	 * copied. Do not copy the value if the attribute is an Enumeration type. 2. If an attribute
	 * type is <bold>not </bold> <code>null</code> then copy the value before setting it on
	 * <code>copyRef</code>.
	 */

	protected void copySingleAttribute(EAttribute attribute, Object aValue, EObject aRefObject, String idSuffix, EObject copyRef) {
		if (attribute.getEType() == null) {
			copyRef.eSet(attribute, aValue);
		} else {
			//MOF108
			// if (attribute.isObjectType())
			if (attribute.getEType() instanceof EClass)
				copyRef.eSet(attribute, containmentCopy((EObject) aValue, idSuffix));
			else
				copyRef.eSet(attribute, aValue);
		}
	}

	/**
	 * This method will iterate over the references of the receiver. If a reference's value is not
	 * <code>null</code>, then a series of tests are done before setting the value with the
	 * copied object, <code>copyRef</code>.
	 * 
	 * Tests: 1. If the reference is <bold>not </bold> many, make a copy of the value if the
	 * reference is composite before setting the value with <code>copyRef</code>; otherwise, just
	 * set the value as is.
	 */

	protected void copySingleReference(EReference aReference, EObject aValue, EObject aRefObject, String idSuffix, EObject copyRef) {
		//Exceptional case; the relationship is not the container relationship
		//And is not navigable in the other direction; go ahead and copy
		if (aValue != aRefObject.eContainer() || aReference.getEOpposite() == null) {
			if (aReference.isContainment())
				copyRef.eSet(aReference, containmentCopy(aValue, idSuffix));
			else
				addDeferredSingleReferenceCopy(aReference, aValue, idSuffix, copyRef);
		}
	}

	/**
	 * Copy all Resources and RefObjects within <code>aGroup</code> and add them to
	 * <code>aGroup</code>. Non composite references will be deferred until all objects are
	 * copied from <code>aGroup</code>.
	 * 
	 * Copy Resources first and then copy RefObjects.
	 */
	public static void createCopy(CopyGroup aGroup) {
		EtoolsCopyUtility utility = new EtoolsCopyUtility();
		utility.copy(aGroup);
	}

	/**
	 * Copies attributes
	 */
	public static void createAttributeCopy(EObject aRefObject, EObject copyObject) {
		EtoolsCopyUtility utility = new EtoolsCopyUtility();
		utility.copyCurrentAttributes(aRefObject, null, copyObject);
	}

	/**
	 * Copies all set attributes and references based on containment. Thus, it will copy the entire
	 * containment tree. Use the idSuffix to define the suffix that will be appended to the existing
	 * IDs of copied objects.
	 */
	public static EObject createCopy(EObject aRefObject) {
		return createCopy(aRefObject, null);
	}

	/**
	 * Copies all set attributes and references based on containment. Thus, it will copy the entire
	 * containment tree. Use the idSuffix to define the suffix that will be appended to the existing
	 * IDs of copied objects.
	 */
	public static EObject createCopy(EObject aRefObject, String idSuffix) {
		EtoolsCopyUtility utility = new EtoolsCopyUtility();
		return utility.copy(aRefObject, idSuffix);
	}

	/**
	 * Copy <code>aResource</code> using <code>newUri</code> for the URI.
	 */
	public static Resource createCopy(Resource aResource, String newUri) {
		EtoolsCopyUtility utility = new EtoolsCopyUtility();
		return utility.copy(aResource, newUri);
	}

	protected void executeDeferredCopyActions() {
		if (primGetDeferredReferenceCopies() != null) {
			Iterator it = primGetDeferredReferenceCopies().iterator();
			DeferredReferenceUtilityAction action;
			while (it.hasNext()) {
				action = (DeferredReferenceUtilityAction) it.next();
				action.performAction();
			}
			primGetDeferredReferenceCopies().clear();
		}
	}

	/**
	 * Insert the method's description here. Creation date: (12/13/2000 9:17:35 PM)
	 * 
	 * @return java.util.HashMap
	 */
	protected java.util.HashMap getCopiedObjects() {
		if (copiedObjects == null)
			copiedObjects = new HashMap(200);
		return copiedObjects;
	}

	protected java.util.HashMap getCopiedAdapters() {
		if (copiedAdapters == null)
			copiedAdapters = new HashMap(200);
		return copiedAdapters;
	}

	/**
	 * Return a cached copy.
	 */
	public EObject getCopy(EObject anObject) {
		if (anObject == null)
			return null;
		return (EObject) getCopiedObjects().get(anObject);
	}

	/**
	 * Return a cached copy, if none, return the argument.
	 */
	public EObject getCopyIfFound(EObject anObject) {
		EObject copied = getCopy(anObject);
		return copied == null ? anObject : copied;
	}

	/**
	 * Insert the method's description here. Creation date: (12/16/2000 9:11:32 AM)
	 * 
	 * @return java.util.List
	 */
	protected java.util.List getDeferredReferenceCopies() {
		if (deferredReferenceCopies == null)
			deferredReferenceCopies = new ArrayList(100);
		return deferredReferenceCopies;
	}

	/**
	 * Return an instance of EObject that is the same type as <code>aRefObject</code>.
	 */
	public EObject newInstance(EObject aRefObject) {
		if (aRefObject == null)
			return null;
		EPackage epkg = (EPackage) aRefObject.eClass().eContainer();
		EFactory factory = epkg.getEFactoryInstance();
		EClass refObj = aRefObject.eClass();
		return factory.create(refObj);
	}

	/**
	 * Return an instance of Resource that uses the same ResourceFactory as the one used by
	 * <code>aResource</code>. If <code>newUri</code> is <code>null</code> then use the URI
	 * from <code>aResource</code>.
	 */
	public Resource newInstance(Resource aResource, String newUri) {
		if (aResource == null)
			return null;
		String innerNewUri = newUri;
		Resource newResource;
		String originalUri = aResource.getURI().toString();
		innerNewUri = innerNewUri == null ? originalUri : innerNewUri;

		Resource.Factory factory = null;
		if (aResource.getResourceSet() == null)
			factory = Resource.Factory.Registry.INSTANCE.getFactory(URI.createURI(originalUri));
		else
			factory = aResource.getResourceSet().getResourceFactoryRegistry().getFactory(URI.createURI(originalUri));


		newResource = factory.createResource(URI.createURI(innerNewUri));
		return newResource;
	}

	/**
	 * Should the id be copied in the case where no suffix is specified? Defaults to false
	 */
	public boolean preserveIds() {
		return preserveIds;
	}

	/**
	 * Copies all set attributes and references based on containment. Thus, it will copy the entire
	 * containment tree. Use the idSuffix to define the suffix that will be appended to the existing
	 * IDs of copied objects. If an idSuffix does not exist, do not set an id on the copied object.
	 */
	protected EObject primCopy(EObject aRefObject, String idSuffix) {
		if (aRefObject == null)
			return null;
		EObject copyRef = primCopyObject(aRefObject, idSuffix);

		copyCurrentReferences(aRefObject, idSuffix, copyRef);
		return copyRef;
	}

	/**
	 * Copies all set attributes based on containment. Thus, it will copy the entire containment
	 * tree. Use the idSuffix to define the suffix that will be appended to the existing IDs of
	 * copied objects. If an idSuffix does not exist, do not set an id on the copied object.
	 */
	protected EObject primCopyObject(EObject aRefObject, String idSuffix) {
		if (aRefObject == null)
			return null;
		EObject copyRef = newInstance(aRefObject);

		recordCopy(aRefObject, copyRef);
		copyCurrentAttributes(aRefObject, idSuffix, copyRef);
		if (isCopyAdapters())
			copyAdapters(aRefObject, copyRef);
		return copyRef;
	}

	protected void copyAdapters(EObject aRefObject, EObject copyRef) {
		List adapters = aRefObject.eAdapters();
		for (int i = 0; i < adapters.size(); i++) {
			Adapter adapter = (Adapter) adapters.get(i);
			copyAdapter(aRefObject, copyRef, adapter);

		}
	}

	protected void copyAdapter(EObject aRefObject, EObject copyRef, Adapter adapter) {
		if (!(adapter instanceof CloneablePublic))
			return;
		CloneablePublic copyAdapter = (CloneablePublic) getCopiedAdapters().get(adapter);
		if (copyAdapter == null) {
			copyAdapter = (CloneablePublic) ((CloneablePublic) adapter).clone();
			getCopiedAdapters().put(adapter, copyAdapter);
		}
		if (copyAdapter != null)
			copyRef.eAdapters().add(copyAdapter);
	}

	/**
	 * Copies all set attributes and references based on containment. Thus, it will copy the entire
	 * containment tree. Use the idSuffix to define the suffix that will be appended to the existing
	 * IDs of copied objects.
	 */
	protected Resource primCopy(Resource aResource, String newUri) {
		if (aResource == null)
			return null;
		Resource copyResource = newInstance(aResource, newUri);
		copyExtent(aResource.getContents(), copyResource.getContents());
		return copyResource;
	}

	protected void copyModificationFlag(Resource aResource, Resource copied) {
		if (aResource.isModified())
			copied.setModified(true);
	}

	/**
	 * Insert the method's description here. Creation date: (12/16/2000 9:11:32 AM)
	 * 
	 * @return java.util.List
	 */
	private java.util.List primGetDeferredReferenceCopies() {
		return deferredReferenceCopies;
	}

	public void recordCopy(EObject aSource, EObject aCopy) {
		getCopiedObjects().put(aSource, aCopy);
	}

	public void recordCopy(Adapter aSource, Adapter aCopy) {
		getCopiedAdapters().put(aSource, aCopy);
	}

	/**
	 * Should the id be copied in the case where no suffix is specified?
	 */
	public void setPreserveIds(boolean value) {
		preserveIds = value;
	}

	protected boolean shouldCopyReference(EReference aReference) {
		return aReference.isChangeable();
	}

	/**
	 * @return
	 */
	public boolean isCopyAdapters() {
		return copyAdapters;
	}

	/**
	 * Flag used to indicate whether adapters should be copied as well. In order for an adapter to
	 * be copied, it must implement the {@link CloneablePublic}interface.
	 */
	public void setCopyAdapters(boolean b) {
		copyAdapters = b;
	}

}


