package org.tigris.subversion.subclipse.graph.editors;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.draw2d.ChopboxAnchor;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.Cursors;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.PolygonDecoration;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.Viewport;
import org.eclipse.draw2d.XYAnchor;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.graphics.Color;
import org.tigris.subversion.subclipse.graph.Activator;
import org.tigris.subversion.subclipse.graph.cache.Branch;
import org.tigris.subversion.subclipse.graph.cache.Cache;
import org.tigris.subversion.subclipse.graph.cache.Graph;
import org.tigris.subversion.subclipse.graph.cache.Node;

public class GraphEditPart extends AbstractGraphicalEditPart {
	private GraphicalViewer viewer;
	private Map branchMap = new HashMap();
	private Map nodeMap = new HashMap();
	private List connections = new ArrayList();
	private IPreferenceStore store = Activator.getDefault().getPreferenceStore();

	public GraphEditPart(GraphicalViewer viewer) {		
		super();
		this.viewer = viewer;
	}

	protected IFigure createFigure() {
		Figure f = new Figure();
		f.setBackgroundColor(ColorConstants.white);
		f.setOpaque(true);

		XYLayout layout = new XYLayout();
		f.setLayoutManager(layout);

		return f;
	}

	protected void createEditPolicies() {
		installEditPolicy(EditPolicy.SELECTION_FEEDBACK_ROLE, null);
	}

	protected List getModelChildren() {
		IPreferenceStore store = Activator.getDefault().getPreferenceStore();
		int showDeleted = store.getInt(RevisionGraphEditor.SHOW_DELETED_PREFERENCE);
		
		Graph graph = (Graph)getModel();
		List paths = graph.getPaths();
		List branches = new ArrayList();
		Iterator iter = paths.iterator();
		int i = 0;
		while (iter.hasNext()) {
			Branch branch = graph.getBranch((String)iter.next());
			
			if(branch.getNodes().size() == 1) {
				Node firstNode = (Node) branch.getNodes().iterator().next();
				if(firstNode.getSource() != null && firstNode.getChildCount() == 0) {
					// is not the root node and is not the target of any arrow
					// therefore is a tag
					branch.setView(null);
					continue;
				}
			}
			
			if (branch.getNodes().size() > 0) {
				Node lastNode = (Node)branch.getNodes().get(branch.getNodes().size() - 1);
				if (lastNode.getAction() == 'D' && Cache.isEqualsOrParent(lastNode.getPath(), branch.getPath())) {
					if (showDeleted == RevisionGraphEditor.SHOW_DELETED_NO || (showDeleted == RevisionGraphEditor.SHOW_DELETED_MODIFIED && !isModified(branch))) {
						// branch has been deleted and item was not modified in that location.
						// do not show this branch, unless it is the location where the item came
						// into existence.
						if (!graph.getRootPath().equals(branch.getPath())) {
							branch.setView(null);
							continue;
						}
					}
				}
			}			
			
			branch.setIndex(i++);
			branches.add(branch);

			int mod = branch.getIndex() % Activator.BG_COLORS.length;
			Color bgcolor = Activator.BG_COLORS[mod];
			Color fgcolor = Activator.FG_COLORS[mod];
			BranchFigure branchFigure = new BranchFigure(branch.getPath(), bgcolor, fgcolor);
			branchMap.put(branch.getPath(), branchFigure);
		}
		
		List nodeList = new ArrayList();
		iter = branches.iterator();
		while (iter.hasNext()) {
			Branch branch = (Branch)iter.next();
			Iterator nodeIter = branch.getNodes().iterator();
			while (nodeIter.hasNext()) {
				Node node = (Node)nodeIter.next();
				nodeList.add(node);
				int mod = branch.getIndex() % Activator.BG_COLORS.length;
				Color bgcolor = Activator.BG_COLORS[mod];
				Color fgcolor = Activator.FG_COLORS[mod];
				NodeFigure nodeFigure = new NodeFigure(node, bgcolor, fgcolor);
				nodeMap.put(node, nodeFigure);
			}
		}
		
		iter = branches.iterator();
		while (iter.hasNext()) {
			Branch branch = (Branch)iter.next();
			Iterator nodeIter = branch.getNodes().iterator();
			while (nodeIter.hasNext()) {
				Node node = (Node)nodeIter.next();
				NodeFigure nodeFigure = (NodeFigure)nodeMap.get(node);
				if(node.getParent() != null) {
					NodeFigure target = (NodeFigure)nodeMap.get(node.getParent());
					if(target != null) {
						makeConnection(getFigure(), target, nodeFigure);
					}
				} else if(node.getSource() != null) {
					NodeFigure target = (NodeFigure)nodeMap.get(node.getSource());
					if(target != null) {
						makeConnection(getFigure(), target, nodeFigure);
					}
				} else {
					BranchFigure branchFigure = (BranchFigure)branchMap.get(branch.getPath());
					makeConnection(getFigure(), branchFigure, nodeFigure);
				}				
			}
		}
		
		// Merged Connections
		iter = branches.iterator();
		while (iter.hasNext()) {
			Branch branch = (Branch)iter.next();
			for (Iterator it = branch.getNodes().iterator(); it.hasNext();) {
				Node node = (Node) it.next();
				List mergedRevisions = node.getMergedRevisions();
				if(mergedRevisions == null)
					continue;				
				NodeFigure nodeFigure = (NodeFigure) nodeMap.get(node);
				for (Iterator iterator = node.getMergedRevisions().iterator(); iterator
						.hasNext();) {
					Node merged = (Node) iterator.next();
					NodeFigure mergedView = (NodeFigure)nodeMap.get(merged);
					if(mergedView != null)
						makeConnection(getFigure(), mergedView, nodeFigure, ColorConstants.red);
				}				
			}
		}

// TODO: Preserve selections
//		Branch selectedBranch = graph.getBranch(graph.getSelectedPath());
//		if (selectedBranch != null) {
//			Node n = selectedBranch.getSource(graph.getSelectedRevision());
//			NodeFigure nodeFigure = (NodeFigure)nodeMap.get(n);
//			if(nodeFigure != null) {
//				selectNode(nodeFigure);
//				// FIXME: it doesn't work
////				scrollTo((Rectangle) contentsLayout.getConstraint(nodeFigure)); 
//			}
//		}
		
		setConnectionVisibility();
		
		Node[] nodeArray = new Node[nodeList.size()];
		nodeList.toArray(nodeArray);
		Arrays.sort(nodeArray);
		for (int j = 0; j < nodeArray.length; j++) {
			nodeArray[j].setGraphIndex(j);
		}
		
		return branches;
	}
	
	public BranchFigure getBranchFigure(String path) {
		return (BranchFigure)branchMap.get(path);
	}
	
	public NodeFigure getNodeFigure(Node node) {
		return (NodeFigure)nodeMap.get(node);
	}
	
	public List getNodes(Branch branch) {
		return (List)nodeMap.get(branch);
	}
	
	private boolean isModified(Branch branch) {
		List nodes = branch.getNodes();
		Iterator iter = nodes.iterator();
		while (iter.hasNext()) {
			Node node = (Node)iter.next();
			if (node.getPath().equals(branch.getPath()) && node.getAction() == 'M') return true;
		}
		return false;
	}

	protected void refreshVisuals() {
		super.refreshVisuals();
	}
	
	public void setConnectionVisibility() {
		Iterator iter = connections.iterator();
		while (iter.hasNext()) {
			PolylineConnection con = (PolylineConnection)iter.next();
			boolean show = false;
			if (!store.getBoolean(RevisionGraphEditor.FILTER_CONNECTIONS)) show = true;
			else {
				IFigure source =con.getSourceAnchor().getOwner();
				IFigure target = con.getTargetAnchor().getOwner();
				if (source instanceof NodeFigure && ((NodeFigure)source).isSelected()) show = true;
				else if (target instanceof NodeFigure && ((NodeFigure)target).isSelected()) show = true;
			}
			con.setVisible(show);
		}		
	}
	
	public boolean isChronologicalMode() {
		return store.getBoolean(RevisionGraphEditor.CHRONOLOGICAL);
	}

	private PolylineConnection makeConnection(IFigure contents, IFigure source, NodeFigure target) {
		return makeConnection(contents, source, target, Activator.CONNECTION_COLOR);
	}

	private PolylineConnection makeConnection(IFigure contents, IFigure source, NodeFigure target, Color color) {
		PolylineConnection c = new PolylineConnection();
		ConnectionAnchor targetAnchor = new ChopboxAnchor(target);
		c.setTargetAnchor(targetAnchor);
		c.setSourceAnchor(new ChopboxAnchor(source));
		PolygonDecoration decoration = new PolygonDecoration();
		decoration.setTemplate(PolygonDecoration.TRIANGLE_TIP);
		c.setTargetDecoration(decoration);
		c.setForegroundColor(color);
		ConnectionMouseListener listener = new ConnectionMouseListener(c);
		c.addMouseMotionListener(listener);
		c.addMouseListener(listener);
		c.setCursor(Cursors.HAND);
		contents.add(c);
		connections.add(c);
		return c;
	}
	
	private void scrollTo(Rectangle fbounds) {
		scrollTo(fbounds.x+fbounds.width/2, fbounds.y+fbounds.height/2);
	}
	
	private void scrollTo(int ax, int ay) {
		Viewport viewport = ((FigureCanvas)viewer.getControl()).getViewport();
		Rectangle vbounds = viewport.getBounds();
		Point p = new Point(ax, ay);
//		target.translateToAbsolute(p); // TODO
		int x = p.x-vbounds.width/2;
		int y = p.y-vbounds.height/2;
		viewport.setHorizontalLocation(x);
		viewport.setVerticalLocation(y);
	}

	private void scrollTo(IFigure target) {
		scrollTo(target.getBounds());
	}

	class ConnectionMouseListener implements MouseMotionListener, MouseListener {

		private PolylineConnection connection;

		public ConnectionMouseListener(PolylineConnection connection) {
			this.connection = connection;
		}

		public void mouseDragged(MouseEvent event) {
		}

		public void mouseEntered(MouseEvent event) {
		}

		public void mouseExited(MouseEvent event) {
			connection.setLineWidth(1);
		}

		public void mouseHover(MouseEvent event) {
			connection.setLineWidth(2);
		}

		public void mouseMoved(MouseEvent event) {
		}

		public void mouseDoubleClicked(MouseEvent event) {
		}

		public void mousePressed(MouseEvent event) {
			NodeFigure nodeFigure = (NodeFigure) connection.getTargetAnchor().getOwner();
			scrollTo(nodeFigure);
			Map map = viewer.getEditPartRegistry();
			EditPart editPart = (EditPart)map.get(nodeFigure.getNode());
			if (editPart != null) viewer.select(editPart);
		}

		public void mouseReleased(MouseEvent event) {
		}

	} class MyXYAnchor extends XYAnchor {

		private IFigure f;

		public MyXYAnchor(Point point, IFigure f) {
			super(point);
			this.f = f;
		}

		public Point getLocation(Point reference) {
			Point p = super.getLocation(reference).getCopy();
			f.translateToAbsolute(p);
			return p;
		}

		public IFigure getOwner() {
			return f;
		}

	}
	
}
