//------------------------------------------------------------------------------
// 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.edit.process.command;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xml.type.XMLTypePackage.Literals;
import org.eclipse.emf.edit.command.CommandParameter;
import org.eclipse.emf.edit.command.CopyCommand;
import org.eclipse.emf.edit.command.CreateCopyCommand;
import org.eclipse.emf.edit.command.InitializeCopyCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.provider.AdapterFactoryTreeIterator;
import org.eclipse.emf.edit.provider.ITreeItemContentProvider;
import org.eclipse.epf.library.edit.IConfigurator;
import org.eclipse.epf.library.edit.IFilter;
import org.eclipse.epf.library.edit.Providers;
import org.eclipse.epf.library.edit.TngAdapterFactory;
import org.eclipse.epf.library.edit.process.IBSItemProvider;
import org.eclipse.epf.library.edit.util.ConstraintManager;
import org.eclipse.epf.library.edit.util.ExtensionManager;
import org.eclipse.epf.library.edit.util.IDiagramManager;
import org.eclipse.epf.library.edit.util.ITextReferenceReplacer;
import org.eclipse.epf.library.edit.util.ProcessUtil;
import org.eclipse.epf.library.edit.util.Suppression;
import org.eclipse.epf.library.edit.util.TngUtil;
import org.eclipse.epf.uma.Activity;
import org.eclipse.epf.uma.Constraint;
import org.eclipse.epf.uma.Diagram;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.Process;
import org.eclipse.epf.uma.ProcessComponent;
import org.eclipse.epf.uma.ProcessPackage;
import org.eclipse.epf.uma.UmaFactory;
import org.eclipse.epf.uma.VariabilityElement;
import org.eclipse.epf.uma.VariabilityType;
import org.eclipse.epf.uma.edit.command.MethodElementInitializeCopyCommand;
import org.eclipse.osgi.util.NLS;

/**
 * Physically copies an activity with all of its direct or inherited structural features and references.
 *   
 * @author Phong Nguyen Le - Jun 21, 2006
 * @since  1.0
 */
public class ActivityDeepCopyCommand extends CopyCommand {
	private static final AdapterFactory[] adapterFactories = {
		TngAdapterFactory.INSTANCE.getWBS_ComposedAdapterFactory(),
		TngAdapterFactory.INSTANCE.getOBS_ComposedAdapterFactory(),
		TngAdapterFactory.INSTANCE.getPBS_ComposedAdapterFactory()
	};
	
	private static final int[] diagramTypes = {
		IDiagramManager.ACTIVITY_DETAIL_DIAGRAM,
		IDiagramManager.ACTIVITY_DIAGRAM,
		IDiagramManager.WORK_PRODUCT_DEPENDENCY_DIAGRAM
	};
	
	private class EditingDomain extends AdapterFactoryEditingDomain {

		/**
		 * @param adapterFactory
		 * @param commandStack
		 */
		public EditingDomain() {
			super(TngAdapterFactory.INSTANCE.getWBS_ComposedAdapterFactory(), new BasicCommandStack()); 
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain#createCommand(java.lang.Class, org.eclipse.emf.edit.command.CommandParameter)
		 */
		public Command createCommand(Class commandClass, CommandParameter commandParameter) {
			if(commandClass == CreateCopyCommand.class) {
				return new CreateDeepCopyCommand(this, commandParameter.getEOwner(), (Helper) commandParameter.getValue());
			}
			if(commandClass == InitializeCopyCommand.class) {
				return new InitializeDeepCopyCommand(this, commandParameter.getEOwner(), (Helper) commandParameter.getValue());
			}
			return super.createCommand(commandClass, commandParameter);
		}
	}
	
	private static class CreateDeepCopyCommand extends CreateCopyCommand {

		/**
		 * @param domain
		 * @param owner
		 * @param copyHelper
		 */
		public CreateDeepCopyCommand(EditingDomain domain, EObject owner, Helper copyHelper) {
			super(domain, owner, copyHelper);
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.emf.edit.command.CreateCopyCommand#doGetChildrenToCopy()
		 */
		public Collection doGetChildrenToCopy() {
			// Create commands to create copies of the children.
			//
			List result = new ArrayList();
			for (Iterator i = owner.eContents().iterator(); i.hasNext(); )
			{
				Object o = i.next();
				if(o instanceof Activity) {
					// make sure that Activity will be copied first
					// so references to its inherited elements can be resolved properly
					//
					result.add(0, o);
				}
				else {
					result.add(o);
				}
			}
			return result;

		}
		
	}
	
	private class InitializeDeepCopyCommand extends MethodElementInitializeCopyCommand {

		/**
		 * @param domain
		 * @param owner
		 * @param copyHelper
		 */
		public InitializeDeepCopyCommand(EditingDomain domain, EObject owner, Helper copyHelper) {
			super(domain, owner, copyHelper);
			copy = (EObject) copyHelper.get(owner);
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.epf.uma.edit.command.MethodElementInitializeCopyCommand#copyReferences()
		 */
		protected void copyReferences() {
			Activity base = null;
			Activity baseCopy = null;
			Collection childBaseCopies = new HashSet();
			if(owner instanceof Activity) {
				Activity activity = (Activity) owner;
				if(ProcessUtil.isExtendingOrLocallyContributing(activity)) {
					// copy inherited elements by deep-copying base activity
					//
					// deep copy of base activity at this point is essential so references to predecessors
					// will be copied correctly
					//
					base = (Activity) activity.getVariabilityBasedOnElement();
					CopyHelper helper = (CopyHelper)copyHelper;
					Activity oldBaseCopy = (Activity) helper.get(base);
					// this will redirect any reference to copy of base in diagram to the copy of the activity
					//
					helper.putVariabilityElement(base, activity);
					ArrayList childBases = new ArrayList();
					for (Iterator iter = activity.getBreakdownElements().iterator(); iter.hasNext();) {
						Object e = iter.next();
						if(e instanceof VariabilityElement) {
							VariabilityElement ve = (VariabilityElement) e;
							VariabilityElement baseElement = ve.getVariabilityBasedOnElement();
							if(baseElement != null &&
									(ve.getVariabilityType() == VariabilityType.LOCAL_CONTRIBUTION_LITERAL || ve.getVariabilityType() == VariabilityType.LOCAL_REPLACEMENT_LITERAL))
							{
								helper.putVariabilityElement(baseElement, ve);
								childBases.add(baseElement);
							}
						}
					}
					
					ActivityDeepCopyCommand cmd = new ActivityDeepCopyCommand(base, helper, config, targetProcess, monitor);
					try {
						cmd.execute();
						
						baseCopy = (Activity) helper.get(base);
						if(activity.getVariabilityType() == VariabilityType.EXTENDS_LITERAL) {
							// keep activity only as backup copy of base
							//
							helper.removeVariabilityElement(base);
							helper.putBackupCopy(base, activity);
							
							// put back oldBaseCopy
							if(oldBaseCopy != null) {
								helper.basicPut(base, oldBaseCopy);
							}
						}
						
						// remove the copies of all base since base copies are not added to the process
						//
						helper.remove(base);
						for (Iterator iter = childBases.iterator(); iter.hasNext();) {
							EObject childBaseCopy = (EObject) helper.remove(iter.next());
							childBaseCopies.add(childBaseCopy);
							EcoreUtil.remove(childBaseCopy.eContainer());
						}
						
						Activity activityCopy = (Activity) copy;
						
						// remove the copies of base diagram if the activity already have diagram of the same type
						//
						for (int i = 0; i < diagramTypes.length; i++) {
							int type = diagramTypes[i];
							if(diagramMgr.getDiagram(activity, type) != null) {
								Diagram diagram = diagramMgr.getDiagram(activityCopy, type);
								if(diagram != null) {
									EcoreUtil.remove(diagram);
								}
							}
						}
						
						// move content of base package over to the activity's package
						//
						moveContent(cmd.pkgCopy, activityCopy);							
					}
					finally {
						cmd.dispose();
					}
				}
			}
			
			super.copyReferences();
			
			if(base != null) {
				// copy inherited breakdown elements from the base activity in the right order
				//
				IConfigurator configurator = null;
				MethodConfiguration currentConfig = null;
				IFilter filter = ProcessUtil.getFilter(((AdapterFactoryEditingDomain)domain).getAdapterFactory());
				if(filter instanceof IConfigurator) {
					configurator = (IConfigurator) filter;
					currentConfig = configurator.getMethodConfiguration();
					configurator.setMethodConfiguration(config);
				}
				try {
					Activity activity = (Activity) owner;
					Activity activityCopy = ((Activity) copy);					
					
					// Add breakdown elements from base copy to the activity copy. 
					// Don't add copy of base element of the existing children
					//
					List breakdownElements = new ArrayList();
					for (Iterator iter = baseCopy.getBreakdownElements().iterator(); iter.hasNext();) {
						Object e = iter.next();
						if(!childBaseCopies.contains(e)) {
							breakdownElements.add(e);
						}						
					}
					activityCopy.getBreakdownElements().addAll(0, breakdownElements);
					EcoreUtil.remove(baseCopy);
					
					// copy other string attributes as well after realize them
					//
					for (Iterator iter = getAttributesToCopy().iterator(); iter.hasNext();) {
						EAttribute attribute = (EAttribute) iter.next();
						Object value = Providers.getConfigurationApplicator().getAttribute(activity, attribute, config);
						activityCopy.eSet(attribute, value);
					}
					
					// copy presentation name
					//
					activityCopy.setPresentationName(ProcessUtil.getPresentationName(activity));					

					// clear the variability data
					//
					activityCopy.setVariabilityBasedOnElement(null);
					activityCopy.setVariabilityType(null);

					// clear suppression data
					//
					Constraint rule = ConstraintManager.getConstraint(activityCopy, ConstraintManager.PROCESS_SUPPRESSION, false);
					if(rule != null) {
						EcoreUtil.remove(rule);
					}
				}
				finally {
					if(configurator != null) {
						// restore current configruation
						//
						configurator.setMethodConfiguration(currentConfig);
					}
				}
			}
		}		
	}

	private ProcessPackage pkgCopy;
	private MethodConfiguration config;
	private IDiagramManager diagramMgr;

	private Process targetProcess;

	private IProgressMonitor monitor;

	private Activity activity;
	
	public ActivityDeepCopyCommand(Activity activity, CopyHelper copyHelper, MethodConfiguration config, Process targetProcess, IProgressMonitor monitor) {
		super(null, activity.eContainer(), copyHelper);
		Assert.isTrue(activity.eContainer() instanceof ProcessPackage, "Activity's container must be a ProcessPackage"); //$NON-NLS-1$
		domain = new EditingDomain();
		this.config = config;
		this.targetProcess = targetProcess;
		diagramMgr = ExtensionManager.getDiagramManager();
		Assert.isNotNull(diagramMgr, "Could not load diagram manager"); //$NON-NLS-1$
		this.monitor = monitor;
		this.activity = activity;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.emf.edit.command.CopyCommand#execute()
	 */
	public void execute() {
		monitor.subTask(NLS.bind("Copying activity ''{0}''", ProcessUtil.getPresentationName(activity)));
		super.execute();
		Collection result = super.getResult();
		if(!result.isEmpty()) {
			pkgCopy = (ProcessPackage) result.iterator().next();
			if(pkgCopy instanceof ProcessComponent) {
				// replace the ProcessComponent with a equivalent ProcessPackage
				//
				ProcessPackage pkg = UmaFactory.eINSTANCE.createProcessPackage();
				Collection features = new ArrayList(pkgCopy.eClass().getEAllStructuralFeatures());
				features.retainAll(pkg.eClass().getEAllStructuralFeatures());
				for (Iterator iter = features.iterator(); iter.hasNext();) {
					EStructuralFeature feature = (EStructuralFeature) iter.next();
					Object value = pkgCopy.eGet(feature);
					pkg.eSet(feature, value);
				}
				Process proc = ((ProcessComponent)pkgCopy).getProcess();
				if(proc != null) {
					pkg.getProcessElements().add(0, proc);
				}
				pkgCopy = pkg;
			}
			
			replaceTextReferences();
		}
	}
	
	/**
	 * If textual descriptions in the copied elements contain references (URLs) to other elements 
	 * within the same copied process then replace these references with references that point to 
	 * the new elements in the copied structures.
	 */
	private void replaceTextReferences() {
		ITextReferenceReplacer txtRefReplacer = ExtensionManager.getTextReferenceReplacer();
		if(txtRefReplacer == null) return;

		Map oldToNewObjectMap = ((CopyHelper) copyHelper).getObjectToCopyMap();
		for (Iterator iter = pkgCopy.eAllContents(); iter.hasNext();) {
			EObject element = (EObject) iter.next();
			for (Iterator attributes = element.eClass().getEAllAttributes().iterator(); attributes.hasNext(); )
			{
				EAttribute attribute = (EAttribute) attributes.next();
				if (attribute.isChangeable() && !attribute.isDerived() && (attribute.isMany() || element.eIsSet(attribute))
						&& attribute.getEAttributeType().getInstanceClass() == Literals.STRING.getInstanceClass())
				{
					String text = (String) element.eGet(attribute);
					if(text != null) {
						text = txtRefReplacer.replace(text, targetProcess, oldToNewObjectMap);
						element.eSet(attribute, text);
					}
				}
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.emf.common.command.CompoundCommand#getResult()
	 */
	public Collection getResult() {
		if(pkgCopy != null) {
			Activity act = ProcessUtil.findActivity(pkgCopy);
			if(act != null) {
				return Collections.singletonList(act);
			}
		}
		return Collections.EMPTY_LIST;
	}
	
	/**
	 * Moves content of source object <code>src</code> over to the target objec <code>target</code>
	 * @param src
	 * @param target
	 */
	private static void moveContent(ProcessPackage src, Activity act) {
		ProcessPackage target = (ProcessPackage) act.eContainer();
		// move content of base package over to the activity's package					
		for (Iterator iter = new ArrayList(src.eContents()).iterator(); iter.hasNext();) {
			EObject e = (EObject) iter.next();
			EStructuralFeature f = e.eContainingFeature();						
			if(f.isMany()) {
				((List)target.eGet(f)).add(e);
			}
		}
		if(src instanceof ProcessComponent) {
			Process baseCopy = ((ProcessComponent)src).getProcess();
			target.getProcessElements().add(baseCopy);
		}

	}
	
	public void copySuppressionStates() {
		for (int i = 0; i < adapterFactories.length; i++) {
			AdapterFactory adapterFactory = adapterFactories[i];
			Activity act = ProcessUtil.findActivity((ProcessPackage) owner);
			ITreeItemContentProvider ip = (ITreeItemContentProvider) adapterFactory.adapt(act, ITreeItemContentProvider.class);
			IBSItemProvider bsIp = (IBSItemProvider) ip;
			Object top = bsIp.getTopItem();
			if(!(top instanceof Process) || !(((EObject)top).eContainer() instanceof ProcessComponent)) {
				// item provider tree of the owner's process is not initialized yet
				//
				ProcessUtil.initializeItemProviderPath(act, adapterFactory);
			}
			Process proc = TngUtil.getOwningProcess(act);
			Suppression suppression = Suppression.getSuppression(proc);
			Iterator iter = new AdapterFactoryTreeIterator(adapterFactory, proc);
//			for (Iterator iter = ip.getChildren(owner).iterator(); iter.hasNext();) {
			while(iter.hasNext()) {
				Object child = iter.next();
				Object e = TngUtil.unwrap(child);
				MethodElement copy = (MethodElement) copyHelper.get(e);

				// copy suppression state, consider current process that inherits this element
				//
				if(copy != null && suppression.isSuppressed(child)) {
					copy.setSuppressed(Boolean.TRUE);
				}
			}
		}
	}
}
