/*******************************************************************************
 * Copyright (c) 2005 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 API and implementation
 *******************************************************************************/
package org.eclipse.bpel.ui.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.bpel.model.Activity;
import org.eclipse.bpel.model.BPELFactory;
import org.eclipse.bpel.model.Flow;
import org.eclipse.bpel.model.Link;
import org.eclipse.bpel.model.Source;
import org.eclipse.bpel.model.Target;
import org.eclipse.emf.ecore.EObject;



/**
 * Helpers for dealing with Links in a Flow.
 * 
 * TODO: do all these helpers work OK if the Sources or Targets list is empty??
 */
public class FlowLinkUtil {
	
	/**
	 * Walks up the parent chain of the source and target activities to find the innermost flow
	 * which contains both.  Returns null if source and target have no common flow.
	 */
	public static Flow getCommonFlow(Activity source, Activity target) {
		if (source == null || target == null) return null;
		Flow[] sourceParents = getParentFlows(source);
		Flow[] targetParents = getParentFlows(target);
		if (sourceParents == null || targetParents == null) return null;
		int sIdx = sourceParents.length - 1;
		int tIdx = targetParents.length - 1;
		Flow commonFlow = null;
		while (sIdx >= 0 && tIdx >= 0) {
			if (sourceParents[sIdx] == targetParents[tIdx]) {
				commonFlow = sourceParents[sIdx];
			} else {
				break;
			}
			sIdx--; tIdx--;
		}
		return commonFlow;
	}

	/**
	 * Returns true if the given object is enclosed in a flow, or false otherwise.
	 */
	public static boolean hasParentFlow(EObject object) {
		for (object = object.eContainer(); object != null; object = object.eContainer()) {
			if (object instanceof Flow)  return true;
		}
		return false;
	}

	/**
	 * Walks up the parent chain of the given object and returns all the parents which are
	 * Flows, from the innermost (i.e. closest ancestor) to outermost (oldest ancestor).
	 * 
	 * The returned array will have non-zero length if and only if hasParentFlow(object) is true.
	 */	
	public static Flow[] getParentFlows(EObject object) {
		List list = new ArrayList();
		getParentFlows(object.eContainer(), list);
		return (Flow[])list.toArray(new Flow[list.size()]);
	}

	protected static void getParentFlows(EObject object, List list) {
		if (object == null) return;
		if (object instanceof Process) return;
		if (object instanceof Flow) list.add(object);
		getParentFlows(object.eContainer(), list);
	}
	
	/**
	 * Returns a list of links.
	 * Callers should NOT MODIFY this list as it may not be the real list!
	 */
	public static List getFlowLinks(Flow flow) {
		if (flow.getLinks() == null) {
			return Collections.EMPTY_LIST;
		}
		return Collections.unmodifiableList(flow.getLinks().getChildren());
	}
	
	public static void addFlowLink(Flow flow, Link link) {
		if (flow.getLinks() == null) {
			flow.setLinks(BPELFactory.eINSTANCE.createLinks());
		}
		flow.getLinks().getChildren().add(link);
	}
	
	public static void removeFlowLink(Flow flow, Link link) {
		if (flow.getLinks() != null) {
			flow.getLinks().getChildren().remove(link);
			if (flow.getLinks().getChildren().isEmpty()) {
				flow.setLinks(null);
			}
		}
	}
	
	public static void removeActivitySource(Source source) {
		Activity activity = source.getActivity();
		if (activity != null && activity.getSources() != null) {
			activity.getSources().getChildren().remove(source);
			if (activity.getSources().getChildren().isEmpty()) {
				activity.setSources(null);
			}
		}
	}
	
	public static void removeActivityTarget(Target target) {
		Activity activity = target.getActivity();
		if (activity != null && activity.getTargets() != null) {
			activity.getTargets().getChildren().remove(target);
			if (activity.getTargets().getChildren().isEmpty()) {
				activity.setTargets(null);
			}
		}
	}

	/**
	 * Returns the source of a Link, or null if it has none.
	 */
	public static Activity getLinkSource(Link link) {
		if (link.getSources().isEmpty()) return null;
		return ((link.getSources().get(0))).getActivity(); 
	}
	
	/**
	 * Returns the target of a Link, or null if it has none.
	 */
	public static Activity getLinkTarget(Link link) {
		if (link.getTargets().isEmpty()) return null;
		return ((link.getTargets().get(0))).getActivity(); 
	}
	
	/**
	 * Sets the source of a Link.
	 */
	public static void setLinkSource(Link link, Activity activity) {
		if (link.getSources().isEmpty()) {
			if (activity != null) {
				// Create new Source.
				Source source = BPELFactory.eINSTANCE.createSource();
				link.getSources().add(source);
				source.setActivity(activity);
			}
		} else {
			Source source = (link.getSources().get(0));
			Activity oldActivity = source.getActivity();
			if (activity != null) {
				// Re-target existing Source.  TODO: is this safe?
				source.setActivity(activity);
			} else {
				link.getSources().remove(source);
			}
			// In either case, the previous activity to which the
			// source was attached may now have an empty sources element.
			// If so, remove it now.
			if (oldActivity != null) {
				if (oldActivity.getSources() != null && oldActivity.getSources().getChildren().isEmpty()) {
					oldActivity.setSources(null);
				}
			}
		}
	}
	
	/**
	 * Sets the target of a Link.
	 */
	public static void setLinkTarget(Link link, Activity activity) {
		if (link.getTargets().isEmpty()) {
			if (activity != null) {
				// Create new Target.
				Target target = BPELFactory.eINSTANCE.createTarget();
				link.getTargets().add(target);
				target.setActivity(activity);
			}
		} else {
			Target target = (link.getTargets().get(0));
			Activity oldActivity = target.getActivity();
			if (activity != null) {
				// Re-target existing Target.  TODO: is this safe?
				target.setActivity(activity);
			} else {
				// Remove existing Target.
				link.getTargets().remove(target);
			}
			// In either case, the previous activity to which the
			// target was attached may now have an empty targets element.
			// If so, remove it now.
			if (oldActivity != null) {
				if (oldActivity.getTargets() != null && oldActivity.getTargets().getChildren().isEmpty()) {
					oldActivity.setTargets(null);
				}
			}
		}
	}
	
	/**
	 * If the from Activity is a source or target in a Link it is
	 * replaced by the to Activity. Nothing happens otherwise.
	 * 
	 * TODO: this was only used by some generic fault-handler stuff in ActivityAdapter.
	 * Consider removing this in the future, if we don't need that stuff anymore!
	 */
	public static void replaceLinkSourceAndTarget(Activity from, Activity to) {
		Flow[] flows = getParentFlows(from);
		if (flows != null && flows.length > 0) {
			Flow flow = flows[0];
			List links = getFlowLinks(flow);
			for (Iterator iter = links.iterator(); iter.hasNext();) {
				Link link = (Link) iter.next();
				Activity temp = getLinkSource(link);
				if (temp != null && temp == from) {
					setLinkSource(link, to); 
				} else {
					temp = getLinkTarget(link);
					if (temp != null && temp == from) {
						setLinkTarget(link, to); 
					}
				}
			}
		}
	}
	
//	/**
//	 * Returns the EditPart which should act as the source for a given Link object.
//	 * (this depends on the collapsed/expanded state..its basically the source Activity
//	 * itself, or its innermost enclosing container, which is actually visible)
//	 */
//	public static EditPart getActingSourceEditPart(EditPartViewer viewer, Link link) {
//		EObject modelObject = getLinkSource(link);
//		while (modelObject != null) {
//			EditPart editPart = (EditPart)viewer.getEditPartRegistry().get(modelObject);
//			if (editPart != null) return editPart;
//			modelObject = BPELUtil.getIContainerParent(modelObject);
//		}
//		return null;
//	}
//	
//	/**
//	 * Returns the EditPart which should act as the target for a given Link object.
//	 * (this depends on the collapsed/expanded state..its basically the target Activity
//	 * itself, or its innermost enclosing container, which is actually visible)
//	 */
//	public static EditPart getActingTargetEditPart(EditPartViewer viewer, Link link) {
//		EObject modelObject = getLinkTarget(link);
//		while (modelObject != null) {
//			EditPart editPart = (EditPart)viewer.getEditPartRegistry().get(modelObject);
//			if (editPart != null) return editPart;
//			modelObject = BPELUtil.getIContainerParent(modelObject);
//		}
//		return null;
//	}
//	
//	/**
//	 * Return all the links for which a collapsed edit part should act as the source,
//	 * even though it really isn't.  (Because the *actual* sources are contained in
//	 * the edit part).
//	 */
//	public static List getAllContainedSourceLinks(EObject model) {
//		List result = new ArrayList();
//		for (TreeIterator it = model.eAllContents(); it.hasNext(); ) {
//			Object object = it.next();
//			// Activity *contains* the Sources which *contains* the Source
//			if (object instanceof Source) {
//				Link link = ((Source)object).getLink();
//				if (link != null) result.add(link);
//			}
//		}
//		return result;
//	}
//	
//	/**
//	 * Return all the links for which a collapsed edit part should act as the source,
//	 * even though it really isn't.  (Because the *actual* sources are contained in
//	 * the edit part).
//	 */
//	public static List getAllContainedTargetLinks(EObject model) {
//		List result = new ArrayList();
//		for (TreeIterator it = model.eAllContents(); it.hasNext(); ) {
//			Object object = it.next();
//			// Activity *contains* the Sources which *contains* the Source
//			if (object instanceof Source) {
//				Link link = ((Source)object).getLink();
//				if (link != null) result.add(link);
//			}
//		}
//		return result;
//	}
	
}
