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

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

import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.epf.library.edit.ICommandListener;
import org.eclipse.epf.library.edit.process.command.VaryActivityCommand;
import org.eclipse.epf.library.edit.util.ProcessUtil;
import org.eclipse.epf.uma.Activity;
import org.eclipse.epf.uma.Descriptor;
import org.eclipse.epf.uma.Diagram;
import org.eclipse.epf.uma.GraphConnector;
import org.eclipse.epf.uma.GraphEdge;
import org.eclipse.epf.uma.GraphNode;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.ProcessPackage;
import org.eclipse.epf.uma.SemanticModelBridge;
import org.eclipse.epf.uma.UMASemanticModelBridge;
import org.eclipse.epf.uma.UmaFactory;
import org.eclipse.epf.uma.VariabilityElement;
import org.eclipse.epf.uma.VariabilityType;
import org.eclipse.epf.uma.WorkOrder;
import org.eclipse.epf.uma.WorkProductDescriptor;
import org.eclipse.epf.uma.util.UmaUtil;


/**
 * Diagram data stored in UMA: SemanticModelBridge .Presentation diagram type,
 * its value can be one of following constants defined in GraphicalDataHelper
 * DIAGRAM_WORKFLOW, DIAGRAM_WORK_PRODUCT_DEPENDENCY, DIAGRAM_ACTIVITY_DETAIL
 * 
 * UMASemanticModelBridge .Element BreakdownElement that the DiagramElement of
 * this SemanticModelBridge represents
 * 
 * SimpleSemanticModelElement .TypeInfo type information for TypedNode, its
 * value can be one of the constants defined in GraphicalDataHelper
 * wpCompositeType WorkProductComposite type
 * 
 * DiagramElement .Property (list of properties)
 * 
 * 
 * @author Phong Nguyen Le
 * @since 1.0
 */
public final class GraphicalDataManager {
	private static final String[] diagramPresentations = new String[] {
			GraphicalDataHelper.DIAGRAM_WORKFLOW,
			GraphicalDataHelper.DIAGRAM_WORK_PRODUCT_DEPENDENCY,
			GraphicalDataHelper.DIAGRAM_ACTIVITY_DETAIL };

	private static final Class[] childTypes = new Class[] { Activity.class,
			WorkProductDescriptor.class, Descriptor.class };

	private static final Class[] diagramChangeListenerClasses = new Class[] {
			IActivityDiagramChangeListener.class,
			IWPDDiagramChangeListener.class,
			IActivityDetailDiagramChangeListener.class };

	private static GraphicalDataManager instance = null;

	//private Map graphicalDataMap = new HashMap();

	private List commandListeners;

	private GraphicalDataManager() {
	}

	public static GraphicalDataManager getInstance() {
		if (instance == null) {
			synchronized (GraphicalDataManager.class) {
				if (instance == null) {
					instance = new GraphicalDataManager();
				}
			}
		}
		return instance;
	}

	// public GraphNode getGraphNode(Activity act, int diagramType, Activity
	// parent) {
	// // ItemProviderAdapter adapter = (ItemProviderAdapter)
	// TngAdapterFactory.INSTANCE.getWBS_ComposedAdapterFactory().adapt(act,
	// ITreeItemContentProvider.class);
	// // Object parent = adapter.getParent(act);
	// // if(parent instanceof Activity) {
	// if(parent == null) {
	// // new Activity
	// //
	// return createGraphNode(act);
	// }
	// if(!act.getSuperActivities().contains(parent)) {
	// throw new IllegalArgumentException("Invalid super activity for " + act +
	// ": " + parent);
	// }
	// Activity parentAct = (Activity) parent;
	// Diagram diagram = getDiagram(parentAct, diagramType).getUMADiagram();
	// return getGraphNode(diagram, act);
	// }

	/**
	 * Gets the GraphNode of the given method element for the given diagram.
	 * Create the GraphNode if it does not exist yet.
	 * 
	 * @param diagram
	 * @param methodElement
	 * @return
	 */
	public GraphNode getGraphNode(Diagram diagram, MethodElement methodElement) {
		if (diagram == null) {
			// new MethodElement
			//
			return createGraphNode(methodElement);
		}
		GraphNode node = findGraphNode(diagram, methodElement);
		if (node == null) {
			node = createGraphNode(methodElement);
			createGraphConnectors(node, diagram);
			diagram.getContained().add(node);
		}
		return node;
	}

	public static GraphNode findGraphNode(Diagram diagram, Object methodElement) {
		for (Iterator iter = diagram.getContained().iterator(); iter.hasNext();) {
			GraphNode element = (GraphNode) iter.next();
			if (element.getSemanticModel() instanceof UMASemanticModelBridge
					&& methodElement == ((UMASemanticModelBridge) element
							.getSemanticModel()).getElement()) {
				return element;
			}
		}
		return null;
	}

	// public DiagramElement getDiagramElement(Object object) {
	// if(object instanceof Activity) {
	// return getGraphNode((Activity) object);
	// }
	// else if(object instanceof WorkOrder) {
	// WorkOrder workOrder = (WorkOrder) object;
	// BreakdownElement be = workOrder.getPred();
	// if(be instanceof Activity) {
	// GraphNode graphNode = getGraphNode((Activity) be);
	// List list = graphNode.getContained();
	// Polyline polyline = null;
	// for(int i = 0; i < list.size(); i++) {
	// Object obj = list.get(i);
	// if(obj instanceof Polyline) {
	// polyline = (Polyline) obj;
	// break;
	// }
	// }
	// if(polyline == null) {
	// polyline = UmaFactory.eINSTANCE.createPolyline();
	// list.add(0, polyline);
	// }
	// return polyline;
	// }
	// }
	// return null;
	// }

	/**
	 * @param object
	 * @return
	 */
	private Diagram createDiagram(Activity act, int diagramType) {
		// check if this activity contributes/extends other activity and try
		// copy
		// the existing diagram from the base
		//
		if (ProcessUtil.isExtendingOrLocallyContributing(act)) {
			Diagram baseDiagram = getUMADiagram((Activity) act
					.getVariabilityBasedOnElement(), diagramType, false);
			if (baseDiagram != null) {
				Diagram copy = GraphicalDataHelper.copyDiagram(baseDiagram);

				UMASemanticModelBridge modelBridge = (UMASemanticModelBridge) copy
						.getSemanticModel();
				modelBridge.setElement(act);
				return copy;
			}
		}

		Diagram diagram = UmaFactory.eINSTANCE.createDiagram();
		UMASemanticModelBridge modelBridge = UmaFactory.eINSTANCE
				.createUMASemanticModelBridge();
		modelBridge.setElement(act);
		modelBridge.setPresentation(diagramPresentations[diagramType]);
		diagram.setSemanticModel(modelBridge);

		// getDiagram(act, diagramType).setUMADiagram(diagram);

		populateDiagram(diagram, act, diagramType);

		return diagram;
	}

	private void populateDiagram(Diagram diagram, Activity act, int diagramType) {
		// create GraphNode for each BreakdownElement and add it to the diagram
		//
		List nodes = new ArrayList();
		for (Iterator iter = act.getBreakdownElements().iterator(); iter
				.hasNext();) {
			Object element = iter.next();
			if (childTypes[diagramType].isInstance(element)) {
				GraphNode node = createGraphNode((MethodElement) element);
				diagram.getContained().add(node);
				nodes.add(node);
			}
		}

		// create GraphConnectors for each BreakdownElement
		//
		for (Iterator iter = nodes.iterator(); iter.hasNext();) {
			GraphNode node = (GraphNode) iter.next();
			createGraphConnectors(node, diagram, diagramType);
		}
	}

//	private void populateActivityDiagram(Diagram diagram, Activity act) {
//		// create GraphNode for each child activity and add it to the diagram
//		// 
//		List nodes = new ArrayList();
//		for (Iterator iter = act.getBreakdownElements().iterator(); iter
//				.hasNext();) {
//			Object element = (Object) iter.next();
//			if (element instanceof Activity) {
//				Activity childAct = (Activity) element;
//				GraphNode node = createGraphNode(childAct);
//				diagram.getContained().add(node);
//				nodes.add(node);
//			}
//		}
//
//		// create GraphConnectors for each child activity
//		//
//		for (Iterator iter = nodes.iterator(); iter.hasNext();) {
//			GraphNode node = (GraphNode) iter.next();
//			createGraphConnectors(node, diagram);
//		}
//	}

	/**
	 * @param act
	 * @param diagramType
	 * @return
	 */
	public IDiagramChangeListener getDiagramChangeListener(Activity act,
			int diagramType) {
		for (Iterator iter = act.eAdapters().iterator(); iter.hasNext();) {
			Object element = (Object) iter.next();
			if (diagramChangeListenerClasses[diagramType].isInstance(element)) {
				return (IDiagramChangeListener) element;
			}
		}

		return null;
	}

	private GraphNode createGraphNode(MethodElement element) {
		GraphNode graphNode = UmaFactory.eINSTANCE.createGraphNode();
		UMASemanticModelBridge modelBridge = UmaFactory.eINSTANCE
				.createUMASemanticModelBridge();
		modelBridge.setElement(element);
		graphNode.setSemanticModel(modelBridge);
		org.eclipse.epf.uma.Point point = UmaFactory.eINSTANCE.createPoint();
		point.setX(new Double(-1));
		point.setY(new Double(-1));
		graphNode.setPosition(point);
		org.eclipse.epf.uma.Dimension dim = UmaFactory.eINSTANCE.createDimension();
		dim.setWidth(new Double(-1));
		dim.setHeight(new Double(-1));
		graphNode.setSize(dim);

		return graphNode;
	}

	public GraphEdge addGraphConnectorPair(Diagram diagram,
			GraphNode graphNode, WorkOrder element) {
		GraphConnector srcConnector = UmaFactory.eINSTANCE
				.createGraphConnector();
		graphNode.getAnchorage().add(srcConnector);
		GraphEdge edge = UmaFactory.eINSTANCE.createGraphEdge();
		UMASemanticModelBridge bridge = UmaFactory.eINSTANCE
				.createUMASemanticModelBridge();
		bridge.setElement(element);
		edge.setSemanticModel(bridge);
		graphNode.getContained().add(edge);
		edge.getAnchor().add(srcConnector);

		GraphNode succNode = getGraphNode(diagram, UmaUtil
				.getOwningActivity(element));
		GraphConnector targetConnector = UmaFactory.eINSTANCE
				.createGraphConnector();
		succNode.getAnchorage().add(targetConnector);
		edge.getAnchor().add(targetConnector);
		return edge;
	}

	public GraphEdge addGraphConnectorPair(GraphNode srcNode,
			GraphNode targetNode) {
		GraphConnector srcConnector = UmaFactory.eINSTANCE
				.createGraphConnector();
		srcNode.getAnchorage().add(srcConnector);
		GraphEdge edge = UmaFactory.eINSTANCE.createGraphEdge();
		srcNode.getContained().add(edge);
		edge.getAnchor().add(srcConnector);

		GraphConnector targetConnector = UmaFactory.eINSTANCE
				.createGraphConnector();
		targetNode.getAnchorage().add(targetConnector);
		edge.getAnchor().add(targetConnector);
		return edge;
	}

	public void removeGraphEdge(GraphEdge edge) {
		EcoreUtil.remove(edge);
		for (Iterator iter = edge.getAnchor().iterator(); iter.hasNext();) {
			GraphConnector conn = (GraphConnector) iter.next();
			EcoreUtil.remove(conn);
		}
	}

	// public void removeGraphConnectorPair(GraphNode srcNode, GraphNode
	// targetNode) {
	// // find the source GraphConnector and remove it, its GraphEdge, and its
	// target GraphConnector
	// //
	// for (Iterator iter = srcNode.getAnchorage().iterator(); iter.hasNext();)
	// {
	// GraphConnector connector = (GraphConnector) iter.next();
	// for (Iterator iterator = connector.getGraphEdge().iterator(); iterator
	// .hasNext();) {
	// GraphEdge edge = (GraphEdge) iterator.next();
	// Object targetConnector = edge.getAnchor().get(1);
	// if(targetNode.getAnchorage().remove(targetConnector)) {
	// srcNode.getContained().remove(edge);
	// // don't remove the edge from connector to support undo
	// //
	// // iterator.remove();
	// }
	// }
	// if(connector.getGraphEdge().isEmpty()) {
	// iter.remove();
	// }
	// }
	// }

	public void removeGraphConnectionPair(GraphNode graphNode,
			WorkOrder workOrder) {
		for (Iterator iter = graphNode.getAnchorage().iterator(); iter
				.hasNext();) {
			GraphConnector conn = (GraphConnector) iter.next();
			GraphEdge edge = (GraphEdge) conn.getGraphEdge().get(0);
			if (edge.eContainer() == graphNode) {
				GraphConnector conn2 = (GraphConnector) edge.getAnchor().get(1);
				SemanticModelBridge bridge = conn2.getGraphElement()
						.getSemanticModel();
				if (bridge instanceof UMASemanticModelBridge
						&& ((UMASemanticModelBridge) bridge).getElement() == UmaUtil
								.getOwningActivity(workOrder)) {
					conn2.getGraphElement().getAnchorage().remove(conn2);
					iter.remove();
					graphNode.getContained().remove(edge);
					return;
				}
			}
		}
	}

	private void createGraphConnectors(GraphNode graphNode, Diagram diagram) {
		createGraphConnectors(graphNode, diagram, getDiagramType(diagram));
	}

	int getDiagramType(Diagram diagram) {
		String typeStr = diagram.getSemanticModel().getPresentation();
		for (int i = 0; i < diagramPresentations.length; i++) {
			if (diagramPresentations[i].equals(typeStr))
				return i;
		}
		return -1;
	}

	private void createGraphConnectors(GraphNode graphNode, Diagram diagram,
			int diagramType) {
		MethodElement e = ((UMASemanticModelBridge) graphNode
				.getSemanticModel()).getElement();
		switch (diagramType) {
		case GraphicalDataHelper.ACTIVITY_DIAGRAM:
			if (e instanceof Activity) {
				Activity act = (Activity) e;
				// create connectors
				//
				// List list = act.getLinkToPredecessor();
				// int size = list.size();
				//				
				// Activity parent = (Activity)
				// ((UMASemanticModelBridge)diagram.getSemanticModel()).getElement();
				// List breakdownElements = parent.getBreakdownElements();
				// for(int i = 0; i < size; i++) {
				// WorkOrder workOrder = (WorkOrder) list.get(i);
				// if(breakdownElements.contains(workOrder.getPred())) {
				// addGraphConnectorPair(diagram, graphNode, workOrder);
				// }
				// }

				for (Iterator iter = act.getLinkToPredecessor().iterator(); iter
						.hasNext();) {
					WorkOrder workOrder = (WorkOrder) iter.next();
					GraphNode srcNode = findGraphNode(diagram, workOrder
							.getPred());
					if (srcNode != null) {
						addGraphConnectorPair(srcNode, graphNode);
					}
				}
			}

			break;
		case GraphicalDataHelper.WORK_PRODUCT_DEPENDENCY_DIAGRAM:
			if (e instanceof WorkProductDescriptor) {
				WorkProductDescriptor descriptor = (WorkProductDescriptor) e;
				for (Iterator iter = descriptor.getImpacts().iterator(); iter
						.hasNext();) {
					WorkProductDescriptor impactedDescriptor = (WorkProductDescriptor) iter
							.next();
					GraphNode targetNode = GraphicalDataManager.findGraphNode(
							diagram, impactedDescriptor);
					if (targetNode != null) {
						addGraphConnectorPair(graphNode, targetNode);
					}
				}
			}

			break;
		}

	}

	/**
	 * Gets the UMA diagram from the UMA model for the given activity.
	 * 
	 * @param e
	 * @param diagramType
	 *            one of the diagram type constants defined in
	 *            {@link GraphicalDataHelper GraphicalDataHelper }
	 * @param create
	 *            if true, new UMA diagram will be created if the activity does
	 *            not have one already.
	 * @return
	 * @see GraphicalDataHelper#ACTIVITY_DIAGRAM
	 * @see GraphicalDataHelper#ACTIVITY_DETAIL_DIAGRAM
	 * @see GraphicalDataHelper#WORK_PRODUCT_DEPENDENCY_DIAGRAM
	 */
	public Diagram getUMADiagram(Activity e, int diagramType, boolean create) {
		Diagram diagram = null;

		// // look for IDiagramChangeListener in activity's adapter list first
		// IDiagramChangeListener listener = (IDiagramChangeListener)
		// UmaUtil.getAdapter(e, diagramListenerType);
		// if(listener != null) {
		// diagram = listener.getDiagram().getUMADiagram();
		// if(diagram != null) return diagram;
		// }

		Object container = e.eContainer();
		if (container != null) {
			ProcessPackage pkg = (ProcessPackage) container;
			for (Iterator iter = pkg.getDiagrams().iterator(); iter.hasNext();) {
				diagram = (Diagram) iter.next();
				SemanticModelBridge modelBridge = diagram.getSemanticModel();
				if (modelBridge != null
						&& modelBridge instanceof UMASemanticModelBridge
						&& e == ((UMASemanticModelBridge) modelBridge)
								.getElement()
						&& diagramPresentations[diagramType]
								.equals(((UMASemanticModelBridge) modelBridge)
										.getPresentation())) {
					return diagram;
				}
			}
		}

		if (create) {
			// could not find it, create new one
			//
			diagram = createDiagram(e, diagramType);
			if (container != null) {
				((ProcessPackage) container).getDiagrams().add(diagram);
			}
			return diagram;
		}
		return null;
	}

	// public Diagram getUMADiagram(Activity act, int diagramType) {
	// Diagram diagram = null;
	//        
	// // look for IDiagramChangeListener in activity's adapter list first
	// IDiagramChangeListener listener = getDiagramChangeListener(act,
	// diagramType);
	// if(listener != null) {
	// diagram = listener.getDiagram().getUMADiagram();
	// if(diagram != null) return diagram;
	// }
	// Object container = act.eContainer();
	// if(container != null) {
	// ProcessPackage pkg = (ProcessPackage) container;
	// for (Iterator iter = pkg.getDiagrams().iterator(); iter.hasNext();) {
	// diagram = (Diagram) iter.next();
	// SemanticModelBridge modelBridge = diagram.getSemanticModel();
	// if(modelBridge != null && modelBridge instanceof UMASemanticModelBridge
	// && act == ((UMASemanticModelBridge)modelBridge).getElement()
	// &&
	// diagramPresentations[diagramType].equals(((UMASemanticModelBridge)modelBridge).getPresentation()))
	// {
	// return diagram;
	// }
	// }
	// }
	//        
	// // could not find it, create new one
	// //
	// return createDiagram(act, diagramType);
	// }

	/**
	 * @param act
	 * @return
	 */
	public org.eclipse.epf.diagram.model.Diagram getDiagram(Activity act,
			int diagramType) {
		IDiagramChangeListener listener = getDiagramChangeListener(act,
				diagramType);
		return listener == null ? null : listener.getDiagram();
	}

	/**
	 * Gets list of command listeners that will handle the diagram related changes of the commands of interesses
	 * 
	 * @return
	 * @see ICommandListener
	 */
	public List getCommandListeners() {
		if (commandListeners == null) {
			commandListeners = new ArrayList();
		}

		commandListeners.add(new ICommandListener() {
			
			public void notifyExecuted(Command command) {
				// get contributor/replacer of the activity
				//
				Activity act = (Activity) command.getResult().iterator().next();
				Activity base = (Activity) act.getVariabilityBasedOnElement();
				if (base != null) {
					Activity parentAct = act.getSuperActivities();
					Diagram diagram = getUMADiagram(parentAct,
							GraphicalDataHelper.ACTIVITY_DIAGRAM, false);
					if (diagram != null) {
						// find existing node for base and link it to the
						// contributore/replacer of the activity
						//
						GraphNode graphNode = findGraphNode(diagram, base);
						if (graphNode != null) {
							UMASemanticModelBridge bridge = (UMASemanticModelBridge) graphNode
									.getSemanticModel();
							if (bridge.getElement() != act) {
								bridge.setElement(act);
							}
						}
					}
					
					if(act.getVariabilityType() == VariabilityType.LOCAL_CONTRIBUTION_LITERAL) {
						// copy diagrams and their publishing options
						//
						for (int i = 0; i < GraphicalDataHelper.DIAGRAM_TYPES.length; i++) {
							int diagramType = GraphicalDataHelper.DIAGRAM_TYPES[i];
							diagram = getUMADiagram(base, diagramType, false);
							if (diagram != null) {
								Diagram copy = getUMADiagram(act, diagramType, true);
								copy.setSuppressed(diagram.getSuppressed());
							}
						}
					}
				}
			}

			public Class getCommandType() {
				return VaryActivityCommand.class;
			}

			public void preUndo(Command command) {
				// get old contributor/replacer of the activity
				//
				Activity act = (Activity) command.getResult().iterator().next();
//				VaryActivityCommand cmd = (VaryActivityCommand) command;
//				BreakdownElementWrapperItemProvider adapter = cmd.getWrapper();
				VariabilityElement baseAct = act.getVariabilityBasedOnElement();
				if (baseAct != null) {
					Activity parentAct = act.getSuperActivities();
					Diagram diagram = getUMADiagram(parentAct,
							GraphicalDataHelper.ACTIVITY_DIAGRAM, false);
					if (diagram != null) {
						// find existing node for the old contributore/replacer
						// old and relink it to the base activity
						//
						GraphNode graphNode = findGraphNode(diagram, act);
						if (graphNode != null) {
							UMASemanticModelBridge bridge = (UMASemanticModelBridge) graphNode
									.getSemanticModel();
							if (bridge.getElement() != baseAct) {
								bridge.setElement(baseAct);
							}
						}
					}
				}
			}

			public void preExecute(Command command) {
				
			}

		});

		return commandListeners;
	}
}
