/*******************************************************************************
 * Copyright 2005-2007, 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.core.widgets;

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

import org.eclipse.draw2d.Animation;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.FreeformLayout;
import org.eclipse.draw2d.FreeformViewport;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.LayoutAnimator;
import org.eclipse.draw2d.LineBorder;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.ScrollPane;
import org.eclipse.draw2d.Viewport;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.graphics.Image;
import org.eclipse.zest.core.widgets.internal.AspectRatioFreeformLayer;
import org.eclipse.zest.core.widgets.internal.ContainerFigure;
import org.eclipse.zest.core.widgets.internal.ExpandGraphLabel;
import org.eclipse.zest.core.widgets.internal.ZestRootLayer;
import org.eclipse.zest.layouts.InvalidLayoutConfiguration;
import org.eclipse.zest.layouts.LayoutAlgorithm;
import org.eclipse.zest.layouts.LayoutEntity;
import org.eclipse.zest.layouts.LayoutRelationship;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm;

/*
 * A Container than can be added to a Graph. Nodes can be added to this
 * container. The container supports collapsing and expanding and has the same
 * properties as the nodes. Containers cannot have custom figures.
 * 
 * @author Ian Bull
 */
public class GraphContainer extends GraphNode implements IContainer {

	//private static final double CONTAINER_SCALE = 0.75;
	private static final double scaledWidth = 300;
	private static final double scaledHeight = 200;
	private static final int CONTAINER_HEIGHT = 200;
	private static final int MIN_WIDTH = 250;
	private static final int ANIMATION_TIME = 100;
	private static final int SUBLAYER_OFFSET = 2;

	private ExpandGraphLabel expandGraphLabel;

	//private FreeformLayer container;
	//private FreeformLayer edgeLayer;
	private List childNodes = null;
	private int childAreaHeight = CONTAINER_HEIGHT;

	public ZestRootLayer zestLayer;
	private ScrollPane scrollPane;
	private LayoutAlgorithm layoutAlgorithm;
	private boolean isExpanded = false;
	//private ScalableFreeformLayeredPane scalledLayer;
	private AspectRatioFreeformLayer scalledLayer;

	/**
	 * Creates a new GraphContainer.  A GraphContainer may contain nodes,
	 * and has many of the same properties as a graph node.
	 * @param graph The graph that the container is being added to
	 * @param style 
	 */
	public GraphContainer(IContainer graph, int style) {
		this(graph, style, "");

	}

	public GraphContainer(IContainer graph, int style, String text) {
		this(graph, style, text, null);

	}

	public GraphContainer(IContainer graph, int style, String text, Image image) {
		super(graph, style, text, image);
		initModel(graph, text, image);
		close(false);
		childNodes = new ArrayList();
	}

	/**
	 * Custom figures cannot be set on a GraphContainer.
	 */
	public void setCustomFigure(IFigure nodeFigure) {
		throw new RuntimeException("Operation not supported:  Containers cannot have custom figures");
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.mylar.zest.core.widgets.GraphItem#getItemType()
	public int getItemType() {
		return GraphItem.CONTAINER;
	}

	/**
	 * Gets the figure for this container.
	 */
	public IFigure getNodeFigure() {
		return this.nodeFigure;
	}

	/**
	 * Close this node.
	 * @param animate
	 */
	public void close(boolean animate) {
		if (animate) {
			Animation.markBegin();
		}
		isExpanded = false;

		expandGraphLabel.setExpandedState(ExpandGraphLabel.CLOSED);
		Rectangle newBounds = scrollPane.getBounds().getCopy();
		newBounds.height = 0;

		//this.nodeFigure.setConstraint(scrollPane, newBounds);
		//this.nodeFigure.revalidate();
		scrollPane.setSize(scrollPane.getSize().width, 0);
		updateFigureForModel(this.zestLayer);
		scrollPane.setVisible(false);
		//setSize(expandGraphLabel.getSize().width, expandGraphLabel.getSize().height);
		List children = this.zestLayer.getChildren();
		for (Iterator iterator = children.iterator(); iterator.hasNext();) {
			IFigure child = (IFigure) iterator.next();
			GraphItem item = getGraph().getGraphItem(child);
			item.setVisible(false);
		}
		Rectangle containerBounds = new Rectangle(this.getLocation(), new Dimension(this.getSize().width, CONTAINER_HEIGHT + this.expandGraphLabel.getSize().height));
		moveNodesUp(containerBounds, this);
		if (animate) {
			Animation.run(ANIMATION_TIME);
		}
		//this.nodeFigure.getUpdateManager().performUpdate();
		updateFigureForModel(nodeFigure);
	}

	private static void addNodeToOrderedList(List orderedNodeList, GraphNode node) {
		Iterator orderedNodeIterator = orderedNodeList.iterator();
		int counter = 0;
		while (orderedNodeIterator.hasNext()) {
			// Look through the list of nodes below and find the right spot for this
			GraphNode nextOrderedNode = (GraphNode) orderedNodeIterator.next();
			if (nextOrderedNode.getLocation().y + nextOrderedNode.getBounds().height > node.getLocation().y + node.getBounds().height) {
				break;
			}
			counter++;
		}
		// Place this in the right location
		orderedNodeList.add(counter, node);
	}

	/**
	 * Gets all the nodes below the yValue.  The nodes are returned in order.
	 * @param nodes
	 * @param yValue
	 * @return
	 */
	private static List getOrderedNodesBelowY(List nodes, int yValue, GraphNode yValueNode) {
		Iterator iterator = nodes.iterator();
		LinkedList orderedNode = new LinkedList();
		while (iterator.hasNext()) {
			GraphNode node = (GraphNode) iterator.next();
			if (node == yValueNode) {
				continue;
			}
			if (node.getLocation().y + node.getBounds().height > yValue) {
				// This node is below the container
				addNodeToOrderedList(orderedNode, node);
			}
		}
		// Convert this to an arrayList for faster access
		List arrayList = new ArrayList();
		iterator = orderedNode.iterator();
		while (iterator.hasNext()) {
			arrayList.add(iterator.next());
		}
		return arrayList;
	}

	/**
	 * Checks if the node intersects the stripe between left and right
	 * @param left
	 * @param right
	 * @param node
	 * @return
	 */
	private static boolean nodeInStripe(int left, int right, GraphNode node) {
		return (node.getBounds().x < right && node.getBounds().x + node.getBounds().width > left);
	}

	void pack(Graph g) {
		GraphNode highestNode = getHighestNode(g);
		moveNodesUp(highestNode.getBounds(), highestNode);
	}

	/**
	 * 
	 * @param g
	 * @return
	 */
	static GraphNode getHighestNode(Graph g) {
		Iterator iterator = g.getNodes().iterator();
		GraphNode lowest /* highest on the screen */= null;

		while (iterator.hasNext()) {
			GraphNode node = (GraphNode) iterator.next();
			if (lowest == null || lowest.getBounds().y > node.getBounds().y) {
				lowest = node;
			}
		}
		return lowest;

	}

	/**
	 * Move the nodes below this node up
	 * @param containerBounds
	 * @param graphContainer
	 */
	private void moveNodesUp(Rectangle containerBounds, GraphNode graphContainer) {

		// Get all nodes below this container, in order
		List orderedNodesBelowY = getOrderedNodesBelowY(parent.getNodes(), containerBounds.y, graphContainer);
		int leftSide = containerBounds.x;
		int rightSide = containerBounds.x + containerBounds.width;
		List nodesToConsider = new LinkedList();
		for (int i = 0; i < orderedNodesBelowY.size(); i++) {
			nodesToConsider.add(orderedNodesBelowY.get(i));
		}
		addNodeToOrderedList(orderedNodesBelowY, graphContainer);

		while (nodesToConsider.size() > 0) {
			GraphNode node = (GraphNode) nodesToConsider.get(0);
			if (nodeInStripe(leftSide, rightSide, node)) {
				leftSide = Math.min(leftSide, node.getBounds().x);
				rightSide = Math.max(rightSide, node.getBounds().x + node.getBounds().width);
				// If this node is in the stripe, move it up 
				// the previous node
				GraphNode previousNode = null;
				int i = 0;
				for (; i < orderedNodesBelowY.size(); i++) {
					if (orderedNodesBelowY.get(i) == node) {
						break;
					}
				}
				int j = i - 1;
				while (j >= 0) {
					GraphNode pastNode = (GraphNode) orderedNodesBelowY.get(j);
					//if (nodeInStripe(leftSide, rightSide, pastNode)) {
					if (nodeInStripe(node.getBounds().x, node.getBounds().x + node.getBounds().width, pastNode)) {
						previousNode = pastNode;
						break;
					}
					j--;
				}
				if (previousNode == null) {
					previousNode = graphContainer;
				}
				int previousLocation = previousNode.getBounds().y + previousNode.getBounds().height + 2;

				orderedNodesBelowY.remove(i);
				node.setLocation(node.getLocation().x, previousLocation);
				addNodeToOrderedList(orderedNodesBelowY, node);

			}
			nodesToConsider.remove(node);
		}
	}

	/**
	 * Open the container.  This opens the graph container to 
	 * show the nodes within and update the twistie
	 */
	public void open(boolean animate) {
		if (animate) {
			Animation.markBegin();
		}
		isExpanded = true;

		expandGraphLabel.setExpandedState(ExpandGraphLabel.OPEN);

		scrollPane.setSize(computeChildArea());
		scrollPane.setVisible(true);
		//setSize(expandGraphLabel.getSize().width, expandGraphLabel.getSize().height + expandedHeight - SUBLAYER_OFFSET);

		List children = this.zestLayer.getChildren();
		for (Iterator iterator = children.iterator(); iterator.hasNext();) {
			IFigure child = (IFigure) iterator.next();
			GraphItem item = getGraph().getGraphItem(child);
			item.setVisible(true);
		}

		updateFigureForModel(nodeFigure);

		Rectangle containerBounds = new Rectangle(this.getLocation(), new Dimension(this.getSize().width, CONTAINER_HEIGHT + this.expandGraphLabel.getSize().height));
		//moveIntersectedNodes(containerBounds, this);
		moveNodesDown(containerBounds, this);
		moveNodesUp(containerBounds, this);
		//pack(graph);
		if (animate) {
			Animation.run(ANIMATION_TIME);
		}
		this.getFigure().getUpdateManager().performValidation();
		//this.nodeFigure.getUpdateManager().performUpdate();

	}

	/**
	 * 
	 * @param containerBounds
	 * @param graphContainer
	 */
	private void moveNodesDown(Rectangle containerBounds, GraphContainer graphContainer) {

		// Find all nodes below here
		List nodesBelowHere = getOrderedNodesBelowY(parent.getNodes(), containerBounds.y, graphContainer);
		Iterator nodesBelowHereIterator = nodesBelowHere.iterator();
		List nodesToMove = new LinkedList();
		int left = containerBounds.x;
		int right = containerBounds.x + containerBounds.width;
		while (nodesBelowHereIterator.hasNext()) {
			GraphNode node = (GraphNode) nodesBelowHereIterator.next();
			if (nodeInStripe(left, right, node)) {
				nodesToMove.add(node);
				left = Math.min(left, node.getBounds().x);
				right = Math.max(right, node.getBounds().x + node.getBounds().width);
			}
		}
		List intersectingNodes = intersectingNodes(containerBounds, nodesToMove, graphContainer);
		int delta = getMaxMovement(containerBounds, intersectingNodes);
		if (delta > 0) {
			shiftNodesDown(nodesToMove, delta);
		}

	}

	void highlightNode(GraphNode node) {

	}

	void highlightEdge(GraphConnection connection) {
	}

	void highlightNode(GraphContainer container) {

	}

	void unhighlightNode(GraphNode node) {

	}

	void unhighlightNode(GraphContainer container) {

	}

//	/**
//	 * Gets a list of nodes below the given node
//	 * @param node
//	 * @return
//	 */
//	private List getNodesBelow(int y, List nodes) {
//		Iterator allNodes = nodes.iterator();
//		LinkedList result = new LinkedList();
//		while (allNodes.hasNext()) {
//			GraphNode nextNode = (GraphNode) allNodes.next();
//			int top = nextNode.getLocation().y;
//			if (top > y) {
//				result.add(nextNode);
//			}
//		}
//		return result;
//	}

	/**
	 * Checks all the nodes in the list of nodesToCheck to see if they intersect with the bounds set
	 * @param node
	 * @param nodesToCheck
	 * @return
	 */
	private List intersectingNodes(Rectangle bounds, List nodesToCheck, GraphNode node) {
		List result = new LinkedList();
		Iterator nodes = nodesToCheck.iterator();
		while (nodes.hasNext()) {
			GraphNode nodeToCheck = (GraphNode) nodes.next();
			if (node == nodeToCheck) {
				continue;
			}
			if (bounds.intersects(nodeToCheck.getBounds())) {
				result.add(nodeToCheck);
			}
		}
		return result;
	}

	/**
	 * Gets the max distance the intersecting nodes need to be shifted to make room for the expanding node
	 * @param bounds
	 * @param nodesToMove
	 * @return
	 */
	private int getMaxMovement(Rectangle bounds, List nodesToMove) {
		Iterator iterator = nodesToMove.iterator();
		int maxMovement = 0;
		while (iterator.hasNext()) {
			GraphNode node = (GraphNode) iterator.next();
			int yValue = node.getLocation().y;
			int distanceFromBottom = (bounds.y + bounds.height) - yValue;
			maxMovement = Math.max(maxMovement, distanceFromBottom);
		}
		return maxMovement + 3;
	}

	/**
	 * Shifts a collection of nodes down.
	 * @param nodesToShift
	 * @param amount
	 */
	private void shiftNodesDown(List nodesToShift, int amount) {
		Iterator iterator = nodesToShift.iterator();
		while (iterator.hasNext()) {
			GraphNode node = (GraphNode) iterator.next();

			node.setLocation(node.getLocation().x, node.getLocation().y + amount);
		}
	}

//	/**
//	 * This finds the highest Y Value of a set of nodes.
//	 * @param nodes
//	 * @return
//	 */
//	private int findSmallestYValue(List nodes) {
//		Iterator iterator = nodes.iterator();
//		int lowestNode /*highest on the screen*/= Integer.MAX_VALUE - 100; // Subtract 100 so we don't overflow
//		while (iterator.hasNext()) {
//			GraphNode node = (GraphNode) iterator.next();
//			int y = node.getLocation().y;
//			lowestNode = Math.min(lowestNode, y);
//		}
//		return lowestNode;
//	}

//	/**
//	 * Clears the nodes that the container intersects as it expands
//	 * @param containerBounds
//	 * @param graphContainer
//	 */
//	private void moveIntersectedNodes(Rectangle containerBounds, GraphNode graphContainer) {
//
//		List nodesBelowHere = getNodesBelow(this.getLocation().y, graphContainer.getGraphModel().getNodes());
//		List intersectingNodes = intersectingNodes(containerBounds, nodesBelowHere, graphContainer);
//		int delta = getMaxMovement(containerBounds, intersectingNodes);
//		shiftNodesDown(intersectingNodes, delta);
//
//		int lowestNode /*highest on the screen*/= findSmallestYValue(intersectingNodes);
//		nodesBelowHere = getNodesBelow(lowestNode, nodesBelowHere);
//
//		while (nodesBelowHere.size() > 0) {
//			Iterator intersectingNodeIterator = intersectingNodes.iterator();
//			List nodesMovedInLastIteration = new LinkedList();
//			while (intersectingNodeIterator.hasNext()) {
//				GraphNode node = (GraphNode) intersectingNodeIterator.next();
//				intersectingNodes = intersectingNodes(node.getBounds(), nodesBelowHere, node);
//				delta = getMaxMovement(node.getBounds(), intersectingNodes);
//				if (delta > 0) {
//					shiftNodesDown(intersectingNodes, delta);
//					nodesMovedInLastIteration.addAll(intersectingNodes);
//				}
//			}
//			lowestNode /*highest on the screen*/= findSmallestYValue(nodesMovedInLastIteration);
//			nodesBelowHere = getNodesBelow(lowestNode, nodesBelowHere);
//			intersectingNodes = nodesMovedInLastIteration;
//		}
//	}

	/**
	 * Gets the graph that this container has been added to.
	 */
	public Graph getGraph() {
		return this.graph.getGraph();
	}

	public int getItemType() {
		return CONTAINER;
	}

	/**
	 * 
	 */
	public void setLayoutAlgorithm(LayoutAlgorithm algorithm, boolean applyLayout) {
		this.layoutAlgorithm = algorithm;
		if (applyLayout) {
			applyLayout();
		}

	}

	public void applyLayout() {
		if ((this.getNodes().size() == 0)) {
			return;
		}

		int layoutStyle = 0;

		if (checkStyle(ZestStyles.NODES_NO_LAYOUT_RESIZE)) {
			layoutStyle = LayoutStyles.NO_LAYOUT_NODE_RESIZING;
		}

		if (layoutAlgorithm == null) {
			layoutAlgorithm = new TreeLayoutAlgorithm(layoutStyle);
		}

		layoutAlgorithm.setStyle(layoutAlgorithm.getStyle() | layoutStyle);

		// calculate the size for the layout algorithm
		//Dimension d = this.scalledLayer.getSize();
		Dimension d = new Dimension();
		d.width = (int) scaledWidth;
		d.height = (int) scaledHeight;

		d.width = d.width - 10;
		d.height = d.height - 10;
		//if (d.height <= 0) {
		//d.height = (CONTAINER_HEIGHT);
		//}
		//d.scale(1 / this.scalledLayer.getScale());

		if (d.isEmpty()) {
			return;
		}
		LayoutRelationship[] connectionsToLayout = getGraph().getConnectionsToLayout(getNodes());
		LayoutEntity[] nodesToLayout = getGraph().getNodesToLayout(getNodes());

		try {
			Animation.markBegin();
			layoutAlgorithm.applyLayout(nodesToLayout, connectionsToLayout, 25, 25, d.width - 50, d.height - 50, false, false);
			Animation.run(ANIMATION_TIME);
			getFigure().getUpdateManager().performUpdate();

		} catch (InvalidLayoutConfiguration e) {
			e.printStackTrace();
		}

	}

	/**
	 * Get the scale for this container. This is the scale applied to the children contained within
	 * @return
	 */
	public double getScale() {
		return this.scalledLayer.getScale();
	}

	/**
	 * Set the scale for this container. This is the scale applied to the children contained within.
	 * @param scale
	 */
	public void setScale(double scale) {
		this.scalledLayer.setScale(scale);
	}

	/***************************************************************************
	 * NON API MEMBERS
	 **************************************************************************/
	protected void initFigure() {
		nodeFigure = createContainerFigure();
	}

	/**
	 * This is a small class to help represent the size of the container.  It should only be
	 * used in the computeContainerSize method.
	 */
	class ContainerDimension {
		int width;
		int labelHeight;
		int expandedHeight;
	}

	/**
	 * Computes size of the scroll pane that the child nodes will be placed in.
	 * @return
	 */
	private Dimension computeChildArea() {
		ContainerDimension containerDimension = computeContainerSize();
		Dimension dimension = new Dimension();
		dimension.width = containerDimension.width;
		dimension.height = containerDimension.expandedHeight - containerDimension.labelHeight + SUBLAYER_OFFSET;
		return dimension;
	}

	/**
	 * Computes the desired size of the container.  This method uses the 
	 * minimum size, label size and setSize to compute the size.
	 * @return
	 */
	private ContainerDimension computeContainerSize() {
		ContainerDimension dimension = new ContainerDimension();
		int labelHeight = expandGraphLabel.getPreferredSize().height;
		int labelWidth = expandGraphLabel.getPreferredSize().width;
		if (labelWidth < MIN_WIDTH) {
			labelWidth = MIN_WIDTH;
			expandGraphLabel.setPreferredSize(labelWidth, labelHeight);
		}
		if (labelHeight < 30) {
			labelHeight = 30;
		}

		dimension.labelHeight = labelHeight;
		dimension.width = labelWidth;
		dimension.width = Math.max(dimension.width, this.size.width);
		dimension.expandedHeight = dimension.labelHeight + childAreaHeight - SUBLAYER_OFFSET;
		dimension.expandedHeight = Math.max(dimension.expandedHeight, this.size.height);

		return dimension;
	}

	/*
	private double computeChildScale() {
		Dimension childArea = computeChildArea();
		double widthScale = childArea.width / scaledWidth;
		double heightScale = childArea.height / scaledHeight;
		return Math.min(widthScale, heightScale);
	}
	*/
	private double computeHeightScale() {
		Dimension childArea = computeChildArea();
		double heightScale = childArea.height / scaledHeight;
		return heightScale;
	}

	private double computeWidthScale() {
		Dimension childArea = computeChildArea();
		double widthScale = childArea.width / scaledWidth;
		return widthScale;
	}

	private IFigure createContainerFigure() {
		GraphContainer node = this;
		IFigure containerFigure = new ContainerFigure();
		containerFigure.setOpaque(true);

		containerFigure.addLayoutListener(LayoutAnimator.getDefault());

		containerFigure.setLayoutManager(new FreeformLayout());
		expandGraphLabel = new ExpandGraphLabel(this, node.getText(), node.getImage(), false);
		expandGraphLabel.setText(getText());
		expandGraphLabel.setImage(getImage());
		ContainerDimension containerDimension = computeContainerSize();

		scrollPane = new ScrollPane();
		scrollPane.addLayoutListener(LayoutAnimator.getDefault());

		Viewport viewport = new FreeformViewport();
		/*
		 * This is the code that helps remove the scroll bars moving when the nodes
		 * are dragged.  
		 *
		viewport.setHorizontalRangeModel(new DefaultRangeModel() {
			public void setAll(int min, int ext, int max) {
				System.out.println("Max: " + max + " : current Max:  " + getMaximum());
				if (max < getMaximum()) {
					max = getMaximum();
				}
				super.setAll(min, ext, max);
			}

			public void setMaximum(int maximum) {
				// TODO Auto-generated method stub
				System.out.println("Max: " + maximum + " : current Max:  " + getMaximum());
				if (maximum < getMaximum()) {
					return;
				}
				super.setMaximum(maximum);
			}
		});
		*/

		scrollPane.setViewport(viewport);
		viewport.addLayoutListener(LayoutAnimator.getDefault());
		scrollPane.setScrollBarVisibility(ScrollPane.AUTOMATIC);

		//scalledLayer = new ScalableFreeformLayeredPane();
		scalledLayer = new AspectRatioFreeformLayer("debug label");
		scalledLayer.addLayoutListener(LayoutAnimator.getDefault());
		//scalledLayer.setScale(computeChildScale());
		scalledLayer.setScale(computeWidthScale(), computeHeightScale());
		//container = new FreeformLayer();
		//edgeLayer = new FreeformLayer();
		zestLayer = new ZestRootLayer();
		zestLayer.addLayoutListener(LayoutAnimator.getDefault());
		//container.addLayoutListener(LayoutAnimator.getDefault());
		//edgeLayer.addLayoutListener(LayoutAnimator.getDefault());
		//scalledLayer.add(edgeLayer);
		//scalledLayer.add(container);
		scalledLayer.add(zestLayer);

		//container.setLayoutManager(new FreeformLayout());
		zestLayer.setLayoutManager(new FreeformLayout());
		scrollPane.setSize(computeChildArea());
		scrollPane.setLocation(new Point(0, containerDimension.labelHeight - SUBLAYER_OFFSET));
		scrollPane.setForegroundColor(ColorConstants.gray);

		expandGraphLabel.setBackgroundColor(getBackgroundColor());
		expandGraphLabel.setForegroundColor(getForegroundColor());
		expandGraphLabel.setLocation(new Point(0, 0));

		containerFigure.add(scrollPane);
		containerFigure.add(expandGraphLabel);

		scrollPane.getViewport().setContents(scalledLayer);
		scrollPane.setBorder(new LineBorder());

		return containerFigure;
	}

	protected void updateFigureForModel(IFigure currentFigure) {

		expandGraphLabel.setTextT(getText());
		expandGraphLabel.setImage(getImage());
		expandGraphLabel.setFont(getFont());

		if (highlighted == HIGHLIGHT_ON) {
			expandGraphLabel.setForegroundColor(getForegroundColor());
			expandGraphLabel.setBackgroundColor(getHighlightColor());
		}
		// @tag ADJACENT : Removed highlight adjacent
		/*
		else if (highlighted == HIGHLIGHT_ADJACENT) {
			expandGraphLabel.setForegroundColor(getForegroundColor());
			expandGraphLabel.setBackgroundColor(getHighlightAdjacentColor());
		}
		*/
		else {
			expandGraphLabel.setForegroundColor(getForegroundColor());
			expandGraphLabel.setBackgroundColor(getBackgroundColor());
		}

		ContainerDimension containerDimension = computeContainerSize();

		expandGraphLabel.setSize(containerDimension.width, containerDimension.labelHeight);
		if (isExpanded) {
			//setSize(expandGraphLabel.getSize().width, expandGraphLabel.getSize().height + expandedHeight - SUBLAYER_OFFSET);
			setSize(containerDimension.width, containerDimension.expandedHeight);
		} else {
			setSize(containerDimension.width, containerDimension.labelHeight);
		}
		scrollPane.setLocation(new Point(expandGraphLabel.getLocation().x, expandGraphLabel.getLocation().y + containerDimension.labelHeight - SUBLAYER_OFFSET));
		//scrollPane.setLocation(new Point(0, labelHeight - SUBLAYER_OFFSET));
		//Rectangle bounds = expandGraphLabel.getBounds().getCopy();
		//Rectangle newBounds = new Rectangle(new Point(bounds.x, bounds.y + labelHeight - SUBLAYER_OFFSET), scrollPane.getSize());
		//figure.setConstraint(scrollPane, newBounds);
		/*
		size.width = labelWidth;
		if (scrollPane.getSize().height > 0) {
			size.height = labelHeight + scrollPane.getSize().height - SUBLAYER_OFFSET;
		} else {
			size.height = labelHeight;
		}
		refreshLocation();
		figure.getUpdateManager().performValidation();
		*/

	}

	protected void refreshLocation() {
		if (nodeFigure == null || nodeFigure.getParent() == null) {
			return; // node figure has not been created yet
		}
		GraphNode node = this;
		Point loc = node.getLocation();

		ContainerDimension containerDimension = computeContainerSize();
		Dimension size = new Dimension();

		expandGraphLabel.setSize(containerDimension.width, containerDimension.labelHeight);
		this.childAreaHeight = computeChildArea().height;
		if (isExpanded) {
			size.width = containerDimension.width;
			size.height = containerDimension.expandedHeight;
		} else {
			size.width = containerDimension.width;
			size.height = containerDimension.labelHeight;
		}
		Rectangle bounds = new Rectangle(loc, size);
		nodeFigure.getParent().setConstraint(nodeFigure, bounds);
		scrollPane.setLocation(new Point(expandGraphLabel.getLocation().x, expandGraphLabel.getLocation().y + containerDimension.labelHeight - SUBLAYER_OFFSET));
		scrollPane.setSize(computeChildArea());
		scalledLayer.setScale(computeWidthScale(), computeHeightScale());
	}

	void addConnectionFigure(PolylineConnection connection) {
		nodeFigure.add(connection);
		//zestLayer.addConnection(connection);
	}

	void addNode(GraphNode node) {
		zestLayer.addNode(node.getNodeFigure());
		this.childNodes.add(node);
		//container.add(node.getNodeFigure());
		//graph.registerItem(node);
	}

	void addNode(GraphContainer container) {
		// Containers cannot be added to other containers (yet)
	}

	public List getNodes() {
		return this.childNodes;
	}

	void paint() {
		Iterator iterator = getNodes().iterator();

		while (iterator.hasNext()) {
			GraphNode node = (GraphNode) iterator.next();
			node.paint();
		}
	}

}
