package prolog.treeview;

import java.io.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;

/**
 * implements a tree class able to layout and paint
 * a amount of nodes. the tree must me contained in
 * a container
 */
public class Tree
{
	/**
	 * the root node of this tree
	 */
	protected RootNode root = null;

	/**
	 * the position this tree has in the containing container
	 */
	protected Rectangle rect = new Rectangle( 0, 0, 0, 0 );

	/**
	 * the container containing this tree
	 */
	protected JComponent container = null;

	/**
	 * constructor
	 */
	public Tree()
	{
	}

	/**
	 * sets the position the tree has in the container
	 */
	public void setPosition( Rectangle r )
	{
		this.rect = r;
	}

	/**
	 * returns the position the tree has in container
	 */
	public Rectangle getPosition()
	{
		return this.rect;
	}

	/**
	 * sets the container which contains this tree
	 */
	public void setContainer( JComponent c )
	{
		this.container = c;
	}

	/**
	 * returns the container which contains this tree
	 */
	public JComponent getContainer()
	{
		return this.container;
	}

	/**
	 * invokes the containers repaint method
	 */
	public void repaint()
	{
		if( this.container != null )
			this.container.repaint();
	}

	/**
	 * sets the root of this tree
	 */
	public void setRoot( RootNode node )
	{
		if( node != null )
		{
			node.setParent( null );
			node.setTree( this );
		}

		this.root = node;

		if( this.root != null )
			doLayout();

		repaint();
	}

	/**
	 * returns the root of this tree
	 * @return the root node of this tree
	 */
	public RootNode getRoot()
	{
		return this.root;
	}

	/**
	 * paints all nodes
	 * @param g - the graphics context to paint in
	 */
	protected void paintNodes( Graphics2D g )
	{
		if( this.root != null )
			this.root.paint( g );
	}

	/**
	 * paints all lines between the nodes
	 * @param g - the graphics context to paint in
	 */
	public void paintLines( Graphics2D g )
	{
		if( this.root != null )
			this.root.paintLine( g );
	}

	/**
	 * paints this tree
	 * @param g - the graphics context to paint in
	 */
	public void paint( Graphics g )
	{
		g.setColor( Color.white );
		g.fillRect( rect.x, rect.y, rect.width, rect.height );
		this.paintLines( (Graphics2D)g );
		this.paintNodes( (Graphics2D)g );
	}

	/**
	 * layouts this tree
	 */
	public void doLayout()
	{
		if( root != null )
		{
			root.recursiveDimensionUpdate();
			doLayout(
				root,
				rect.x + rect.width/ 2,
				rect.y + 10 );
		}
	}

	/**
	 * layouts the tree and invokes repaint
	 */
	public void updateTree()
	{
		doLayout();
		repaint();
	}

	/**
	 * the recursive layout method
	 * @param n - the current node
	 * @param x - the middle of the new node
	 * @param y - the new y
	 */
	protected void doLayout( Node n, int x, int y )
	{
		if( n != null )
		{
			// update this node
			n.getRect().x = x - (n.getRect().width / 2);
			n.getRect().y = y;

			// search for max y in childs
			int maxY = 0;
			int maxX = 0;
			Iterator i=n.getChilds();
			while( i.hasNext() )
			{
				Node child = (Node)i.next();
				if( child.getRect().height > maxY )
					maxY = child.getRect().height;
				maxX += getRecursiveWidth( child );
				if( i.hasNext() )
					maxX += 5;
			}

			// layout da childs dood (please use durex next time ;-O )
			i=n.getChilds();
			int currentX = x - maxX/2;
			while( i.hasNext() )
			{
				Node child = (Node)i.next();

				// do X
				child.getRect().x = currentX;

				// do Y
				child.getRect().y = y;

				// recursive descent
				int curWidth = getRecursiveWidth( child );
				doLayout(
					child,
					currentX + (curWidth/2),
					y + n.getRect().height + 20 );

				// increment X
				currentX += curWidth;
				if( i.hasNext() )
					currentX += 5;
			}
		}
	}

	/**
	 * returns the width of the childs of this node
	 */
	protected int getChildsWidth( Node n )
	{
		int width = 0;
		Iterator i=n.getChilds();
		while( i.hasNext() )
		{
			width += ((Node)i.next()).getDimension().width;
			if( i.hasNext() )
				width += 5;
		}
		return width;
	}

	/**
	 * returns recusive the total width of the
	 * given node and all of its child nodes
	 */
	protected int getRecursiveWidth( Node n )
	{
		int back = n.getRect().width;

		int childsWidth = 0;
		Iterator i=n.getChilds();
		while( i.hasNext() )
		{
			childsWidth += getRecursiveWidth( ((Node)i.next()) );
			if( i.hasNext() )
				childsWidth += 5;
		}
		if( childsWidth > back )
			back = childsWidth;

		return back;
	}

	/**
	 * returns recusive the total width of the
	 * given node and all of its child nodes
	 */
	protected int getRecursiveWidth( Node n, int layer )
	{
		int back = 0;

		if( layer != 0 )
			back = n.getRect().width;

		int childsWidth = 0;
		Iterator i=n.getChilds();
		while( i.hasNext() )
		{
			childsWidth += getRecursiveWidth( ((Node)i.next()), layer+1 );
			if( i.hasNext() )
				childsWidth += 5;
		}
		if( childsWidth > back )
			back = childsWidth;

		return back;
	}

	/**
	 * generates xml representing this object
	 * @param out - the writer where to append the xml
	 * @throws IOException
	 */
	public void genXml( Writer out )
		throws IOException
	{
		root.genXml( out );
	}

	/**
	 * saves the tree as xml
	 * @param filename - the filename to save the file
	 */
	public boolean exportXml( String filename )
	{
		boolean back = true;
		try
		{
			FileWriter out = new FileWriter( filename );
			genXml( out );
			out.close();
		}
		catch( Exception ae )
		{
			back = false;
			ae.printStackTrace();
		}
		return back;
	}

	/**
	 * returns the preferred size of this tree
	 */
	public Dimension getPreferredSize()
	{
		Dimension back = new Dimension( 0, 0 );
		if( this.root != null )
		{
			back =
				new Dimension(
					this.root.getMaximalWidth() + 20,
					this.root.getMaximalHeight() + 20 );
		}
		return back;
	}
}
