//------------------------------------------------------------------------------
// 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.lang.reflect.InvocationTargetException;
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 org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.util.AbstractTreeIterator;
import org.eclipse.emf.edit.provider.ITreeItemContentProvider;
import org.eclipse.epf.library.edit.LibraryEditResources;
import org.eclipse.epf.library.edit.TngAdapterFactory;
import org.eclipse.epf.library.edit.ui.IActionTypeProvider;
import org.eclipse.epf.library.edit.ui.UserInteractionHelper;
import org.eclipse.epf.library.edit.util.ActivityHandler;
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.BreakdownElement;
import org.eclipse.epf.uma.CapabilityPattern;
import org.eclipse.epf.uma.DeliveryProcess;
import org.eclipse.epf.uma.DescribableElement;
import org.eclipse.epf.uma.Descriptor;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.Process;
import org.eclipse.epf.uma.ProcessPackage;
import org.eclipse.epf.uma.TeamProfile;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.Viewer;


/**
 * @author Phong Nguyen Le
 * @since 1.0
 */
public class ActivityDropCommand extends BSDropCommand {

	private Viewer viewer;

	private List oldPatterns;

	private List patterns;

	private Process targetProcess;

	private boolean isDeliveryProcess;

	private boolean canExtend;

	private int type;
	
	private Collection appliedActivities;

	private AdapterFactory adapterFactory;

	private ActivityHandler activityHandler;

	protected boolean preExecuted;

	protected HashSet addedObjects;

	public ActivityDropCommand(Activity target, List activities, Viewer viewer, AdapterFactory adapterFactory) {
		super(target, activities);
		setLabel(LibraryEditResources.ActivityDropCommand_label); 
		this.viewer = viewer;
		this.adapterFactory = adapterFactory;
		targetProcess = TngUtil.getOwningProcess(target);
		
		isDeliveryProcess = targetProcess instanceof DeliveryProcess;
		if (isDeliveryProcess) {
			oldPatterns = new ArrayList(((DeliveryProcess) targetProcess)
					.getIncludesPatterns());
		}
	}

	public void setType(int type) {
		this.type = type;
		if(type == IActionTypeProvider.DEEP_COPY) {
			runAsJob = true;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.library.edit.process.command.BSDropCommand#execute()
	 */
	public void execute() {
		IActionTypeProvider actionTypeProvider = (IActionTypeProvider) viewer;
		
		for (Iterator iter = dropElements.iterator(); iter.hasNext();) {
			BreakdownElement element = (BreakdownElement) iter.next();
			
			if ((ProcessUtil.hasContributorOrReplacer((Activity) element))){
				canExtend = false;
				break;
			}
			
			Process proc = TngUtil.getOwningProcess(element);
			if (targetProcess.getVariabilityBasedOnElement() == proc) {
				canExtend = false;
				break;
			}
			
			if (proc instanceof CapabilityPattern && proc != targetProcess) {
				canExtend = true;
				break;
			}
		}
		if ( viewer != null) {
			viewer = null;
			// delegate the execution of this command
			//
			actionTypeProvider.execute(this, canExtend);

			return;
		}

		activityHandler = new ActivityHandler();
		
		if(type == IActionTypeProvider.DEEP_COPY) {
			doDeepCopy();
		}
		else {
			super.execute();
		}
	}

	/**
	 * 
	 */
	private void doDeepCopy() {
		if(!UserInteractionHelper.confirmDeepCopy(dropElements)) {
			return;
		}
		MethodConfiguration deepCopyConfig = null;
		try {
			deepCopyConfig = UserInteractionHelper.chooseDeepCopyConfiguration(targetProcess, adapterFactory);
		}
		catch(OperationCanceledException e) {
			return;
		}
		activityHandler.setDeepCopyConfig(deepCopyConfig);
		activityHandler.setTargetProcess(targetProcess);
		
		IRunnableWithProgress runnable = new IRunnableWithProgress() {

			public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
				activityHandler.setMonitor(monitor);
				preExecuted = preExecute();
			}
			
		};
		UserInteractionHelper.runWithProgress(runnable, getLabel());			
		if (preExecuted) {
			redo();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.library.edit.process.command.BSDropCommand#preExecute()
	 */
	protected boolean preExecute() {
		if (!super.preExecute())
			return false;

		// process the selected activities and deep-copy the valid ones
		//
		if (isDeliveryProcess) {
			patterns = new ArrayList();
		}
		
		for (Iterator iter = dropElements.iterator(); iter.hasNext();) {
			Object element = (Object) iter.next();
			if ((element instanceof Activity)
			// && element != target
			) {
				Activity act = (Activity) element;
				
				if(type == IActionTypeProvider.DEEP_COPY) {
					activityHandler.deepCopy(act);
				}
				else {
					Process proc = TngUtil.getOwningProcess(act);
					if (proc instanceof CapabilityPattern && proc != targetProcess) {
						if (type == IActionTypeProvider.EXTEND) {
							activityHandler.extend(act);
							if (patterns != null)
								patterns.add(proc);
						} else {
							activityHandler.copy(act);
						}
					} else {
						activityHandler.copy(act);
					}
				}
			}
		}

		appliedActivities = activityHandler.getActivities();

		return !appliedActivities.isEmpty();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.library.edit.process.command.BSDropCommand#doExecute()
	 */
	protected void doExecute() {
		Iterator itor = appliedActivities.iterator();

		ProcessPackage pkg = (ProcessPackage) activity.eContainer();
		while (itor.hasNext()) {
			Activity act = (Activity) itor.next();

			if (act.eContainer() != null
					&& act.eContainer().eContainer() != pkg) {
				pkg.getChildPackages().add(act.eContainer());
			}

			if (patterns != null && !patterns.isEmpty()) {
				DeliveryProcess proc = (DeliveryProcess) targetProcess;
				for (Iterator iter = patterns.iterator(); iter.hasNext();) {
					Object pattern = iter.next();
					if (!proc.getIncludesPatterns().contains(pattern)) {
						proc.getIncludesPatterns().add(pattern);
					}
				}
			}
		}

		activity.getBreakdownElements().addAll(appliedActivities);
		
		if(!activityHandler.getDeepCopies().isEmpty()) {
			postDeepCopy();
		}

		// Hack to refresh providers forcefully before calling fixDuplicateNames
		ITreeItemContentProvider adapter = (ITreeItemContentProvider) adapterFactory.adapt(activity, ITreeItemContentProvider.class);
		adapter.getChildren(activity);
		
		// fix duplicate names of newly added elements
		fixDuplicateNames();
		
		getModifiedResources().add(activity.eResource());
	}

	/**
	 * 
	 */
	private void postDeepCopy() {
		Runnable runnable = new Runnable() {

			public void run() {
				// add to default configuratation of the target process any missing content
				//
				if(addedObjects == null) {
					addedObjects = new HashSet();
				}
				else {
					addedObjects.clear();
				}
				boolean defaultConfigChanged = false;
				for (Iterator iter = activityHandler.getDeepCopies().iterator(); iter.hasNext();) {
					Iterator iterator = new AbstractTreeIterator(iter.next()) {

						/**
						 * Comment for <code>serialVersionUID</code>
						 */
						private static final long serialVersionUID = 1L;

						protected Iterator getChildren(Object object) {
							if(object instanceof Activity) {
								return ((Activity)object).getBreakdownElements().iterator();
							}
							return Collections.EMPTY_LIST.iterator();
						}

					};
					while(iterator.hasNext()) {
						Object e = iterator.next();
						if(e instanceof Descriptor) {
							MethodElement me = ProcessUtil.getAssociatedElement((Descriptor) e);
							int size = addedObjects.size();
							ProcessUtil.addToDefaultConfiguration(targetProcess, me, addedObjects);
							if(!defaultConfigChanged && (size != addedObjects.size())) {
								getModifiedResources().add(targetProcess.getDefaultContext().eResource());
								defaultConfigChanged = true;
							}
						}
					}
				}
			}
			
		};
		
		UserInteractionHelper.runInUI(runnable, getLabel());
		
		MethodConfiguration deepCopyConfig = activityHandler.getDeepCopyConfig();			

		if(deepCopyConfig != null) {
			// synchronize the deep copies with deep copy configuration				
			//
			final SynchronizeCommand synchCmd = new SynchronizeCommand(activityHandler.getDeepCopies(), deepCopyConfig, null, false);
			try {
				runnable = new Runnable() {

					public void run() {
						synchCmd.initilize();
					}
					
				};
				UserInteractionHelper.runWithProgress(runnable, LibraryEditResources.ProcessAutoSynchronizeAction_prepare);
				if(synchCmd.isIntialized()) {
					synchCmd.execute();
				}
			}
			finally {
				synchCmd.dispose();
			}
		}
	}

	/**
	 * Fixes duplicate names of newly added elements
	 */
	private void fixDuplicateNames() {
		Suppression suppression = Suppression.getSuppression(targetProcess);
		for (Iterator iter = appliedActivities.iterator(); iter.hasNext();) {
			Activity act = (Activity) iter.next();
			fixDuplicateNames(act, suppression);
			fixTeamProfileDuplicateNames(act, suppression);
		}			
	}
	
	private void fixDuplicateNames(BreakdownElement e, Suppression suppression) {
		fixDuplicateNames(e, suppression, adapterFactory);
	}
	
	private static void fixDuplicateNames(BreakdownElement e, Suppression suppression, AdapterFactory adapterFactory) {
		String baseName = e.getName();
		if (ProcessUtil.checkBreakdownElementName(adapterFactory, e, baseName, suppression) != null) {
			for (int i = 1; true; i++) {
				String name = baseName + '_' + i;
				if (ProcessUtil.checkBreakdownElementName(adapterFactory, e, name, suppression) == null) {
					e.setName(name);
					break;
				}
			}
		}
		baseName = ProcessUtil.getPresentationName(e);
		if (ProcessUtil.checkBreakdownElementPresentationName(adapterFactory, e, baseName, suppression) != null) {
			for (int i = 1; true; i++) {
				String name = baseName + '_' + i;
				if (ProcessUtil.checkBreakdownElementPresentationName(adapterFactory, e, name, suppression) == null) {
					e.setPresentationName(name);
					break;
				}
			}
		}
	}		
	
	private void fixTeamProfileDuplicateNames(Activity act, Suppression suppression) {
		for (int i = act.getBreakdownElements().size() - 1; i > -1; i--) {
			Object element = act.getBreakdownElements().get(i);
			if(element instanceof TeamProfile) {
				fixDuplicateNames((BreakdownElement) element, suppression, TngAdapterFactory.INSTANCE.getOBS_ComposedAdapterFactory());
			}
			else if(element instanceof Activity) {
				fixTeamProfileDuplicateNames((Activity) element, suppression);
			}
		}
	}
		

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.library.edit.process.command.BSDropCommand#doUndo()
	 */
	protected void doUndo() {
		activity.getBreakdownElements().removeAll(appliedActivities);
		if (isDeliveryProcess) {
			DeliveryProcess proc = (DeliveryProcess) targetProcess;
			proc.getIncludesPatterns().clear();
			proc.getIncludesPatterns().addAll(oldPatterns);
		}
	}

	public static void setName(List siblings, Activity e) {
		String baseName = e.getName();
		if (!isNameTaken(siblings, e, baseName)) {
			return;
		}
		for (int i = 1; true; i++) {
			String name = baseName + '_' + i;
			if (!isNameTaken(siblings, e, name)) {
				e.setName(name);
				return;
			}
		}
	}

	public static void setDefaultPresentationName(List siblings, Activity e) {
		// if(e.getPresentationName() != null &&
		// e.getPresentationName().trim().length() > 0) return;

		String basePresentationName = ProcessUtil.getPresentationName(e);
		if (!isPresentationNameTaken(siblings, e, basePresentationName)) {
			// e.setPresentationName(basePresentationName);
			return;
		}
		for (int i = 1; true; i++) {
			String name = basePresentationName + '_' + i;
			if (!isPresentationNameTaken(siblings, e, name)) {
				e.setPresentationName(name);
				return;
			}
		}
	}

	private static boolean isNameTaken(List siblings, DescribableElement e,
			String name) {
		for (int i = siblings.size() - 1; i > -1; i--) {
			BreakdownElement sibling = (BreakdownElement) siblings.get(i);
			// if(sibling != e && name.equals(sibling.getPresentationName())) {
			if (sibling != e && name.equals(sibling.getName())) {
				return true;
			}
		}
		return false;
	}

	private static boolean isPresentationNameTaken(List siblings,
			DescribableElement e, String name) {
		for (int i = siblings.size() - 1; i > -1; i--) {
			BreakdownElement sibling = (BreakdownElement) siblings.get(i);
			// if(sibling != e && name.equals(sibling.getPresentationName())) {
			if (sibling != e
					&& name.equals(ProcessUtil.getPresentationName(sibling))) {
				return true;
			}
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.emf.common.command.AbstractCommand#getAffectedObjects()
	 */
	public Collection getAffectedObjects() {
		if(executed) {
			return appliedActivities;
		}
		else {
			return Collections.EMPTY_LIST;
		}
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.emf.common.command.AbstractCommand#getResult()
	 */
	public Collection getResult() {
		return getAffectedObjects();
	}

	public ActivityHandler getActivityHandler() {
		return activityHandler;
	}
}