//------------------------------------------------------------------------------
// 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.ListIterator;
import java.util.Set;

import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.util.AbstractTreeIterator;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.epf.library.edit.IConfigurator;
import org.eclipse.epf.library.edit.LibraryEditPlugin;
import org.eclipse.epf.library.edit.LibraryEditResources;
import org.eclipse.epf.library.edit.Providers;
import org.eclipse.epf.library.edit.TngAdapterFactory;
import org.eclipse.epf.library.edit.command.IResourceAwareCommand;
import org.eclipse.epf.library.edit.ui.UserInteractionHelper;
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.Deliverable;
import org.eclipse.epf.uma.Descriptor;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.Process;
import org.eclipse.epf.uma.Role;
import org.eclipse.epf.uma.RoleDescriptor;
import org.eclipse.epf.uma.Task;
import org.eclipse.epf.uma.TaskDescriptor;
import org.eclipse.epf.uma.TeamProfile;
import org.eclipse.epf.uma.WorkProduct;
import org.eclipse.epf.uma.WorkProductDescriptor;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.widgets.Display;


/**
 * Command for one-way synchronization from method to process.
 * 
 * @author Phong Nguyen Le - Nov 22, 2005
 * @since 1.0
 */
public class SynchronizeCommand extends CompoundCommand implements
		IResourceAwareCommand {

	private Collection elements;

	private IConfigurator configurator;

	protected boolean aborted;

	private ArrayList deleteList;

	protected boolean preExecSuccessful;

	protected List deleteCommandList;

	protected boolean successful;

	private MethodConfiguration config;

	private Set synchFeatures;

	private DeleteUnusedDescriptorsCommand deleteUnusedDescriptorsCommand;
	
	private Collection activities;

	private boolean showSuccessfulMsg = true;

	private boolean intialized;

//	private Object UIContext;

	/**
	 * Constructs a SynchronizeCommand that use the process default
	 * configuration to synchronize all the synchronizable features
	 * 
	 * @param elements
	 *            a list of BreakdownElement objects
	 */
	public SynchronizeCommand(String label, Collection elements) {
		super(label);
		this.elements = elements;
	}
	
	/**
	 * Constructs a SynchronizeCommand that use the given configuration 
	 * and list of synchronizable features to synchronize
	 * 
	 * @param elements
	 * @param config the configuration
	 * @param synchFeatures the synchronizable features
	 */
	public SynchronizeCommand(Collection elements, MethodConfiguration config, Set synchFeatures, boolean showSuccessfulMsg) {
		super(LibraryEditResources.AutoSynchronizeCommand_label); 
		this.elements = elements;
		this.config = config;
		this.synchFeatures = synchFeatures;
		this.showSuccessfulMsg = showSuccessfulMsg; 
	}
	
	public void setMethodConfiguration(MethodConfiguration config) {
		this.config = config;
		if(configurator != null) {
			configurator.setMethodConfiguration(config);
		}
	}
	
	public void setSynchronizationFeatures(Set synchFeatures) {
		this.synchFeatures = synchFeatures;
	}
	
	private boolean doInitialize() {
		if (elements == null || elements.isEmpty()) {
			return false;
		}

		deleteCommandList = new ArrayList();
		deleteList = new ArrayList();
		activities = new ArrayList();
		for (Iterator iter = elements.iterator(); iter.hasNext();) {
			Object element = (Object) iter.next();
			addToDeleteList(element, deleteList);
			if(element instanceof Activity) {
				activities.add(element);
			}
		}
		elements.removeAll(deleteList);

		for (Iterator iter = elements.iterator(); iter.hasNext();) {
			Object object = iter.next();
			if (object instanceof Descriptor) {
				Descriptor descriptor = (Descriptor) object;
				if (descriptor.getIsSynchronizedWithSource().booleanValue()
						&& !deleteList.contains(descriptor)) {
					if(descriptor.getSuperActivities() == null) {						
						// descriptor is used by TeamProfile to represent a role or used by a deliverable descriptor to
						// represent a deliverable part
						//
						if(descriptor instanceof WorkProductDescriptor && ((WorkProductDescriptor)descriptor).getWorkProduct() instanceof Deliverable) {
							append(new SynchronizeDeliverableDescriptorCommand((WorkProductDescriptor) descriptor, synchFeatures, config));
						}
						else {
							append(new BasicSynchronizeDescriptorCommand(descriptor, synchFeatures, config));
						}
					}
					else {
						Activity act = descriptor.getSuperActivities();
						if (object instanceof TaskDescriptor) {
							Task task = ((TaskDescriptor) object).getTask();
							if (task != null) {
								append(new WBSDropCommand(act, Collections
										.singletonList(task), config, synchFeatures));
							}
						} else if (object instanceof RoleDescriptor) {
							Role role = ((RoleDescriptor) object).getRole();
							if (role != null) {
								append(new OBSDropCommand(act, Collections
										.singletonList(role), config, synchFeatures, configurator));
							}
						} else if (object instanceof WorkProductDescriptor) {
							WorkProduct wp = ((WorkProductDescriptor) object)
							.getWorkProduct();
							if (wp != null) {
								append(new PBSDropCommand(act, Collections
										.singletonList(wp), config, synchFeatures, configurator));
							}
						}
					}
				}
			} else if (object instanceof Activity) {
				appendCommands((Activity) object);
			} else if (object instanceof TeamProfile) {
				appendCommands((TeamProfile)object);
			}
		}
		return !deleteList.isEmpty() || !commandList.isEmpty() || !activities.isEmpty();
	}

	public boolean initilize() {
		return intialized = doInitialize();		
	}
	
	/**
	 * @return the intialized
	 */
	public boolean isIntialized() {
		return intialized;
	}

	/**
	 * @param activity
	 */
	private void appendCommands(Activity activity) {
		List tasks = new ArrayList();
		List roles = new ArrayList();
		List workProducts = new ArrayList();
		List activities = new ArrayList();
		for (Iterator iter = activity.getBreakdownElements().iterator(); iter
				.hasNext();) {
			Object element = iter.next();
			if (element instanceof Descriptor) {
				if (((Descriptor) element).getIsSynchronizedWithSource()
						.booleanValue()
						&& !deleteList.contains(element)) {
					if (element instanceof TaskDescriptor) {
						Task task = ((TaskDescriptor) element).getTask();
						if (task != null) {
							tasks.add(task);
						}
					} else if (element instanceof RoleDescriptor) {
						Role role = ((RoleDescriptor) element).getRole();
						if (role != null) {
							roles.add(role);
						}
					} else if (element instanceof WorkProductDescriptor) {
						WorkProduct wp = ((WorkProductDescriptor) element)
								.getWorkProduct();
						if (wp != null) {
							workProducts.add(wp);
						}
					}
				}
			} else if (element instanceof Activity) {
				activities.add(element);
			} else if (element instanceof TeamProfile) {
				appendCommands((TeamProfile)element);
			}
		}
		if (!tasks.isEmpty()) {
			append(new WBSDropCommand(activity, tasks, config, synchFeatures));
		}
		if (!roles.isEmpty()) {
			append(new OBSDropCommand(activity, roles, config, synchFeatures, configurator));
		}
		if (!workProducts.isEmpty()) {
			append(new PBSDropCommand(activity, workProducts, config,
					synchFeatures, configurator));
		}
		for (Iterator iter = activities.iterator(); iter.hasNext();) {
			appendCommands((Activity) iter.next());
		}
	}
	
	private void appendCommands(TeamProfile team) {
		Iterator iter = new AbstractTreeIterator(team, false) {

			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			protected Iterator getChildren(Object object) {
				if(object instanceof TeamProfile) {
					TeamProfile team = ((TeamProfile)object);
					List children = new ArrayList(team.getSubTeam());
					children.addAll(team.getTeamRoles());
					return children.iterator();
				}
				else {
					return Collections.EMPTY_LIST.iterator();
				}
			}
			
		};
		while(iter.hasNext()) {
			Object obj = iter.next();
			
			// synch only own role descriptor of team profile
			//
			if(obj instanceof RoleDescriptor && ((RoleDescriptor)obj).getSuperActivities() == null) {
				append(new BasicSynchronizeDescriptorCommand((Descriptor) obj, synchFeatures, config));
			}
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.library.edit.command.IResourceAwareCommand#getModifiedResources()
	 */
	public Collection getModifiedResources() {
		HashSet modifiedResources = new HashSet();
		for (Iterator iter = commandList.iterator(); iter.hasNext();) {
			IResourceAwareCommand cmd = (IResourceAwareCommand) iter.next();
			modifiedResources.addAll(cmd.getModifiedResources());
		}
		return modifiedResources;
	}
	
//	public void setUIContext(Object UIContext) {
//		this.UIContext = UIContext;
//	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.common.command.CompoundCommand#execute()
	 */
	public void execute() {
		// delete all descriptors whose linked element are no longer in the
		// configuration
		//
		if (!deleteList.isEmpty()) {
			Command cmd = delete(deleteList);
			if (cmd != null) {
				deleteCommandList.add(cmd);
			}
		}

		if (!aborted) {
//			IRunnableWithProgress runnable = new IRunnableWithProgress() {
//
//				public void run(IProgressMonitor monitor)
//						throws InvocationTargetException, InterruptedException {
//					preExecSuccessful = preExecute();
//				}
//
//			};
//						
//			UserInteractionHelper.runWithProgress(runnable, false, null);
//
//			if (preExecSuccessful) {
//				try {
//					BusyIndicator.showWhile(Display.getCurrent(), new Runnable() {
//						
//						public void run() {
//							superRedo();
//							successful = true;
//						}
//						
//					});
//				}
//				catch(RuntimeException e) {
//					LibraryEditPlugin.getDefault().getMsgDialog().displayError(getLabel(), e.toString());
//				}
//			}
			
			Runnable runnable = new Runnable() {

				public void run() {
					preExecSuccessful = preExecute();
					if(preExecute()) {
						superRedo();
						successful = true;
					}
				}

			};
			UserInteractionHelper.runInUI(runnable, getLabel());
			
			if (successful) {				
				if(!activities.isEmpty()) {
					if(deleteUnusedDescriptorsCommand == null) {
						deleteUnusedDescriptorsCommand = new DeleteUnusedDescriptorsCommand(elements, true, deleteList) {
							
							protected Command delete(List elements) {
								return SynchronizeCommand.this.delete(elements);
							}
							
						};
						deleteCommandList.add(deleteUnusedDescriptorsCommand);
					}
					deleteUnusedDescriptorsCommand.execute();
				}
				
				if (showSuccessfulMsg) {
					LibraryEditPlugin
							.getDefault()
							.getMsgDialog()
							.displayInfo(
									getLabel(),
									LibraryEditResources.AutoSynchronizeCommand_sucessful); //$NON-NLS-1$
				}
			}
		}
	}

	private void superRedo() {
		super.redo();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.common.command.CompoundCommand#redo()
	 */
	public void redo() {
		execute();
	}

	public boolean isSucessful() {
		return successful;
	}

	protected boolean preExecute() {
		for (ListIterator commands = commandList.listIterator(); commands
				.hasNext();) {
			Object command = commands.next();
			if(command instanceof BSDropCommand && !((BSDropCommand)command).preExecute()) {
				return false;
			}
			
			// be polite to other threads (no effect on some platforms)
			//
			Thread.yield();
		}
		return true;
	}

	/**
	 * Deletes the specified elements
	 * @param elements
	 * @return the executed command that deleted the given elements
	 */
	protected Command delete(List elements) {
		EditingDomain domain = new AdapterFactoryEditingDomain(
				TngAdapterFactory.INSTANCE.getProcessComposedAdapterFactory(),
				new BasicCommandStack());
		Command cmd = new ProcessElementDeleteCommand(RemoveCommand.create(domain,
				elements), elements);
		cmd.execute();
		return cmd;
	}

	private IConfigurator getConfigurator() {
		if (configurator == null) {
			MethodConfiguration config = this.config;
			if(config == null) {
				Process proc = TngUtil.getOwningProcess((BreakdownElement) elements
						.iterator().next());
				config = proc.getDefaultContext();
			}
			configurator = Providers.getConfiguratorFactory()
					.createConfigurator(config);
		}
		return configurator;
	}

	/**
	 * @param element
	 * @param deleteList
	 */
	private void addToDeleteList(Object element, List deleteList) {
		if (element instanceof Descriptor) {
			if (!getConfigurator().accept(element)) {
				deleteList.add(element);
			}
		} else if (element instanceof Activity) {
			for (Iterator iter = ((Activity) element).getBreakdownElements()
					.iterator(); iter.hasNext();) {
				addToDeleteList(iter.next(), deleteList);
			}
		}
	}

	private void superUndo() {
		super.undo();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.common.command.CompoundCommand#undo()
	 */
	public void undo() {
		BusyIndicator.showWhile(Display.getCurrent(), new Runnable() {

			public void run() {
				if (!deleteCommandList.isEmpty()) {
					for (ListIterator commands = deleteCommandList
							.listIterator(deleteCommandList.size()); commands
							.hasPrevious();) {
						try {
							Command command = (Command) commands.previous();
							command.undo();
						} catch (RuntimeException exception) {
							// Skip over the command that threw the exception.
							//
							commands.next();

							try {
								// Iterate forward over the undone commands to
								// redo them.
								//
								while (commands.hasNext()) {
									Command command = (Command) commands.next();
									command.redo();
								}
							} catch (RuntimeException nestedException) {
								CommonPlugin.INSTANCE
										.log(new WrappedException(
												CommonPlugin.INSTANCE
														.getString("_UI_IgnoreException_exception"), nestedException).fillInStackTrace()); //$NON-NLS-1$
							}

							throw exception;
						}
					}
				}

				superUndo();
			}

		});
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.common.command.CompoundCommand#prepare()
	 */
	protected boolean prepare() {
		return true;
	}
}
