//------------------------------------------------------------------------------
// 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.util;

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

import org.eclipse.emf.common.command.AbstractCommand;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.provider.AdapterFactoryTreeIterator;
import org.eclipse.emf.edit.provider.ITreeItemContentProvider;
import org.eclipse.emf.edit.provider.ItemProviderAdapter;
import org.eclipse.emf.edit.provider.WrapperItemProvider;
import org.eclipse.epf.library.edit.IConfigurator;
import org.eclipse.epf.library.edit.IFilter;
import org.eclipse.epf.library.edit.LibraryEditResources;
import org.eclipse.epf.library.edit.TngAdapterFactory;
import org.eclipse.epf.library.edit.command.IResourceAwareCommand;
import org.eclipse.epf.library.edit.process.BSActivityItemProvider;
import org.eclipse.epf.library.edit.process.BreakdownElementWrapperItemProvider;
import org.eclipse.epf.library.edit.process.IBSItemProvider;
import org.eclipse.epf.library.edit.process.command.RemoveUnusedDescriptorsCommand;
import org.eclipse.epf.uma.Activity;
import org.eclipse.epf.uma.Artifact;
import org.eclipse.epf.uma.BreakdownElement;
import org.eclipse.epf.uma.Constraint;
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.RoleDescriptor;
import org.eclipse.epf.uma.TaskDescriptor;
import org.eclipse.epf.uma.TeamProfile;
import org.eclipse.epf.uma.VariabilityElement;
import org.eclipse.epf.uma.VariabilityType;
import org.eclipse.epf.uma.WorkBreakdownElement;
import org.eclipse.epf.uma.WorkProduct;
import org.eclipse.epf.uma.WorkProductDescriptor;
import org.eclipse.epf.uma.ecore.impl.MultiResourceEObject;
import org.eclipse.epf.uma.ecore.util.OppositeFeature;
import org.eclipse.epf.uma.util.AssociationHelper;
import org.eclipse.epf.uma.util.UmaUtil;

import com.ibm.icu.util.StringTokenizer;


/**
 * @author Phong Nguyen Le - Aug 9, 2005
 * @since 1.0
 */
public class Suppression {

	public static final String WBS = "wbs"; //$NON-NLS-1$

	private static final String TBS = "tbs"; //$NON-NLS-1$

	private static final String WPBS = "wpbs"; //$NON-NLS-1$

	private static final String CBS = "cbs"; //$NON-NLS-1$

	private static final Map procToSuppressionMap = new HashMap();

	private static boolean autoInheritIntermediateSuppressionState = true;

	private Process process;

	private Set suppressedExternalElementPaths;

	private boolean modified;

	private Set internalUnsuppressedElements = new HashSet();

	public static final void clearCachedSuppressions() {
		procToSuppressionMap.clear();
	}
	
	/**
	 * Removes suppressions of invalid processes
	 */
	public static final void cleanUp() {
		ArrayList listToRemove = new ArrayList();
		for (Iterator iter = new ArrayList(procToSuppressionMap.keySet()).iterator(); iter.hasNext();) {
			Process proc = (Process) iter.next();
			if(proc.eIsProxy()) {
				listToRemove.add(proc);
			}
		}
		int size = listToRemove.size();
		for (int i = 0; i < size; i++) {
			procToSuppressionMap.remove(listToRemove.get(i));
		}
	}

	public static final void setAutoInheritSuppressionStates(boolean b) {
		autoInheritIntermediateSuppressionState = b;
	}

	public Suppression(Process process) {
		this.process = process;
		suppressedExternalElementPaths = loadSuppressedElementPaths();
	}

	public Process getProcess() {
		return process;
	}

	private Set loadSuppressedElementPaths() {
		Constraint rule = ConstraintManager.getConstraint(process,
				ConstraintManager.PROCESS_SUPPRESSION, false); // (Constraint)
		// process.getOwnedRules().get(0);
		if (rule != null && rule.getBody().length() > 0) {
			Set paths = new HashSet();
			for (StringTokenizer tokens = new StringTokenizer(rule.getBody()); tokens
					.hasMoreTokens();) {
				paths.add(tokens.nextToken());
			}
			return paths;
		}
		return null;
	}

	private Set getSuppressedExternalElementPaths() {
		if (suppressedExternalElementPaths == null) {
			suppressedExternalElementPaths = loadSuppressedElementPaths();
			if (suppressedExternalElementPaths == null) {
				suppressedExternalElementPaths = new HashSet();
			}
		}
		return suppressedExternalElementPaths;
	}

	/**
	 * @param process
	 * @param selection
	 * @return
	 */
	public boolean hasSuppressed(Collection selection) {
		for (Iterator iter = selection.iterator(); iter.hasNext();) {
			Object element = iter.next();
			if (element instanceof BreakdownElementWrapperItemProvider) {
				if (isSuppressed((BreakdownElementWrapperItemProvider) element)) {
					return true;
				}
			} else if (element instanceof BreakdownElement) {
				if (isSuppressed((BreakdownElement) element)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Checks if the given selection has any element that is not suppressed yet
	 * in this suppression's process
	 * 
	 * @param selection
	 * @return
	 */
	public boolean canSuppress(Collection selection) {
		return canDo(selection, true);
	}

	/**
	 * Checks if the given selection has any element that is suppressed in this
	 * suppression's process
	 * 
	 * @param selection
	 * @return
	 */
	public boolean canReveal(Collection selection) {
		return canDo(selection, false);
	}

	/**
	 * Extracts items from the given selection that can be suppressed/revealed
	 * (depending on the <code>suppressed</code> parameter)
	 * 
	 * @param selection
	 * @param suppressed
	 * @return
	 */
	private Collection getApplicableItems(Collection selection,
			boolean suppressed) {
		ArrayList applicableItems = new ArrayList();
		for (Iterator iter = selection.iterator(); iter.hasNext();) {
			Object element = iter.next();
			if (element instanceof BreakdownElementWrapperItemProvider) {
				BreakdownElementWrapperItemProvider wrapper = (BreakdownElementWrapperItemProvider) element;
				if (wrapper.isReadOnly()) {
					if (isInSuppressedList(wrapper) != suppressed) {
						applicableItems.add(element);
					}
				} else {
					Object e = TngUtil.unwrap(wrapper);
					if (e instanceof MethodElement
							&& ((MethodElement) e).getSuppressed()
									.booleanValue() != suppressed) {
						applicableItems.add(element);
					}
				}
			} else if (element instanceof MethodElement) {
				if (((MethodElement) element).getSuppressed().booleanValue() != suppressed) {
					applicableItems.add(element);
				}
			}
		}
		return applicableItems;
	}
	
	/**
	 * Checks if the given element is valid to suppress/reveal upon
	 * @param element
	 * @return
	 */
	public static boolean isValid(Object element) {
		// don't show suppress/reveal for roledescriptor under team, work product descriptor under deliverable work product,
		// and rolled-up item
		//
		BreakdownElement obj = null;
		if (element instanceof BreakdownElementWrapperItemProvider) {
			BreakdownElementWrapperItemProvider wrapper = (BreakdownElementWrapperItemProvider) element;
			if(wrapper.isRollupChild()) {
				return false;
			}
			obj = (BreakdownElement) TngUtil.unwrap(wrapper);
		}
		else if (element instanceof BreakdownElement)
			obj = (BreakdownElement) element;
		if (obj instanceof RoleDescriptor || obj instanceof WorkProductDescriptor) {
			if (obj.getSuperActivities() == null
					|| obj.getSuperActivities() == null) {
				return false;
			}
		}
		
		return true;
	}

	/**
	 * Checks if the given selection has any item that can be
	 * suppressed/revealed (depending on the <code>suppressed</code>
	 * parameter)
	 * 
	 * @param selection
	 * @param suppressed
	 * @return
	 */
	private boolean canDo(Collection selection, boolean suppressed) {
		for (Iterator iter = selection.iterator(); iter.hasNext();) {
			Object element = iter.next();
			if(isValid(element)) {				
				if (element instanceof BreakdownElementWrapperItemProvider) {
					BreakdownElementWrapperItemProvider wrapper = (BreakdownElementWrapperItemProvider) element;
					if (wrapper.isReadOnly()) {
						if (isInSuppressedList(wrapper) != suppressed) {
							return true;
						}
					} else {
						Object e = TngUtil.unwrap(wrapper);
						if (e instanceof MethodElement &&
								((MethodElement) e).getSuppressed().booleanValue() != suppressed) {
						}
					}
				} else if (element instanceof MethodElement) {
					if (((MethodElement) element).getSuppressed().booleanValue() != suppressed) {
						return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * A breakdown element is suppressed if itself or any of its parent activity
	 * is suppressed
	 * 
	 * @param e
	 * @return
	 */
	private boolean __isSuppressed(BreakdownElement e) {
		return getSuppressed(e, true, null) != null;
	}

	private static boolean isDirectlySuppressed(MethodElement e) {
		Boolean b = e.getSuppressed();
		if (b != null && b.booleanValue()) {
			return true;
		}
		if (e instanceof VariabilityElement) {
			VariabilityElement ve = (VariabilityElement) e;
			VariabilityType variabilityType = ve.getVariabilityType();
			if (variabilityType == VariabilityType.EXTENDS_LITERAL
					|| variabilityType == VariabilityType.CONTRIBUTES_LITERAL) {
				if (ve.getVariabilityBasedOnElement() != null) {
					return isDirectlySuppressed(ve
							.getVariabilityBasedOnElement());
				}
			}
		}

		return false;
	}

	/**
	 * Gets the suppressed breakdown element that made the given breakdown
	 * element become suppressed. This method will check the suppression state of the parent upto the specified
	 * <code>top</code> only if <code>checkParent</code> is <code>true</code>. It will not check the suppression state of
	 * <code>top</code>
	 * @param e
	 * @return
	 */
	private static BreakdownElement getSuppressed(BreakdownElement e, boolean checkParent, Object top) {
		if (e.getSuppressed().booleanValue()) {
			return e;
		}

		if(checkParent) {
			for (BreakdownElement be = e; be != top && be.getSuperActivities() != null;) {
				be = be.getSuperActivities();
				if (be.getSuppressed().booleanValue()) {
					return be;
				}
			}
			
			// special handling for nested artifact descriptors and deliverable parts
			//
			if (e instanceof WorkProductDescriptor) {
				WorkProductDescriptor wpDesc = ((WorkProductDescriptor)e);
				WorkProduct wp = wpDesc.getWorkProduct();			
				if (wp instanceof Artifact) {
					// look for parent artifact descriptor in the same activity
					// and check their suppression state
					//
					Activity act = UmaUtil.getParentActivity(e);
					if (act != null) {
						for (Iterator iter = act.getBreakdownElements().iterator(); iter
						.hasNext();) {
							Object element = iter.next();
							if (element != e
									&& element instanceof WorkProductDescriptor) {
								WorkProductDescriptor wpd = ((WorkProductDescriptor) element);
								WorkProduct otherWp = wpd.getWorkProduct();
								if (otherWp instanceof Artifact
										&& UmaUtil.isContainedBy(wp, otherWp)
										&& wpd.getSuppressed().booleanValue()) {
									return wpd;
								}
							}
						}
					}
				}
				if(wpDesc != top && wpDesc.getSuperActivities() == null) {
					List list = AssociationHelper.getDeliverableDescriptors(wpDesc);
					if(list.size() == 1) {
						// this work product descriptor is a deliverable part of a deliverable work product descriptor
						// check the suppression state of the deliverable work product descriptor
						//
						return getSuppressed((BreakdownElement) list.get(0), checkParent, top);
					}
				}
			}
			// special handling for team profile own role descriptor
			//
			else if (e instanceof RoleDescriptor) {
				if (e.getSuperActivities() == null) {
					List list = AssociationHelper.getTeamProfiles((RoleDescriptor) e);
					if (list.size() == 1) {
						TeamProfile team = (TeamProfile) list.get(0);
						if(team != top && team != null) {
							return getSuppressed(team, checkParent, top);
						}
					}
				}
			}
			// special handling for sub team profile
			//
			else if (e instanceof TeamProfile) {
				if (e.getSuperActivities() == null) {
					TeamProfile superTeam = ((TeamProfile) e).getSuperTeam();
					if (superTeam != top && superTeam != null) {
						return getSuppressed(superTeam, checkParent, top);
					}
				}
			}
		}

		return null;
	}

	public boolean isInSuppressedList(
			BreakdownElementWrapperItemProvider wrapper) {
		if (suppressedExternalElementPaths == null) {
			return false;
		}

		// check if wrapper's path is in suppressedExternalElementPaths set
		//
		String path = getPath(wrapper);
		return suppressedExternalElementPaths.contains(path);
	}

	/**
	 * Gets the suppressed breakdown element that made the given wrapper become
	 * suppressed
	 * 
	 * @param wrapper
	 * @return
	 */
	private BreakdownElement getSuppressed(
			BreakdownElementWrapperItemProvider wrapper) {
		return getSuppressed(wrapper, true);
	}

	/**
	 * Gets the suppressed breakdown element that made the given wrapper become
	 * suppressed
	 * 
	 * @param wrapper
	 * @return
	 */
	private BreakdownElement getSuppressed(
			BreakdownElementWrapperItemProvider wrapper, boolean checkBase) {
		return getSuppressed(wrapper, checkBase, true,
				autoInheritIntermediateSuppressionState);
	}

	/**
	 * Gets the suppressed breakdown element that made the given wrapper become
	 * suppressed
	 * 
	 * @param wrapper
	 * @return
	 */
	private BreakdownElement getSuppressed(
			BreakdownElementWrapperItemProvider wrapper, boolean checkBase,
			boolean checkLocal, boolean inheritSuppressionState) {
		return getSuppressed(wrapper, checkBase, checkLocal, inheritSuppressionState, true, null);
	}
	
	private BreakdownElement getSuppressed(
			BreakdownElementWrapperItemProvider wrapper, boolean checkBase,
			boolean checkLocal, boolean inheritSuppressionState, boolean checkParent, Object top) {
		BreakdownElement e = (BreakdownElement) TngUtil.unwrap(wrapper);
		if (!wrapper.isReadOnly()) {
			// this is a wrapper of local element
			//
			return getSuppressed(e, checkParent, top);
		}

		// check if the base breakdown element is suppressed, directly or
		// indirectly, in its own process
		//
		if (checkBase) {
			BreakdownElement suppressed = getSuppressed(e, checkParent, top);
			if (suppressed != null) {
				return suppressed;
			}
		}

		Object parent = null;
		if (checkLocal) {
			// check if the wrapper is suppressed in this process
			//
			if (isInSuppressedList(wrapper)) {
				return e;
			}

			// check if the any local parent is suppressed
			//
			if(checkParent) {
				parent = wrapper.getParent(wrapper);
				if (parent instanceof BreakdownElement && parent != top) {
					BreakdownElement suppressed = getSuppressed((BreakdownElement) parent, checkParent, top);
					if (suppressed != null) {
						return suppressed;
					}
				}
			}
		}

		// check if the breakdown element is suppressed in one of the immediate
		// base process
		//
		if (inheritSuppressionState) {
			Process proc = TngUtil.getOwningProcess(e);
			Activity inheritor = ProcessUtil.getInheritor(wrapper);
			BreakdownElement base = (BreakdownElement) inheritor
					.getVariabilityBasedOnElement();
			if (base != null) {
				Process immediateBaseProc = TngUtil.getOwningProcess(base);
				if (proc != immediateBaseProc) {
					// find the object in the breakdown structure of the
					// immediate
					// base process that represents the same breakdown element
					//
					Object object = inheritor.getVariabilityBasedOnElement();
					ArrayList objects = new ArrayList(ProcessUtil
							.getParentList(inheritor, wrapper));
					objects.add(e);
					for (Iterator iter = objects.iterator(); iter.hasNext();) {
						Object element = iter.next();
						ITreeItemContentProvider adapter = (ITreeItemContentProvider) wrapper
								.getAdapterFactory().adapt(object,
										ITreeItemContentProvider.class);
						find_child: for (Iterator iterator = adapter
								.getChildren(object).iterator(); iterator
								.hasNext();) {
							Object child = iterator.next();
							if (element == TngUtil.unwrap(child)) {
								object = child;
								break find_child;
							}
						}
					}
					if (object instanceof BreakdownElementWrapperItemProvider) {
						Suppression suppression = getSuppression(immediateBaseProc);
						BreakdownElement element = suppression.getSuppressed(
								(BreakdownElementWrapperItemProvider) object,
								false, true, inheritSuppressionState, checkParent, TngUtil.unwrap(object));
						if (element != null) {
							return element;
						}
					}
				}
			}
		}

		// check if the any inherited parent is suppressed
		//
		if(checkParent) {
			if (parent == null) {
				parent = wrapper.getParent(wrapper);
			}
			if (parent instanceof BreakdownElementWrapperItemProvider && TngUtil.unwrap(parent) != top) {
				return getSuppressed((BreakdownElementWrapperItemProvider) parent,
						checkBase, true, inheritSuppressionState, checkParent, top);
			}
		}

		return null;
	}

	/**
	 * Gets the cached suppression for the given process if one exists or
	 * creates new one
	 * 
	 * @param immediateBaseProc
	 * @return
	 */
	public static Suppression getSuppression(Process proc) {
		Suppression suppression = (Suppression) procToSuppressionMap.get(proc);
		if (suppression == null) {
			synchronized (procToSuppressionMap) {
				suppression = (Suppression) procToSuppressionMap.get(proc);
				if (suppression == null) {
					suppression = new Suppression(proc);
					procToSuppressionMap.put(proc, suppression);
				}
			}
		}
		return suppression;
	}

	private boolean __isSuppressed(BreakdownElementWrapperItemProvider wrapper) {
		return getSuppressed(wrapper) != null;
	}
	
	public void saveToModel() {
		if (suppressedExternalElementPaths == null) {
			return;
		}

		StringBuffer paths = new StringBuffer();
		for (Iterator iter = suppressedExternalElementPaths.iterator(); iter
				.hasNext();) {
			String path = (String) iter.next();
			if (isValid(path)) {
				paths.append(path).append(' ');
			}
		}

		Constraint rule = ConstraintManager.getConstraint(process,
				ConstraintManager.PROCESS_SUPPRESSION, true);
		// if(process.getOwnedRules().isEmpty()) {
		// rule = UmaFactory.eINSTANCE.createConstraint();
		// process.getOwnedRules().add(rule);
		// }
		// else {
		// rule = (Constraint) process.getOwnedRules().get(0);
		// }
		//		
		rule.setBody(paths.toString());
	}

	/**
	 * Checks if the given path is valid in process
	 * 
	 * @param path
	 * @return
	 */
	private boolean isValid(String path) {
		URI uri = URI.createURI(path);
		String type = uri.scheme();
		ConfigurableComposedAdapterFactory adapterFactory = null;
		if (WBS.equals(type)) {
			adapterFactory = (ConfigurableComposedAdapterFactory) TngAdapterFactory.INSTANCE
					.getWBS_ComposedAdapterFactory();
		} else if (TBS.equals(type)) {
			adapterFactory = (ConfigurableComposedAdapterFactory) TngAdapterFactory.INSTANCE
					.getOBS_ComposedAdapterFactory();
		} else if (WPBS.equals(type)) {
			adapterFactory = (ConfigurableComposedAdapterFactory) TngAdapterFactory.INSTANCE
					.getPBS_ComposedAdapterFactory();
		} else if (CBS.equals(type)) {
			adapterFactory = (ConfigurableComposedAdapterFactory) TngAdapterFactory.INSTANCE
					.getProcessComposedAdapterFactory();
		} else {
			return false;
		}

		// use process's default configuration to validate
		//
		MethodConfiguration currentConfig = null;
		IConfigurator configurator = null;
		IFilter filter = adapterFactory.getFilter();
		if (filter instanceof IConfigurator) {
			configurator = (IConfigurator) adapterFactory.getFilter();
			currentConfig = configurator.getMethodConfiguration();
			configurator.setMethodConfiguration(process.getDefaultContext());
		}
		try {
			String guid = uri.authority();
			if (!process.getGuid().equals(guid)) {
				return false;
			}
			Object object = process;
			ITreeItemContentProvider adapter = (ITreeItemContentProvider) adapterFactory
					.adapt(object, ITreeItemContentProvider.class);
			for (int i = 0; i < uri.segmentCount(); i++) {
				guid = uri.segment(i);
				Iterator iter = adapter.getChildren(object).iterator();
				adapter = null;
				find_adapter: while (iter.hasNext()) {
					Object child = iter.next();
					object = TngUtil.unwrap(child);
					if (object instanceof MethodElement) {
						if (guid.equals(((MethodElement) object).getGuid())) {
							if (child instanceof ITreeItemContentProvider) {
								adapter = (ITreeItemContentProvider) child;
							} else {
								adapter = (ITreeItemContentProvider) adapterFactory
										.adapt(child,
												ITreeItemContentProvider.class);
							}
							break find_adapter;
						}
					} else {
						// must be an ItemProviderAdapter
						//
						ItemProviderAdapter itemProvider = (ItemProviderAdapter) object;
						MethodElement e = (MethodElement) itemProvider
								.getTarget();
						if (guid.equals(e.getGuid())) {
							adapter = (ITreeItemContentProvider) itemProvider;
							break find_adapter;
						}
					}
				}
				if (adapter == null) {
					return false;
				}
			}
			return true;
		} finally {
			// restore configuration
			//
			if (configurator != null) {
				configurator.setMethodConfiguration(currentConfig);
			}
		}
	}
	
//	private boolean reveal(List selection, Collection revealedDescriptors) {
//		boolean readOnlyElementAffected = false;
//		for (Iterator iter = selection.iterator(); iter.hasNext();) {
//			Object element = (Object) iter.next();
//			if (element instanceof BreakdownElementWrapperItemProvider) {
//				BreakdownElementWrapperItemProvider wrapper = (BreakdownElementWrapperItemProvider) element;
//				MethodElement e = (MethodElement) TngUtil.unwrap(wrapper);
//				if (!wrapper.isReadOnly()) { // wrapper of local element
//					e.setSuppressed(Boolean.FALSE);
//					if(e instanceof Descriptor) {
//						revealedDescriptors.add(e);
//					}
//				} else { // was suppressed by this process
//
//					// remove the path of suppressed element from the list
//					//
//					String path = getPath(wrapper);
//					getSuppressedExternalElementPaths().remove(path);
//					readOnlyElementAffected = true;
//				}
//			} else if (element instanceof MethodElement) {
//				((MethodElement) element).setSuppressed(Boolean.FALSE);
//				if(element instanceof Descriptor) {
//					revealedDescriptors.add(element);
//				}
//			}
//		}
//		modified = true;
//		return readOnlyElementAffected;
//	}

//	/**
//	 * @param selection
//	 * @return true if this call revealed any read-only element
//	 */
//	public boolean reveal(List selection) {
//		ArrayList revealedDescriptors = new ArrayList();
//		boolean ret = reveal(selection, revealedDescriptors);
//		List descriptorsToReveal = getOwnRelatedElements(revealedDescriptors, false);
//		if(descriptorsToReveal != null) {
//			boolean ret2 = reveal(descriptorsToReveal, revealedDescriptors);
//			return ret || ret2;
//		}
//		return ret;
//	}
	
	/**
	 * @deprecated need to use {@link SuppressionCommand} instead
	 */
	public void reveal(List selection) {
		doSetSuppressed(selection, false);
	}

	private static String getViewType(
			BreakdownElementWrapperItemProvider wrapper) {
		// no need to store separate path for CBS, reuse WBS/TBS/WPBS paths
		//		
		Object e = TngUtil.unwrap(wrapper);
		if (e instanceof WorkBreakdownElement) {
			return WBS;
		} else if (e instanceof TeamProfile || e instanceof RoleDescriptor) {
			return TBS;
		} else if (e instanceof WorkProductDescriptor) {
			return WPBS;
		} else {
			return ""; //$NON-NLS-1$
		}
	}
	
	/**
	 * Gets the process GUID in the given GUID path
	 *  
	 * @param guidPath
	 * @return
	 * @see #getPath(BreakdownElementWrapperItemProvider)
	 */
	public static String getProcessGUID(String guidPath) {
		int id = guidPath.indexOf("://"); //$NON-NLS-1$
		if(id != -1) {
			int beginIndex = id + 3;
			if(beginIndex < guidPath.length()) {
				int endIndex = guidPath.indexOf('/', beginIndex);
				if(endIndex != -1) {
					return guidPath.substring(beginIndex, endIndex);
				}
			}
		}
		return null;
	}

	/**
	 * @param wrapper
	 * @return
	 */
	public static String getPath(BreakdownElementWrapperItemProvider wrapper) {
		StringBuffer path = new StringBuffer(); //$NON-NLS-1$
		List parentList = ProcessUtil.getParentList(null, wrapper);
		if (!parentList.isEmpty()) {
			for (Iterator iter = parentList.iterator(); iter.hasNext();) {
				MethodElement e = (MethodElement) iter.next();
				// exclude TaskDescriptor and RoleDescriptor from the parent
				// path b/c those descriptors can be
				// parent only in CBS view
				if (!(e instanceof TaskDescriptor || e instanceof RoleDescriptor)) {
					path.append('/').append(e.getGuid());
				}
			}
		}
		MethodElement e = (MethodElement) TngUtil.unwrap(wrapper);
		path.append('/').append(e.getGuid());
		String viewType = getViewType(wrapper);
		path.insert(0, ":/").insert(0, viewType); //$NON-NLS-1$
		return path.toString();
	}
	
	private static List getOwnRelatedElements(Collection changedDescriptors, boolean suppressed) {
		if(!changedDescriptors.isEmpty()) {
			ArrayList descriptorsToChange = new ArrayList();
			for (Iterator iter = new ArrayList(changedDescriptors).iterator(); iter
					.hasNext();) {
				Descriptor desc = (Descriptor) iter.next();
				Object[] relationships = null;
				if(desc instanceof TaskDescriptor) {
					relationships = RemoveUnusedDescriptorsCommand.TASK_DESCRIPTOR__RELATIONSHIPS;
				}
				else if(desc instanceof RoleDescriptor) {
					relationships = RemoveUnusedDescriptorsCommand.ROLE_DESCRIPTOR__RELATIONSHIPS;
				}
				else if(desc instanceof WorkProductDescriptor) {
					relationships = RemoveUnusedDescriptorsCommand.WORK_PRODUCT_DESCRIPTOR__RELATIONSHIPS;
				}
				if(relationships != null) {
					for (int i = 0; i < relationships.length; i++) {
						Object feature = relationships[i];
						boolean isMany;
						Object value;
						if(feature instanceof OppositeFeature) {
							OppositeFeature f = (OppositeFeature) feature;
							isMany = f.isMany();
							value = ((MultiResourceEObject)desc).getOppositeFeatureValue(f);
						}
						else {
							EStructuralFeature f = (EStructuralFeature) feature;
							isMany = f.isMany();
							value = desc.eGet(f);
						}
						if(isMany) {
							for (Iterator iterator = ((Collection)value)
									.iterator(); iterator.hasNext();) {
								Descriptor ref = (Descriptor) iterator.next();
								if(ref.getSuppressed().booleanValue() != suppressed &&
										!ProcessUtil.checkDescriptorReferences(changedDescriptors, ref)) {
									descriptorsToChange.add(ref);
									changedDescriptors.add(ref);
								}
							}
						}
						else {
							Descriptor ref = (Descriptor) value;
							if(ref.getSuppressed().booleanValue() != suppressed &&
									!ProcessUtil.checkDescriptorReferences(changedDescriptors, ref)) {
								descriptorsToChange.add(ref);
								changedDescriptors.add(ref);
							}
						}
					}
				}
			}
			return descriptorsToChange;
		}
		return null;
	}

	/**
	 * Suppresses or reveals the given selection
	 * 
	 * @param selection
	 * @param suppressed 
	 * @return {@link Result} object that contains the result of this call
	 */
	private Result setSuppressed(List selection, boolean suppressed) {
		Result result = doSetSuppressed(selection, suppressed);
		
		// if descriptors had been suppressed, related elements that are not used anywhere else also need to be suppressed (just like delete)
		//	
		if(!result.descriptors.isEmpty()) {
			List descriptorsToSuppress = getOwnRelatedElements(result.descriptors, suppressed);
			if(descriptorsToSuppress != null) {
				Result result2 = doSetSuppressed(descriptorsToSuppress, suppressed);
				
				// merge results
				//
				result.elements.addAll(result2.elements);
				result.descriptors.addAll(result2.descriptors);
				result.paths.addAll(result2.paths);
			}
		}
		
		return result;
	}
	
	/**
	 * @deprecated need to use {@link SuppressionCommand} instead
	 */
	public void suppress(List selection) {
		doSetSuppressed(selection, true);
	}
	
//	public boolean suppress(List selection) {
//		ArrayList suppressedDescriptors = new ArrayList();
//		boolean ret = suppress(selection, suppressedDescriptors);
//		
//		// if descriptors had been suppressed, related elements that are not used anywhere else also need to be suppressed (just like delete)
//		//	
//		List descriptorsToSuppress = getOwnRelatedElements(suppressedDescriptors, true);
//		if(descriptorsToSuppress != null) {
//			ArrayList out = new ArrayList();
//			boolean ret2 = suppress(descriptorsToSuppress, out);
//			if(out.size() != descriptorsToSuppress.size()) {
//				LibraryEditPlugin.getDefault().getLogger().logError("Suppression.suppress(List) is buggy."); //$NON-NLS-1$
//			}			
//			return ret || ret2;
//		}
//		return ret;
//	}
	
	private static class Result {
		/** Elements that have been suppressed or revealed */
		Collection elements;
		
		/** Descriptors that have been suppressed or revealed */
		Collection descriptors;
		
		/** Paths that have been added to or removed from <code>suppressedExternalElementPaths</code> */ 
		Collection paths;
		
		Result() {
			elements = new ArrayList();
			descriptors = new ArrayList();			
			paths = new ArrayList();
		}
		
		boolean isEmpty() {
			return elements.isEmpty() && paths.isEmpty();
		}
		
		void clear() {
			elements.clear();
			descriptors.clear();
			paths.clear();
		}
	}
	
	/**
	 * Suppresses or reveals the given selection depending on the value of <code>suppressed</code>
	 * 
	 * @param selection
	 * @param suppressed
	 * @return
	 */
	private Result doSetSuppressed(List selection, boolean suppressed) {
		Result result = new Result();
		for (Iterator iter = selection.iterator(); iter.hasNext();) {
			Object element = (Object) iter.next();
			if (element instanceof BreakdownElementWrapperItemProvider) {
				BreakdownElementWrapperItemProvider wrapper = (BreakdownElementWrapperItemProvider) element;
//				if (!isSuppressed(wrapper)) {
					BreakdownElement e = (BreakdownElement) TngUtil
							.unwrap(wrapper);
					if (!wrapper.isReadOnly()) {
						// wrapper of local element
						//
						if(e.getSuppressed().booleanValue() != suppressed) {
							e.setSuppressed(Boolean.valueOf(suppressed));
							result.elements.add(e);
							if(e instanceof Descriptor) {
								result.descriptors.add(e);
							}
						}
					} else {
						// add the paths of suppressed element to the map
						//
						String path = getPath(wrapper);
						boolean b;
						if(suppressed) {
							b = getSuppressedExternalElementPaths().add(path);
						}
						else {
							b = getSuppressedExternalElementPaths().remove(path);
						}
						if(b) {
							result.paths.add(path);
						}
//					}
				}
			} else if (element instanceof MethodElement) {
				MethodElement e = ((MethodElement) element);
				if(e.getSuppressed().booleanValue() != suppressed) {
					e.setSuppressed(Boolean.valueOf(suppressed));
					result.elements.add(e);
					if(e instanceof Descriptor) {
						result.descriptors.add(e);
					}
				}
			}
		}
		modified = !result.isEmpty();
		return result;		
	}
	
//	private boolean suppress(List selection, Collection suppressedDescriptors) {
//		boolean readOnlyElementSuppressed = false;
//		for (Iterator iter = selection.iterator(); iter.hasNext();) {
//			Object element = (Object) iter.next();
//			if (element instanceof BreakdownElementWrapperItemProvider) {
//				BreakdownElementWrapperItemProvider wrapper = (BreakdownElementWrapperItemProvider) element;
//				if (!isSuppressed(wrapper)) {
//					BreakdownElement e = (BreakdownElement) TngUtil
//							.unwrap(wrapper);
//					if (!wrapper.isReadOnly()) {
//						// wrapper of local element
//						//
//						e.setSuppressed(Boolean.TRUE);
//						if(e instanceof Descriptor) {
//							suppressedDescriptors.add(e);
//						}
//					} else {
//						// add the paths of suppressed element to the map
//						//
//						String path = getPath(wrapper);
//						getSuppressedExternalElementPaths().add(path);
//						readOnlyElementSuppressed = true;
//					}
//				}
//			} else if (element instanceof MethodElement) {
//				((MethodElement) element).setSuppressed(Boolean.TRUE);
//				if(element instanceof Descriptor) {
//					suppressedDescriptors.add(element);
//				}
//			}
//		}
//		modified = true;
//		return readOnlyElementSuppressed;
//	}

	/**
	 * @param selection
	 * @return
	 */
	public boolean hasUnsuppressed(List selection) {
		for (Iterator iter = selection.iterator(); iter.hasNext();) {
			Object element = iter.next();
			if (element instanceof BreakdownElementWrapperItemProvider) {
				if (!isSuppressed((BreakdownElementWrapperItemProvider) element)) {
					return true;
				}
			} else if (element instanceof BreakdownElement) {
				if (!isSuppressed((BreakdownElement) element)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * @return Returns the modified.
	 */
	public boolean isSaveNeeded() {
		return modified;
	}

	public void saveIsDone() {
		modified = false;
	}

	/**
	 * check if the element, or the item provider or adaptor associated with the
	 * element is suppressed.
	 * 
	 * @param e
	 *            MethodElement or an associated wrapper, adaptor, or item
	 *            provider, etc.
	 * @return boolean
	 */
	public boolean isSuppressed(Object e) {
//		long start = System.currentTimeMillis();
		
		try {
			
		if (e == null) {
			return true;
		}

		if (internalUnsuppressedElements.contains(e)) {
			return false;
		}

		if (e instanceof BreakdownElementWrapperItemProvider) {
			return __isSuppressed((BreakdownElementWrapperItemProvider) e);
		} else if (e instanceof BreakdownElement) {
			return __isSuppressed((BreakdownElement) e);
		} else {
			Object targetObj = null;
			if (e instanceof WrapperItemProvider) {
				targetObj = ((WrapperItemProvider) e).getValue();
			} else if (e instanceof ItemProviderAdapter) {
				targetObj = ((ItemProviderAdapter) e).getTarget();
			}

			if (targetObj != null && targetObj != e) {
				return isSuppressed(targetObj);
			}
		}

		return false;
		
		}
		finally {
//			long time = (System.currentTimeMillis() - start);
//			if(time > 1000) {
//				BreakdownElement be = (BreakdownElement)TngUtil.unwrap(e);
//				String msg = "Suppression.isSuppressed(): time taken (ms) " + time +
//				"\n  process: " + (e instanceof BreakdownElementWrapperItemProvider ? ((BreakdownElementWrapperItemProvider)e).getTopItem() : TngUtil.getOwningProcess(be)) +
//				"\n  element: " + ProcessUtil.getLabelWithPath(be);
//				
//				System.out.println(msg);
//				LibraryEditPlugin.getDefault().getLogger().logInfo(msg);
//			}
		}
	}

	/**
	 * Updates the suppression state of the given <code>wrapper</code> from
	 * its base.
	 * 
	 * @param wrapper
	 * @return <code>true</code> if this call modified the suppression state
	 *         of the given wrapper, <code>false</code> otherwise
	 */
	public boolean updateSuppressionFromBase(
			BreakdownElementWrapperItemProvider wrapper) {
		String path = getPath(wrapper);
		boolean ret;
		BreakdownElement e = getSuppressed(wrapper, true, false, true, false, null);
		if (e != null) {
			ret = getSuppressedExternalElementPaths().add(path);
		} else {
			ret = getSuppressedExternalElementPaths().remove(path);
		}
		if (ret) {
			modified = true;
		}
		return ret;
	}

	/**
	 * Excludes the descriptors with the same linked element from the check
	 * 
	 * @param selectionToReveal
	 * @return
	 */
	public String checkDuplicateNameAfterReveal(
			Collection selectionToRevealOrSuppress,
			AdapterFactory adapterFactory) {
		Collection elementsToReveal = getApplicableItems(
				selectionToRevealOrSuppress, false);
		for (Iterator iter = elementsToReveal.iterator(); iter.hasNext();) {
			Object element = iter.next();
			if (element instanceof BreakdownElement) {
				BreakdownElement be = (BreakdownElement) element;
				String msg = ProcessUtil.checkBreakdownElementName(
						adapterFactory, be, be.getName(), this);
				if (msg != null) {
					return LibraryEditResources.Suppression_nameDuplication; //$NON-NLS-1$
				}
				msg = ProcessUtil.checkBreakdownElementPresentationName(
						adapterFactory, be, be.getPresentationName(), this);
				if (msg != null) {
					return LibraryEditResources.Suppression_presentationNameDuplication; //$NON-NLS-1$
				}
			} else {
				Object unwrapped = TngUtil.unwrap(element);
				if (unwrapped instanceof BreakdownElement) {
					ITreeItemContentProvider itemProvider = (ITreeItemContentProvider) adapterFactory
							.adapt(element, ITreeItemContentProvider.class);
					Object parent = itemProvider.getParent(element);
					if (parent instanceof BreakdownElement) {
						itemProvider = (ITreeItemContentProvider) adapterFactory
								.adapt(parent, ITreeItemContentProvider.class);
						Collection siblings = itemProvider.getChildren(parent);
						Object linkedElement = null;
						if (element instanceof Descriptor) {
							linkedElement = ProcessUtil
									.getAssociatedElement((Descriptor) element);
						}
						try {
							internalUnsuppressedElements.add(element);
							nameCheckLoop: for (Iterator iterator = siblings
									.iterator(); iterator.hasNext();) {
								Object sibling = iterator.next();
								if (sibling instanceof BreakdownElement) {
									BreakdownElement be = (BreakdownElement) sibling;
									// skip checking on suppressed element
									//
									if (be.getSuppressed().booleanValue()) {
										continue nameCheckLoop;
									}
									if (linkedElement != null
											&& sibling instanceof Descriptor) {
										// skip checking on descriptors with the
										// same linked element
										//
										Object otherLinkedElement = ProcessUtil
												.getAssociatedElement((Descriptor) element);
										if (otherLinkedElement == linkedElement) {
											continue nameCheckLoop;
										}
									}
									String msg = ProcessUtil
											.checkBreakdownElementName(
													adapterFactory, be, be
															.getName(), this);
									if (msg != null) {
										return LibraryEditResources.Suppression_nameDuplication; //$NON-NLS-1$
									}
									msg = ProcessUtil
											.checkBreakdownElementPresentationName(
													adapterFactory, be,
													be.getPresentationName(),
													this);
									if (msg != null) {
										return LibraryEditResources.Suppression_presentationNameDuplication; //$NON-NLS-1$
									}
								}
							}
						} finally {
							internalUnsuppressedElements.remove(element);
						}
					}
				}
			}
		}
		return null;
	}

	public Object getObjectByPath(String[] guidPath,
			AdapterFactory adapterFactory) {
		if (guidPath.length == 0 || adapterFactory == null
				|| !guidPath[0].equals(process.getGuid())) {
			return null;
		}

		Object object = process;
		int len = guidPath.length;
		for (int i = 1; i < len; i++) {
			ITreeItemContentProvider adapter = (ITreeItemContentProvider) adapterFactory
					.adapt(object, ITreeItemContentProvider.class);
			boolean found = false;
			
			// if current object is an activity, make sure it is rolled down before looking into its children
			//
			IBSItemProvider rolledUpAdapter = null;
			Iterator iter = null;
			try {
				if(TngUtil.unwrap(object) instanceof Activity) {
					if(adapter instanceof BSActivityItemProvider) {
						BSActivityItemProvider activityItemProvider = (BSActivityItemProvider) adapter;
						if(activityItemProvider.isRolledUp()) {
							activityItemProvider.basicSetRolledUp(false);
							rolledUpAdapter = activityItemProvider;
						}
					}
					else if(adapter instanceof IBSItemProvider) {
						IBSItemProvider itemProvider = (IBSItemProvider) adapter;
						if(itemProvider.isRolledUp()) {
							itemProvider.setRolledUp(false);
							rolledUpAdapter = itemProvider;
						}
					}
				}
				iter = adapter.getChildren(object).iterator();
			}
			finally {
				if(rolledUpAdapter != null) {
					rolledUpAdapter.setRolledUp(true);
				}
			}
			
			find_child: while (iter.hasNext()) {
				Object child = iter.next();
				Object e = TngUtil.unwrap(child);
				if (e instanceof MethodElement
						&& ((MethodElement) e).getGuid().equals(guidPath[i])) {
					found = true;
					object = child;
					break find_child;
				}
			}
			
			if (!found) {
				return null;
			}
		}
		return object;
	}

	/**
	 * Checks if the breakdown element identified by the given
	 * <code>guidPath</code> is suppressed in the process of this Suppression
	 * object
	 * 
	 * @param guidPath
	 * @return
	 * @exception IllegalArgumentException
	 *                if the object with the given path could not be found using
	 *                the given adapter factory
	 */
	public boolean isSuppressed(String[] guidPath, AdapterFactory adapterFactory)
			throws IllegalArgumentException {
		Object object = getObjectByPath(guidPath, adapterFactory);
		if (object == null) {
			throw new IllegalArgumentException(
					"Could not find object with path '" + guidPath + "'"); //$NON-NLS-1$ //$NON-NLS-2$
		}
		return isSuppressed(object);
	}
	
	/**
	 * Gets the set of suppressed item of the given processes.
	 * 
	 * @param proc
	 * @param adapterFactories the adapter factories for WBS, TBS, WPBS, and CBS views
	 * @return
	 */
	public static Set getSuppressedItems(Process proc,  AdapterFactory[] adapterFactories) {
		Set suppressedItems = new HashSet();
		Suppression suppression = new Suppression(proc);
		for (int i = 0; i < adapterFactories.length; i++) {
			for (Iterator iter = new AdapterFactoryTreeIterator(adapterFactories[i], proc); iter.hasNext();) {
				Object item = iter.next();
				if(suppression.isSuppressed(item)) {
					suppressedItems.add(item);
				}
			}
		}
		return suppressedItems;
	}
	
	public static class SuppressionCommand extends AbstractCommand implements IResourceAwareCommand
	{
		private Collection modifiedResources;
		private List collection;
		private Result result;
		private boolean suppressed;
		private Suppression suppression;
		

		public SuppressionCommand(Suppression suppression, List selection, boolean suppressed) {
			this.suppression = suppression;
			collection = selection;
			this.suppressed = suppressed;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.epf.library.edit.command.IResourceAwareCommand#getModifiedResources()
		 */
		public Collection getModifiedResources() {
			if(modifiedResources == null) {
				modifiedResources = Collections.singletonList(suppression.getProcess().eResource());
			}
			return modifiedResources;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.emf.common.command.Command#execute()
		 */
		public void execute() {
			result = suppression.setSuppressed(collection, suppressed);
			didExecute();
		}
		
		/**
		 * 
		 */
		protected void didExecute() {
			
		}

		/* (non-Javadoc)
		 * @see org.eclipse.emf.common.command.Command#redo()
		 */
		public void redo() {
			execute();
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.emf.common.command.AbstractCommand#undo()
		 */
		public void undo() {
			if(result != null && !result.isEmpty()) {
				if(!result.elements.isEmpty()) {
					for (Iterator iter = result.elements.iterator(); iter.hasNext();) {
						MethodElement e = (MethodElement) iter.next();
						e.setSuppressed(Boolean.valueOf(!suppressed));
					}
				}
				if(!result.paths.isEmpty()) {
					for (Iterator iter = result.paths.iterator(); iter.hasNext();) {
						Object path = iter.next();
						if(suppressed) {
							suppression.getSuppressedExternalElementPaths().remove(path);
						}
						else {
							suppression.getSuppressedExternalElementPaths().add(path);
						}
					}
				}
				didUndo();
				result.clear();
			}
		}
				
		/**
		 * 
		 */
		protected void didUndo() {
			
		}

		/* (non-Javadoc)
		 * @see org.eclipse.emf.common.command.AbstractCommand#prepare()
		 */
		protected boolean prepare() {
			return true;
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.emf.common.command.AbstractCommand#getResult()
		 */
		public Collection getResult() {
			if(!result.isEmpty()) {
				return collection;
			}
			return Collections.EMPTY_LIST;
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.emf.common.command.AbstractCommand#getAffectedObjects()
		 */
		public Collection getAffectedObjects() {
			return collection;
		}		
		
		public boolean isReadOnlyElementAffected() {
			return !result.paths.isEmpty();
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.emf.common.command.AbstractCommand#dispose()
		 */
		public void dispose() {
			if(result != null) {
				result.clear();
			}
			
			super.dispose();
		}
	}
	
}
