/*******************************************************************************
 * Copyright 2005, CHISEL Group, University of Victoria, Victoria, BC, Canada.
 * 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: The Chisel Group, University of Victoria
 ******************************************************************************/
package org.eclipse.zest.layouts.exampleUses;

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

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.zest.layouts.InvalidLayoutConfiguration;
import org.eclipse.zest.layouts.LayoutAlgorithm;
import org.eclipse.zest.layouts.LayoutBendPoint;
import org.eclipse.zest.layouts.LayoutEntity;
import org.eclipse.zest.layouts.LayoutRelationship;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.algorithms.GridLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.HorizontalLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.HorizontalTreeLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.RadialLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.SpringLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.VerticalLayoutAlgorithm;
import org.eclipse.zest.layouts.exampleStructures.SimpleNode;
import org.eclipse.zest.layouts.exampleStructures.SimpleRelationship;
import org.eclipse.zest.layouts.progress.ProgressEvent;
import org.eclipse.zest.layouts.progress.ProgressListener;

/**
 * @author Rob Lintern
 * @author Ian Bull
 * A simple example of using layout algorithms with a SWT application.
 */
public class SimpleSWTExample {

	private static final Color BLACK = new Color(Display.getDefault(), 0, 0, 0);
	private static final Color NODE_NORMAL_COLOR = new Color(Display.getDefault(), 225, 225, 255);
	private static final Color NODE_SELECTED_COLOR = new Color(Display.getDefault(), 255, 125, 125);
	private static final Color NODE_ADJACENT_COLOR = new Color(Display.getDefault(), 255, 200, 125);
	private static final Color BORDER_NORMAL_COLOR = new Color(Display.getDefault(), 0, 0, 0);
	private static final Color BORDER_SELECTED_COLOR = new Color(Display.getDefault(), 255, 0, 0);
	private static final Color BORDER_ADJACENT_COLOR = new Color(Display.getDefault(), 255, 128, 0);
	private static final Color RELATIONSHIP_COLOR = new Color(Display.getDefault(), 192, 192, 225);
	private static final Color RELATIONSHIP_HIGHLIGHT_COLOR = new Color(Display.getDefault(), 255, 200, 125);

	private static final String[] NAMES = new String[] { "Peggy", "Rob", "Ian", "Chris", "Simon", "Wendy", "Steven", "Kim", "Neil", "Dave", "John", "Suzanne", "Jody", "Casey", "Bjorn", "Peter", "Erin", "Lisa", "Jennie", "Liz", "Bert", "Ryan", "Nick", "Amy", "Lee", "Me", "You", "Max", "NCI", "OWL",
			"Ed", "Jamie", "Protege", "Matt", "Bryan", "Pete", "Sam", "Bob", "Katie", "Bill", "Josh", "Davor", "Ken", "Jacob", "Norm", "Jim", "Maya", "Jill", "Kit", "Jo", "Joe", "Andrew", "Charles", "Pat", "Patrick", "Jeremy", "Mike", "Michael", "Patricia", "Marg", "Terry", "Emily", "Ben", "Holly",
			"Joanna", "Joanne", "Evan", "Tom", "Dan", "Eric", "Corey", "Meghan", "Kevin", "Nina", "Ron", "Daniel", "David", "Jeff", "Nathan", "Amanda", "Phil", "Tricia", "Steph", "Stewart", "Stuart", "Bull", "Lintern", "Callendar", "Thompson", "Rigby", "Adam", "Judith", "Cynthia", "Sarah", "Sara",
			"Roger", "Andy", "Kris", "Mark", "Shane", "Spence", "Ivy", "Ivanna", "Julie", "Justin", "Emile", "Toby", "Robin", "Rich", "Kathy", "Cathy", "Nicky", "Ricky", "Danny", "Anne", "Ann", "Jen", "Robert", "Calvin", "Alvin", "Scott", "Kumar" };

	//private static final boolean RENDER_HIGH_QUALITY = true;

	private static final int INITIAL_PANEL_WIDTH = 800;
	private static final int INITIAL_PANEL_HEIGHT = 600;
	private static final double INITIAL_NODE_WIDTH = 20;
	private static final double INITIAL_NODE_HEIGHT = 15;

	protected static ArrayList algorithms = new ArrayList();
	{
		algorithms.add(new SpringLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
		algorithms.add(new TreeLayoutAlgorithm(LayoutStyles.NONE));
		algorithms.add(new HorizontalTreeLayoutAlgorithm(LayoutStyles.NONE));
		algorithms.add(new RadialLayoutAlgorithm(LayoutStyles.NONE));
		algorithms.add(new GridLayoutAlgorithm(LayoutStyles.NONE));
		algorithms.add(new HorizontalLayoutAlgorithm(LayoutStyles.NONE));
		algorithms.add(new VerticalLayoutAlgorithm(LayoutStyles.NONE));
	}

	protected static ArrayList algorithmNames = new ArrayList();
	{
		algorithmNames.add("Spring");
		algorithmNames.add("Fade");
		algorithmNames.add("Tree - V");
		algorithmNames.add("Tree - H");
		algorithmNames.add("Radial");
		algorithmNames.add("Grid");
		algorithmNames.add("Horizontal");
		algorithmNames.add("Vertical");
	}

	protected static ArrayList algorithmAnimates = new ArrayList();
	{
		algorithmAnimates.add(Boolean.TRUE);
		algorithmAnimates.add(Boolean.TRUE);
		algorithmAnimates.add(Boolean.FALSE);
		algorithmAnimates.add(Boolean.FALSE);
		algorithmAnimates.add(Boolean.FALSE);
		algorithmAnimates.add(Boolean.FALSE);
		algorithmAnimates.add(Boolean.FALSE);
		algorithmAnimates.add(Boolean.FALSE);
	}

	//private long updateGUICount = 0;
	private boolean animate = true;
	private static boolean continuous = false;
	private static boolean asynchronously = false;

	private Shell mainShell;
	private Composite mainComposite;
	private List entities;
	private List relationships;

	private ToolBar toolBar;
	private Label lblProgress;

	private LayoutAlgorithm currentLayoutAlgorithm;
	protected SimpleNode selectedEntity;
	protected SimpleNode hoverEntity;

	protected Point mouseDownPoint;
	protected Point selectedEntityPositionAtMouseDown;
	private long idCount = 0;

	public SimpleSWTExample(Display display) {
		mainShell = new Shell(display);
		mainShell.addControlListener(new ControlListener() {
			public void controlMoved(ControlEvent e) {
			}

			public void controlResized(ControlEvent e) {
				mainShell.layout(true);
			}
		});
		GridLayout gridLayout = new GridLayout(1, true);
		mainShell.setLayout(gridLayout);
		GridData toolbarGridData = new GridData(GridData.HORIZONTAL_ALIGN_CENTER, GridData.VERTICAL_ALIGN_BEGINNING, true, true);
		toolBar = new ToolBar(mainShell, SWT.HORIZONTAL);
		toolBar.setLayoutData(toolbarGridData);
		toolBar.setLayout(new FillLayout(SWT.HORIZONTAL));

		GridData progressGridData = new GridData(GridData.HORIZONTAL_ALIGN_CENTER, GridData.VERTICAL_ALIGN_END, true, false);
		progressGridData.widthHint = 300;
		lblProgress = new Label(mainShell, SWT.NONE);
		lblProgress.setLayoutData(progressGridData);
		lblProgress.setText("Progress: ");

		for (int i = 0; i < algorithms.size(); i++) {
			final LayoutAlgorithm algorithm = (LayoutAlgorithm) algorithms.get(i);
			String algorithmName = (String) algorithmNames.get(i);
			final boolean algorithmAnimate = ((Boolean) algorithmAnimates.get(i)).booleanValue();
			ToolItem algorithmButton = new ToolItem(toolBar, SWT.PUSH);
			algorithmButton.setText(algorithmName);

			new ToolItem(toolBar, SWT.SEPARATOR);

			algorithmButton.addSelectionListener(new SelectionListener() {
				public void widgetSelected(org.eclipse.swt.events.SelectionEvent e) {
					currentLayoutAlgorithm = algorithm;
					algorithm.setEntityAspectRatio((double) mainComposite.getClientArea().width / (double) mainComposite.getClientArea().height);
					animate = algorithmAnimate;
					performLayout(false);
				}

				public void widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent e) {
				}
			});
		}

		ToolItem redrawButton = new ToolItem(toolBar, SWT.PUSH);
		redrawButton.setText("Redraw");
		redrawButton.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				mainComposite.redraw();
			}

			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});

		ToolItem stopButton = new ToolItem(toolBar, SWT.PUSH);
		stopButton.setText("Stop");
		stopButton.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				currentLayoutAlgorithm.stop();
			}

			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});

		ToolItem continuousButton = new ToolItem(toolBar, SWT.CHECK);
		continuousButton.setText("Continuous");
		continuousButton.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				setContinuous();
			}

			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});

		ToolItem asynchronousButton = new ToolItem(toolBar, SWT.CHECK);
		asynchronousButton.setText("Asynchronous");
		asynchronousButton.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				setAsynchronously();
			}

			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});

		createMainPanel();
		SimpleNode.setNodeColors(NODE_NORMAL_COLOR, BORDER_NORMAL_COLOR, NODE_SELECTED_COLOR, NODE_ADJACENT_COLOR, BORDER_SELECTED_COLOR, BORDER_ADJACENT_COLOR);
		SimpleRelationship.setDefaultColor(RELATIONSHIP_COLOR);
		SimpleRelationship.setDefaultHighlightColor(RELATIONSHIP_HIGHLIGHT_COLOR);
		createTreeGraph(4, 3, false);
		mainShell.pack();
		//mainShell.setSize(INITIAL_PANEL_WIDTH + 100, INITIAL_PANEL_HEIGHT + 200);
	}

	public void setAsynchronously() {
		if (asynchronously) {
			asynchronously = false;
		} else {
			asynchronously = true;
		}

	}

	public void setContinuous() {
		if (continuous) {
			continuous = false;
		} else {
			continuous = true;
		}
	}

	IProgressMonitor progressMonitor = null;
	ProgressMonitorDialog pmd = null;
	boolean GUI_UPDATING = false;

	private void performLayout(boolean placeRandomly) {

		if (!continuous) {
		}

		if (currentLayoutAlgorithm.isRunning()) {
			throw new RuntimeException("Layout is already running");
		}
		if (placeRandomly) {
			placeRandomly();
		}
		ProgressListener progressListener = new ProgressListener() {

			int lastStep = 0;

			class progressSync implements Runnable {

				public static final int PROGRESS_UPDATED = 1;
				public static final int PROGRESS_STARTED = 2;
				public static final int PROGRESS_ENDED = 3;
				public static final int UPDATE_GUI = 4;

				private int progressState = -1;
				private ProgressEvent e;

				public progressSync(int progressState, final ProgressEvent e) {
					this.progressState = progressState;
					this.e = e;

				}

				public void run() {

					switch (progressState) {
					case PROGRESS_STARTED:
						if (!continuous) {
							pmd = new ProgressMonitorDialog(getShell());
							progressMonitor = pmd.getProgressMonitor();
							pmd.open();
							progressMonitor.beginTask("Layout Running...", e.getTotalNumberOfSteps());
						}
						break;

					case PROGRESS_UPDATED:
						if (!continuous) {
							progressMonitor.worked(e.getStepsCompleted() - lastStep);
							lastStep = e.getStepsCompleted();
						}
						break;

					case PROGRESS_ENDED:
						if (!continuous) {
							progressMonitor.done();
							pmd.close();
						}
						updateGUI();
						mainShell.redraw();
						break;
					case UPDATE_GUI:
						updateGUI();
						GUI_UPDATING = false;
						break;
					}
					mainComposite.redraw();

				}

			}

			public void progressUpdated(final ProgressEvent e) {
				if (asynchronously) {
					if (!mainComposite.isDisposed()) {
						Display.getDefault().asyncExec(new progressSync(progressSync.PROGRESS_UPDATED, e));
						if (!GUI_UPDATING) {
							GUI_UPDATING = true;
							Display.getDefault().asyncExec(new progressSync(progressSync.UPDATE_GUI, e));
						}
					}
				} else {
					if (!mainComposite.isDisposed()) {
						new progressSync(progressSync.PROGRESS_UPDATED, e).run();
					}
				}

			}

			public void progressStarted(ProgressEvent e) {
				if (asynchronously) {
					if (!mainComposite.isDisposed()) {
						Display.getDefault().asyncExec(new progressSync(progressSync.PROGRESS_STARTED, e));
					}
				} else {
					if (!mainComposite.isDisposed()) {
						new progressSync(progressSync.PROGRESS_STARTED, e).run();
					}
				}

			}

			public void progressEnded(ProgressEvent e) {
				if (asynchronously) {
					if (!mainComposite.isDisposed()) {
						Display.getDefault().asyncExec(new progressSync(progressSync.PROGRESS_ENDED, e));
					}
				} else {
					if (!mainComposite.isDisposed()) {
						new progressSync(progressSync.PROGRESS_ENDED, e).run();
					}
				}
				currentLayoutAlgorithm.removeProgressListener(this);
				Display.getDefault().asyncExec(new progressSync(progressSync.PROGRESS_UPDATED, e));
			}
		};
		currentLayoutAlgorithm.addProgressListener(progressListener);

		try {
			LayoutEntity[] layoutEntities = new LayoutEntity[entities.size()];
			entities.toArray(layoutEntities);
			LayoutRelationship[] layoutRelationships = new LayoutRelationship[relationships.size()];
			relationships.toArray(layoutRelationships);
			currentLayoutAlgorithm.applyLayout(layoutEntities, layoutRelationships, 0, 0, mainComposite.getClientArea().width - 30, mainComposite.getClientArea().height - 17, asynchronously, continuous);
			if (!animate) {
				updateGUI();
			}
		} catch (InvalidLayoutConfiguration e) {
			e.printStackTrace();
		}
	}

	private Shell getShell() {
		return mainShell;
	}

	private void createMainPanel() {
		mainComposite = new Canvas(mainShell, SWT.NO_BACKGROUND);
		GridData mainGridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL, GridData.VERTICAL_ALIGN_FILL, true, true);
		mainGridData.widthHint = INITIAL_PANEL_WIDTH;
		mainGridData.heightHint = INITIAL_PANEL_HEIGHT;
		mainComposite.setLayoutData(mainGridData);
		mainComposite.addPaintListener(new GraphPaintListener());

		mainComposite.setBackground(new Color(Display.getCurrent(), 255, 255, 255));
		mainComposite.setLayout(null);

		mainComposite.addMouseMoveListener(new MouseMoveListener() {

			public void mouseMove(MouseEvent e) {

				if (selectedEntity == null) {
					// Nothing selected, lets use a mouse hover
					SimpleNode oldEntity = hoverEntity;
					hoverEntity = null;

					for (Iterator iter = entities.iterator(); iter.hasNext() && selectedEntity == null;) {
						SimpleNode entity = (SimpleNode) iter.next();
						double x = entity.getX();
						double y = entity.getY();
						double w = entity.getWidth();
						double h = entity.getHeight();
						Rectangle rect = new Rectangle((int) x, (int) y, (int) w, (int) h);
						if (rect.contains(e.x, e.y)) {
							hoverEntity = entity;
							hoverEntity.ignoreInLayout(true);
							hoverEntity.setSelected();
							break;
						}
					}
					if (oldEntity != null && oldEntity != hoverEntity) {
						oldEntity.ignoreInLayout(false);
						oldEntity.setUnSelected();
					}
				}

			}

		});
		mainComposite.addMouseListener(new MouseListener() {

			public void mouseDoubleClick(MouseEvent e) {
			}

			public void mouseDown(MouseEvent e) {
				selectedEntity = null;
				hoverEntity = null;
				for (Iterator iter = entities.iterator(); iter.hasNext() && selectedEntity == null;) {
					SimpleNode entity = (SimpleNode) iter.next();
					double x = entity.getX();
					double y = entity.getY();
					double w = entity.getWidth();
					double h = entity.getHeight();
					Rectangle rect = new Rectangle((int) x, (int) y, (int) w, (int) h);
					if (rect.contains(e.x, e.y)) {
						selectedEntity = entity;
					}
				}
				if (selectedEntity != null) {
					mouseDownPoint = new Point(e.x, e.y);
					selectedEntityPositionAtMouseDown = new Point((int) selectedEntity.getX(), (int) selectedEntity.getY());
					selectedEntity.ignoreInLayout(true);
					selectedEntity.setSelected();
				} else {
					mouseDownPoint = null;
					selectedEntityPositionAtMouseDown = null;
				}
			}

			public void mouseUp(MouseEvent e) {
				if (selectedEntity != null) {
					selectedEntity.ignoreInLayout(false);
					selectedEntity.setUnSelected();
					List relatedNodes = selectedEntity.getRelatedEntities();
					for (Iterator iter = relatedNodes.iterator(); iter.hasNext();) {
						SimpleNode element = (SimpleNode) iter.next();
						element.setUnSelected();
					}
					SimpleRelationship[] rels = selectedEntity.getRelationships();
					for (int i = 0; i < rels.length; i++) {
						rels[i].resetLineWidth();
					}
				}
				selectedEntity = null;
				mouseDownPoint = null;
				selectedEntityPositionAtMouseDown = null;
			}
		});

		// stops the algorithm when the window is closed
		mainComposite.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				if (currentLayoutAlgorithm != null) {
					currentLayoutAlgorithm.stop();
				}
			}
		});

		mainComposite.addMouseMoveListener(new MouseMoveListener() {

			public void mouseMove(MouseEvent e) {
				if (selectedEntity != null && mouseDownPoint != null) {
					double dx = e.x - mouseDownPoint.x;
					double dy = e.y - mouseDownPoint.y;

					selectedEntity.setLocation(selectedEntityPositionAtMouseDown.x + dx, selectedEntityPositionAtMouseDown.y + dy);
					mainComposite.redraw();
				}
			}
		});
	}

	static int lastUpdateCall = 0;

	/**
	 * 
	 * @param maxLevels Max number of levels wanted in tree	
	 * @param maxChildren Max number of children for each node in the tree
	 * @param random Whether or not to pick random number of levels (from 1 to maxLevels) and 
	 * random number of children (from 1 to maxChildren)
	 */
	private void createTreeGraph(int maxLevels, int maxChildren, boolean random) {
		entities = new ArrayList();
		relationships = new ArrayList();

		// ccallendar - testing out having 2 roots 
		SimpleNode root = createSimpleNode(getNextID());
		entities.add(root);
		SimpleNode root2 = createSimpleNode(getNextID());
		entities.add(root2);
		// end

		SimpleNode currentParent = createSimpleNode(getNextID());
		entities.add(currentParent);

		// ccallendar - adding relationships from the parent to the 2 roots
		SimpleRelationship rel = new SimpleRelationship(root, currentParent, false);
		root.addRelationship(rel);
		currentParent.addRelationship(rel);
		relationships.add(rel);
		rel = new SimpleRelationship(root2, currentParent, false);
		root2.addRelationship(rel);
		currentParent.addRelationship(rel);
		relationships.add(rel);
		// end 

		int levels = random ? (int) (Math.random() * maxLevels + 1) : maxLevels;
		createTreeGraphRecursive(currentParent, maxChildren, levels, 1, random);
	}

	private void createTreeGraphRecursive(SimpleNode currentParentNode, int maxChildren, int maxLevel, int level, boolean random) {
		if (level > maxLevel) {
			return;
		}

		int numChildren = random ? (int) (Math.random() * maxChildren + 1) : maxChildren;
		for (int child = 0; child < numChildren; child++) {
			SimpleNode childNode = createSimpleNode(getNextID());
			entities.add(childNode);
			SimpleRelationship rel = new SimpleRelationship(currentParentNode, childNode, false);
			childNode.addRelationship(rel);
			currentParentNode.addRelationship(rel);
			relationships.add(rel);
			SimpleRelationship.setDefaultSize(2);
			createTreeGraphRecursive(childNode, maxChildren, maxLevel, level + 1, random);
		}
	}

	private int repeats = 0;

	/**
	 * Gets the next name from the names list.
	 * Once all the names have been used up the names are
	 * repeated with a '1' after the name.
	 * @return String name
	 */
	private String getNextID() {
		if (idCount >= NAMES.length) {
			idCount = 0;
			repeats++;
		}
		String id = NAMES[(int) idCount];
		if (repeats > 0) {
			id += "_" + repeats;
		}
		idCount++;
		return id;
	}

	/** Places nodes randomly on the screen **/
	private void placeRandomly() {
		for (Iterator iter = entities.iterator(); iter.hasNext();) {
			SimpleNode simpleNode = (SimpleNode) iter.next();
			double x = Math.random() * INITIAL_PANEL_WIDTH - INITIAL_NODE_WIDTH;
			double y = Math.random() * INITIAL_PANEL_HEIGHT - INITIAL_NODE_HEIGHT;
			simpleNode.setLocationInLayout(x, y);
		}
	}

	/**
	 * Creates a SimpleNode
	 * @param name
	 * @return SimpleNode
	 */
	private SimpleNode createSimpleNode(String name) {
		SimpleNode simpleNode = new SimpleNode(name);
		int w = name.length() * 8; // an initial approximation of the width
		simpleNode.setSizeInLayout(Math.max(w, INITIAL_NODE_WIDTH), INITIAL_NODE_HEIGHT);
		return simpleNode;
	}

	private void updateGUI() {
		if (!mainComposite.isDisposed()) {
			mainComposite.redraw();
			//mainComposite.update();
		}
	}

	static Display display = null;

	public static void main(String[] args) {
		display = Display.getDefault();
		SimpleSWTExample simpleSWTExample = new SimpleSWTExample(display);
		Shell shell = simpleSWTExample.getShell();
		//shell.pack();
		shell.open();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) {
				display.sleep();
			}
		}
		display.dispose();
	}

	/**
	 * Implements a paint listener to display nodes and edges  
	 */
	private class GraphPaintListener implements PaintListener {

		long lastPaint;

		public void paintControl(PaintEvent e) {
			Date date = new Date();
			long currentTime = date.getTime();
			if (currentTime - lastPaint < 40) {
				return;
			} else {
				lastPaint = currentTime;
			}
			if (Display.getDefault() == null || e.width == 0 || e.height == 0) {
				return;
			}
			long startTime = date.getTime();

			// do a bit of our own double-buffering to stop flickering
			Image imageBuffer;

			try {
				imageBuffer = new Image(Display.getDefault(), e.width, e.height);
			} catch (SWTError noMoreHandles) {
				imageBuffer = null;
				noMoreHandles.printStackTrace();
				return;
			} catch (IllegalArgumentException tooBig) {
				imageBuffer = null;
				tooBig.printStackTrace();
				return;
			}

			GC gcBuffer = new GC(imageBuffer);

			// paint the relationships 
			for (Iterator iter = relationships.iterator(); iter.hasNext();) {
				SimpleRelationship rel = (SimpleRelationship) iter.next();
				SimpleNode src = (SimpleNode) rel.getSourceInLayout();
				SimpleNode dest = (SimpleNode) rel.getDestinationInLayout();

				// highlight the adjacent nodes if one of the nodes is selected
				if (src.equals(selectedEntity)) {
					dest.setAdjacent();
					rel.setSelected();
				} else if (dest.equals(selectedEntity)) {
					src.setAdjacent();
					rel.setSelected();
				} else {
					rel.setUnSelected();
				}

				// Add bend points if required
				if ((rel).getBendPoints() != null && (rel).getBendPoints().length > 0) {
					src = drawBendPoints(rel, gcBuffer); // change source to last bendpoint
				}

				double srcX = src.getX() + src.getWidth() / 2.0;
				double srcY = src.getY() + src.getHeight() / 2.0;
				double destX = dest.getX() + dest.getWidth() / 2.0;
				double destY = dest.getY() + dest.getHeight() / 2.0;
				drawEdge(srcX, srcY, destX, destY, rel, gcBuffer);

			}

			// paint the nodes
			for (Iterator iter = entities.iterator(); iter.hasNext();) {
				SimpleNode entity = (SimpleNode) iter.next();

				String name = entity.toString();
				Point textSize = gcBuffer.stringExtent(name);
				int entityX = (int) entity.getX();
				int entityY = (int) entity.getY();
				//TODO: What about resize from the layout algorithm
				int entityWidth = Math.max((int) entity.getWidth(), textSize.x + 8);
				int entityHeight = Math.max((int) entity.getHeight(), textSize.y + 2);

				gcBuffer.setBackground((Color) entity.getColor());
				gcBuffer.fillRoundRectangle(entityX, entityY, entityWidth, entityHeight, 8, 8);

				// position the text in the middle of the node
				int x = (int) (entityX + (entityWidth / 2.0)) - (textSize.x / 2);
				gcBuffer.setForeground(BLACK);
				gcBuffer.drawString(name, x, entityY);
				gcBuffer.setForeground((Color) entity.getBorderColor());
				gcBuffer.setLineWidth(entity.getBorderWidth());
				gcBuffer.drawRoundRectangle(entityX, entityY, entityWidth, entityHeight, 8, 8);
			}

			e.gc.drawImage(imageBuffer, 0, 0);
			imageBuffer.dispose();
			gcBuffer.dispose();

			long time = date.getTime() - startTime;
			if (time > 200) {
			}
		}

		/**
		 * Draw an edge
		 * @param gcBuffer
		 * @param srcX
		 * @param srcY
		 * @param destX
		 * @param destY
		 * @param rel
		 */
		private void drawEdge(double srcX, double srcY, double destX, double destY, SimpleRelationship rel, GC gcBuffer) {
			gcBuffer.setForeground((Color) rel.getColor());
			gcBuffer.setLineWidth(rel.getLineWidth());
			gcBuffer.drawLine((int) srcX, (int) srcY, (int) destX, (int) destY);
		}

		/**
		 * Draws a set of lines between bendpoints
		 * TODO - This does not always draw outside the node.
		 * @param relationship
		 * @param bendNodes
		 * @param bendEdges
		 * @return the last bendpoint entity or null if there are no bendpoints
		 */
		private SimpleNode drawBendPoints(SimpleRelationship rel, GC gcBuffer) {
			final String DUMMY_TITLE = "dummy";
			LayoutBendPoint[] bendPoints = (rel).getBendPoints();
			LayoutBendPoint bp;
			SimpleNode startEntity = (SimpleNode) rel.getSourceInLayout();
			SimpleNode destEntity = null;

			double srcX = startEntity.getX() + startEntity.getWidth() / 2.0;
			double srcY = startEntity.getY() + startEntity.getHeight() / 2.0;
			for (int i = 1; i < bendPoints.length - 1; i++) {
				bp = bendPoints[i];
				destEntity = new SimpleNode(DUMMY_TITLE, bp.getX(), bp.getY(), 0.01, 0.01);
				drawEdge(srcX, srcY, bp.getX(), bp.getY(), rel, gcBuffer);
				startEntity = destEntity;
				srcX = startEntity.getX();
				srcY = startEntity.getY();
			}
			return destEntity;
		}

	}

}
