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

import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.util.AbstractTreeIterator;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.epf.library.edit.IConfigurator;
import org.eclipse.epf.library.edit.LibraryEditPlugin;
import org.eclipse.epf.library.edit.Providers;
import org.eclipse.epf.library.edit.command.BatchCommand;
import org.eclipse.epf.library.edit.command.INestedCommandProvider;
import org.eclipse.epf.library.edit.ui.UserInteractionHelper;
import org.eclipse.epf.library.edit.util.ExtensionManager;
import org.eclipse.epf.library.edit.util.ProcessUtil;
import org.eclipse.epf.library.edit.util.TngUtil;
import org.eclipse.epf.uma.Activity;
import org.eclipse.epf.uma.Artifact;
import org.eclipse.epf.uma.Deliverable;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.ProcessPackage;
import org.eclipse.epf.uma.RoleDescriptor;
import org.eclipse.epf.uma.Task;
import org.eclipse.epf.uma.TaskDescriptor;
import org.eclipse.epf.uma.UmaPackage;
import org.eclipse.epf.uma.VariabilityElement;
import org.eclipse.epf.uma.WorkProduct;
import org.eclipse.epf.uma.WorkProductDescriptor;


/**
 * Command to drag and drop work products to Work Product Usage breakdown
 * structure
 * 
 * @author Phong Nguyen Le
 * @author Shilpa Toraskar
 * @since 1.0
 */
public class PBSDropCommand extends BSDropCommand {
	private ArrayList wpDescList;

	private ArrayList roleDescList;

	private Map wpDescToDeliverableParts;

	private HashMap wpdToDeliverableDescriptorMap;

	private BatchCommand updateDeliverablePartsCmd;

	private boolean newDuplicatesRemoved;

	private IConfigurator configurator;

	private ArrayList linkedTasks;

	private HashMap wpdToTaskFeaturesMap;

	public PBSDropCommand(Activity activity, List workProducts) {
		super(activity, workProducts);
		this.activity = activity;
		for (Iterator iter = dropElements.iterator(); iter.hasNext();) {
			Object element = iter.next();
			if (!(element instanceof WorkProduct)) {
				iter.remove();
			}
		}
	}

	/**
	 * @param activity
	 * @param dropElements
	 * @param synch
	 */
	public PBSDropCommand(Activity activity, List dropElements, boolean synch) {
		super(activity, dropElements, synch);
	}

	/**
	 * @param activity
	 * @param dropElements
	 * @param config
	 * @param synchFeatures
	 */
	public PBSDropCommand(Activity activity, List dropElements,
			MethodConfiguration config, Set synchFeatures, IConfigurator configurator) {
		super(activity, dropElements, config, synchFeatures);
		this.configurator = configurator;
	}

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

		wpDescList = new ArrayList();
		taskDescList = new ArrayList();
		roleDescList = new ArrayList();
		wpDescToDeliverableParts = new HashMap();
		wpdToDeliverableDescriptorMap = new HashMap();

		if(!synchronize || (synchronize && synchFeatures.contains(UmaPackage.eINSTANCE.getArtifact_ContainedArtifacts()))) {
			// add subartifacts to dropElements list if there is any
			//
			for (Iterator iter = new ArrayList(dropElements).iterator(); iter
			.hasNext();) {
				Object element = iter.next();
				if (element instanceof Artifact) {
					Iterator iterator = new AbstractTreeIterator(element, false) {
						
						/**
						 * Comment for <code>serialVersionUID</code>
						 */
						private static final long serialVersionUID = -4820477887426087262L;
						
						protected Iterator getChildren(Object object) {
							Object subArtifacts = Providers.getConfigurationApplicator().getReference(
									(VariabilityElement) object,
									UmaPackage.eINSTANCE.getArtifact_ContainedArtifacts(),
									getMethodConfiguration());
							return ((Collection)subArtifacts).iterator();
						}
						
					};
					
					while (iterator.hasNext()) {
						Artifact subArtifact = (Artifact) iterator.next();
						if (!dropElements.contains(subArtifact) && prepareAdd((WorkProduct) subArtifact)) 
						{
							dropElements.add(subArtifact);
						}
					}
				}
			}
		}

		MethodConfiguration config = getMethodConfiguration();
		Set descriptorsToRefresh = synchronize ? batchCommand.getDescriptorsToRefresh() : null;
		for (int i = 0; i < dropElements.size(); i++) {
			WorkProduct wp = (WorkProduct) dropElements.get(i);			
			if (TngUtil.isContributor(wp)) {
				wp = (WorkProduct) TngUtil.getBase(wp);
			}
			WorkProductDescriptor wpDesc = (WorkProductDescriptor) ProcessCommandUtil
					.getDescriptor(wp, activity, config);
			if (wpDesc == null) {
				wpDesc = ProcessCommandUtil.createWorkProductDescriptor(wp, config,
						wpDescToDeliverableParts);
				wpDescList.add(wpDesc);

				// automatic adding to the existing deliverable descriptor in
				// the activity's scope if there is any
				// valid one.
				//
				WorkProductDescriptor descriptor = UserInteractionHelper
						.getDeliverable(activity, wp);
				if (descriptor != null) {
					wpdToDeliverableDescriptorMap.put(wpDesc, descriptor);
				}
				
				if(wpdToTaskFeaturesMap != null) {
					// replace work product with its work product descriptor in wpdToTaskFeaturesMap
					//
					Map featuresMap = (Map) wpdToTaskFeaturesMap.get(wp);
					if(featuresMap != null) {
						wpdToTaskFeaturesMap.remove(wp);
						wpdToTaskFeaturesMap.put(wpDesc, featuresMap);
					}
				}

			} else {
				if (descriptorsToRefresh != null && wpDesc.getIsSynchronizedWithSource().booleanValue()) {
					descriptorsToRefresh.add(wpDesc);
				}
				if (wp instanceof Deliverable && synchFeatures.contains(UmaPackage.eINSTANCE.getDeliverable_DeliveredWorkProducts())) {
					ProcessCommandUtil.createDeliverableParts(wpDesc,
							(Deliverable) wp, config, wpDescToDeliverableParts, descriptorsToRefresh);
				}
				
				if(wpdToTaskFeaturesMap != null) {
					wpdToTaskFeaturesMap.remove(wp);
				}
			}

		
			if (!synchronize) {
				// get all possible tasks for this workproduct for
				// which task descriptor needs to be created
				List tasks = ProcessUtil.getTasksForWorkProduct(wp, config);

				if ((tasks != null) && (tasks.size() > 0)) {
					// show task selections dialog
					List selectedTasks = UserInteractionHelper.selectTasks(
							tasks, wp);

					// create task descriptors for this workproduct
					if ((selectedTasks != null) && (!(selectedTasks.isEmpty()))) {
						for (int j = 0; j < selectedTasks.size(); j++) {
							Task task = (Task) selectedTasks.get(j);

							// call this method even the descriptor for the
							// given task already exists in this activity
							// to add any additional relationships in case of
							// recent change in the default configuration
							// of the process.
							PBSDropCommand.addToDescriptorLists(task, activity,
									taskDescList, roleDescList, wpDescList,
									wpDescToDeliverableParts,
									descriptorsToRefresh,
									batchCommand.getObjectToNewFeatureValuesMap(), 
									config, synchFeatures);
						}
					} else {
						// If no tasks are selected, add Responsible role to wp
						PBSDropCommand.addResponsibleRole(wpDesc, activity,
								roleDescList, descriptorsToRefresh, config);
					}
				}
				else {
					//	If there are no tasks to show, add Responsible role to wp
					PBSDropCommand.addResponsibleRole(wpDesc, activity,	roleDescList, descriptorsToRefresh, config);
				}
			}
//			else {
//				//	in case of synchronization, add Responsible role to wp
//				if (synchFeatures.contains(UmaPackage.eINSTANCE.getRole_ResponsibleFor()))
//					PBSDropCommand.addResponsibleRole(wpDesc, activity,	roleDescList, descriptorsToRefresh, config);
//			}				
		}

		if(!wpDescToDeliverableParts.isEmpty()) {
			updateDeliverablePartsCmd = new BatchCommand(true);
			for (Iterator iter = wpDescToDeliverableParts.entrySet().iterator(); iter
			.hasNext();) {
				Map.Entry entry = (Map.Entry) iter.next();
				Object wpDesc = entry.getKey();
				updateDeliverablePartsCmd.getObjectToNewFeatureValuesMap().put(wpDesc, 
						Collections.singletonMap(UmaPackage.eINSTANCE.getWorkProductDescriptor_DeliverableParts(), entry.getValue()));
			}
		}
		return !taskDescList.isEmpty()
				|| !roleDescList.isEmpty()
				|| !wpDescList.isEmpty()
				|| (updateDeliverablePartsCmd != null && updateDeliverablePartsCmd.canExecute())
				|| !wpdToDeliverableDescriptorMap.isEmpty()
				|| (descriptorsToRefresh != null && !descriptorsToRefresh
						.isEmpty())
				|| batchCommand.canExecute();
	}
	
	/**
	 * @param subArtifact
	 * @return
	 */
	private boolean prepareAdd(WorkProduct wp) {
		if (synchronize) {	
			// get linked tasks of the task descriptors in the activity for the configuration
			//
			List tasks = getLinkedTasks();
			
			if(!tasks.isEmpty()) {
				// select only work products that are input or output of an existing task in the activity
				//	
				Map featuresMap = ProcessCommandUtil.getFeaturesMap(tasks, wp, getMethodConfiguration());
				if(!featuresMap.isEmpty()) {
					// use the wp to the key in the map right now and replace it with
					// its work product descriptor later
					//
					if(wpdToTaskFeaturesMap == null) {
						wpdToTaskFeaturesMap = new HashMap();
					}
					wpdToTaskFeaturesMap.put(wp, featuresMap);
					return true;
				}
			}
			return false;
		}
		return true;
	}

	/**
	 * @return
	 */
	private List getLinkedTasks() {
		if(linkedTasks == null) {
			linkedTasks = new ArrayList();
			for (Iterator iter = activity.getBreakdownElements().iterator(); iter.hasNext();) {
				Object element = iter.next();
				if(element instanceof TaskDescriptor) {
					Task task = ((TaskDescriptor)element).getTask();
					if(task != null && configurator.accept(element)) {
						linkedTasks.add(task);
					}
				}
			}
		}
		return linkedTasks;
	}

	/**
	 * Removes new elements that are duplicate b/c they are created by the previous WBSDropCommand
	 */
	private void removeNewDuplicates() {
		if(synchronize) {
			if(!newDuplicatesRemoved) {
				for (Iterator iter = roleDescList.iterator(); iter.hasNext();) {
					RoleDescriptor roleDesc = (RoleDescriptor) iter.next();
					Object desc = ProcessCommandUtil.getDescriptor(roleDesc.getRole(), activity, getMethodConfiguration());
					if(desc != null) {
						iter.remove();
					}
				}
				newDuplicatesRemoved = true;			
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.library.edit.process.command.BSDropCommand#doExecute()
	 */
	protected void doExecute() {
		removeNewDuplicates();
		
		// automatically add work product descriptor to deliverable part
		//
		if (!wpdToDeliverableDescriptorMap.isEmpty()) {
			for (Iterator iter = wpdToDeliverableDescriptorMap.entrySet()
					.iterator(); iter.hasNext();) {
				Map.Entry entry = (Map.Entry) iter.next();
				WorkProductDescriptor deliverable = (WorkProductDescriptor) entry
						.getValue();
				deliverable.getDeliverableParts().add(entry.getKey());
			}
		}

		// add work product descriptors
		//
		activity.getBreakdownElements().addAll(wpDescList);

		// add task descriptors
		//
		activity.getBreakdownElements().addAll(taskDescList);

		// add role descriptors
		//
		activity.getBreakdownElements().addAll(roleDescList);
		
		// associate new work product descriptors with task descriptors
		//
		if(wpdToTaskFeaturesMap != null) {
			for (Iterator iter = wpdToTaskFeaturesMap.entrySet().iterator(); iter.hasNext();) {
				Map.Entry entry = (Map.Entry) iter.next();
				Map taskFeatures = (Map) entry.getValue();
				for (Iterator iterator = taskFeatures.entrySet().iterator(); iterator
				.hasNext();) {
					Map.Entry ent = (Map.Entry) iterator.next();
					TaskDescriptor taskDesc = (TaskDescriptor) ProcessCommandUtil.getDescriptor(ent.getKey(), activity.getBreakdownElements(), getMethodConfiguration(), false);
					for (Iterator iterator1 = ((Collection)ent.getValue()).iterator(); iterator1
					.hasNext();) {
						EStructuralFeature f = (EStructuralFeature) iterator1.next();
						EStructuralFeature descFeature = (EStructuralFeature) FEATURE_MAP.get(f);
						if(descFeature.isMany()) {
							((List)taskDesc.eGet(descFeature)).add(entry.getKey());							
						}
						else {
							// TODO: need to back up old value here
							//
							taskDesc.eSet(descFeature, entry.getKey());
						}
					}
				}
			}
		}

		// add new descriptors to activity's package
		//
		ProcessPackage pkg = (ProcessPackage) activity.eContainer();
		if (pkg != null) {
			pkg.getProcessElements().addAll(taskDescList);
			pkg.getProcessElements().addAll(roleDescList);
			pkg.getProcessElements().addAll(wpDescList);

			for (Iterator iter = wpDescToDeliverableParts.values().iterator(); iter
					.hasNext();) {
				pkg.getProcessElements().addAll((Collection) iter.next());
			}
		}

//		getModifiedResources().add(activity.eResource());
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.epf.library.edit.process.command.BSDropCommand#updateDescriptors()
	 */
	protected void updateDescriptors() {
		super.updateDescriptors();
		
		// add deliverable parts to the work product descriptors
		//
		if(updateDeliverablePartsCmd != null) {
			updateDeliverablePartsCmd.execute();
		}
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.epf.library.edit.process.command.BSDropCommand#undoUpdateDescriptors()
	 */
	protected void undoUpdateDescriptors() {
		// remove deliverable parts of the work product descriptors
		//
		if(updateDeliverablePartsCmd != null) {
			updateDeliverablePartsCmd.undo();
		}

		super.undoUpdateDescriptors();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.library.edit.process.command.BSDropCommand#doUndo()
	 */
	protected void doUndo() {
		// remove work product descriptors
		//
		activity.getBreakdownElements().removeAll(wpDescList);

		// remove role descriptors
		//
		activity.getBreakdownElements().removeAll(roleDescList);

		// remove task descriptors
		//
		activity.getBreakdownElements().removeAll(taskDescList);

		// remove work product descriptor from deliverable part
		//
		if (!wpdToDeliverableDescriptorMap.isEmpty()) {
			for (Iterator iter = wpdToDeliverableDescriptorMap.entrySet()
					.iterator(); iter.hasNext();) {
				Map.Entry entry = (Map.Entry) iter.next();
				WorkProductDescriptor deliverable = (WorkProductDescriptor) entry
						.getValue();
				deliverable.getDeliverableParts().remove(entry.getKey());
			}
		}

		// remove descriptors from activity's package
		//
		ProcessPackage pkg = (ProcessPackage) activity.eContainer();
		if (pkg != null) {
			pkg.getProcessElements().removeAll(taskDescList);
			pkg.getProcessElements().removeAll(roleDescList);
			pkg.getProcessElements().removeAll(wpDescList);
			for (Iterator iter = wpDescToDeliverableParts.values().iterator(); iter
					.hasNext();) {
				pkg.getProcessElements().removeAll((Collection) iter.next());
			}
		}
		
		// disassociate new work product descriptors with task descriptors
		//
		if(wpdToTaskFeaturesMap != null) {
			for (Iterator iter = wpdToTaskFeaturesMap.entrySet().iterator(); iter.hasNext();) {
				Map.Entry entry = (Map.Entry) iter.next();
				Map taskFeatures = (Map) entry.getValue();
				for (Iterator iterator = taskFeatures.entrySet().iterator(); iterator
						.hasNext();) {
					Map.Entry ent = (Map.Entry) iterator.next();
					TaskDescriptor taskDesc = (TaskDescriptor) ProcessCommandUtil.getDescriptor(ent.getKey(), activity, getMethodConfiguration());
					for (Iterator iterator1 = ((Collection)ent.getValue()).iterator(); iterator1
							.hasNext();) {
						EStructuralFeature f = (EStructuralFeature) iterator1.next();
						EStructuralFeature descFeature = (EStructuralFeature) FEATURE_MAP.get(f);
						if(descFeature.isMany()) {
							((List)taskDesc.eGet(descFeature)).remove(entry.getKey());							
						}
						else {
							taskDesc.eSet(descFeature, null);
						}
					}
				}
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.common.command.AbstractCommand#getAffectedObjects()
	 */
	public Collection getAffectedObjects() {
		if (wpDescList != null) {
			return wpDescList;
		}

		return super.getAffectedObjects();
	}

	/**
	 * 
	 * @param task
	 * @param activity
	 * @param taskDescList
	 * @param roleDescList
	 * @param wpDescList
	 * @param wpDescToDeliverableParts
	 * @param descriptorsToRefresh
	 * @return <code>true</code> if a new TaskDescriptor is created for the
	 *         given task, <code>false</code> otherwise
	 */
	static boolean addToDescriptorLists(Task task, Activity activity,
			List taskDescList, List roleDescList, List wpDescList,
			Map wpDescToDeliverableParts, Set descriptorsToRefresh,
			Map descriptorToNewFeatureValuesMap, MethodConfiguration config, Set synchFeatures) {
		TaskDescriptor desc = ProcessCommandUtil.createTaskDescriptor(task, activity,
				roleDescList, wpDescList, wpDescToDeliverableParts, null,
				descriptorsToRefresh, descriptorToNewFeatureValuesMap, 
				config, synchFeatures);
		if ((desc != null) && (taskDescList != null)
				&& !taskDescList.contains(desc)) {
			taskDescList.add(desc);
		}
		return desc != null;
	}

	static void addResponsibleRole(WorkProductDescriptor wpDesc,
			Activity activity, List roleDescList, Set descriptorsToRefresh, MethodConfiguration config) {
		RoleDescriptor roleDesc = ProcessCommandUtil.createRoleDescriptor(wpDesc,
				activity, roleDescList, descriptorsToRefresh, config);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.epf.library.edit.process.command.BSDropCommand#dispose()
	 */
	public void dispose() {
		if(roleDescList != null) {
			roleDescList.clear();
		}
		if(wpDescList != null) {
			wpDescList.clear();
		}
		if(wpDescToDeliverableParts != null) {
			wpDescToDeliverableParts.clear();
		}
		if(wpdToDeliverableDescriptorMap != null) {
			wpdToDeliverableDescriptorMap.clear();
		}
		if(wpdToTaskFeaturesMap != null) {
			wpdToTaskFeaturesMap.clear();
		}
		
		super.dispose();
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.epf.library.edit.process.command.BSDropCommand#executeNestedCommands()
	 */
	protected void executeNestedCommands() {
		List nestedCommandProviders = ExtensionManager.getNestedCommandProviders();
		if(!nestedCommandProviders.isEmpty()) {
			if(!taskDescList.isEmpty()) {
				for (Iterator iter = nestedCommandProviders.iterator(); iter
						.hasNext();) {
					INestedCommandProvider cmdProvider = (INestedCommandProvider) iter.next();
					try {
						Command cmd = cmdProvider.createRelatedObjects(taskDescList, PBSDropCommand.this);
						if(cmd != null && cmd.canExecute()) {							
							cmd.execute();
							getNestedCommands().add(cmd);
						}
					}
					catch(Exception e) {
						LibraryEditPlugin.getDefault().getLogger().logError(e);
					}
				}
			}
		}

		super.executeNestedCommands();
	}
}
