/* --------------------------------------------------------------------

SearchTreeCanvas class

Copyright (c) 1996 by Christopher R. Waterson. All Rights Reserved.
Permission to use, copy, modify, and distribute this software
and its documentation for NON-COMMERCIAL purposes and without
fee is hereby granted provided that this copyright notice
appears in all copies.

File:		SearchTreeCanvas.java
Synopsis:	A tree "Canvas" that provides UI
Author:		Christopher R. Waterson <waterson@eecs.umich.edu>
Created:	18 Feb 1996
Updated:	Christopher R. Waterson <waterson@eecs.umich.edu>
Last Modified:	31 Oct 1996

-------------------------------------------------------------------- */

package eightpuzzle;
import java.awt.*;


/**
 * Implements a canvas that paints a dynamically evolving search tree
 */

class SearchTreeCanvas
extends java.awt.Canvas {

	/**
	 * The root node in the search tree
	 */

	PuzzleNode m_root;


	// The coordinates of the origin
	int m_originX;
	int m_originY;


	/**
	 * The scaling factor to use when sizing the tree
	 */

	double m_scale;


	// The extent of the current search
	int m_xMin;
	int m_yMin;
	int m_xMax;
	int m_yMax;
	int m_width;
	int m_height;

	/**
	 * An image buffer used for double-buffering
	 */

	private Image m_imageBuffer;


	/**
	 * The image buffer's graphics context
	 */

	private Graphics m_imageBufferGraphics;



	private static final int POINT_SIZE = 1;



	/**
	 * Create a new SearchTreeCanvas.
	 *
	 * @param width		The preferred width for the canvas
	 * @param height	The preferred height for the canvas
	 */

	public SearchTreeCanvas() {
		m_scale = 8.0;
	}


	/**
	 * Return the preferred size of the canvas
	 */

	public Dimension
	preferredSize() {
		return new Dimension(100, 100);
	}


	/**
	 * Return the minimum size of the canvas
	 */

	public Dimension
	minimumSize() {
		return new Dimension(0, 0);
	}


	/**
	 * Sets the root of the search tree to the specified node.
	 *
	 * @param root	The search tree's root
	 */

	public void SetRoot(PuzzleNode root) {
		m_root = root;

		// To remove any old drawing
		m_imageBuffer = null;
		repaint();
	}


	/**
	 * Paints the search tree in the specified graphics
	 */

	public void paint(Graphics g) {

		Dimension d = size();

		// Has the size of the canvas changed? If so, do a FULL repaint
		if (m_width != d.width
				|| m_height != d.height
				|| m_imageBuffer == null) {
			// Reset the width and height variables
			m_width = d.width;
			m_height = d.height;

			if (m_imageBufferGraphics != null) m_imageBufferGraphics.dispose();

			// Create a new image buffer
			m_imageBuffer = createImage(d.width, d.height);
			m_imageBufferGraphics = m_imageBuffer.getGraphics();

			// Set it to the background color
			m_imageBufferGraphics.setColor(getBackground());
			m_imageBufferGraphics.fillRect(0, 0, d.width, d.height);

			// Recompute the origin to be the center of the canvas.
			m_originX = d.width / 2;
			m_originY = d.height / 2;

			m_xMin = m_xMax = m_originX;
			m_yMin = m_yMax = m_originY;

			if (m_root == null) return;

			synchronized (m_root) {
				// Recursively paint the entire tree into the image buffer
				PaintSubtree(m_imageBufferGraphics, m_root);
			}
		}

		// Draw the image
		g.drawImage(m_imageBuffer, 0, 0, getBackground(), this);
	}


	/**
	 * Update the canvas by simply repainting the image, if possible.
	 */

	public void update(Graphics g) {
		if (m_imageBuffer != null) {
			// Draw the image
			g.drawImage(m_imageBuffer, 0, 0, getBackground(), this);
		} else {
			// Otherwise, resort to the native update(), which clears
			// the canvas and calls paint()
			super.update(g);
		}
	}


	/**
	 * Recursively paints the search tree one subtree at a time.
	 *
	 * @param g		The Graphics context in which to paint
	 * @paran node	The subtree to paint.
	 */

	private void PaintSubtree(Graphics g,
				PuzzleNode node) {

		// Obviously don't paint anything if there is no node to paint!
		if (node == null) return;

		// Iterate through each child of the current node.
		for (int i = 0; i < node.getNumChildren(); ++i) {

			DrawArc(g, node.getChild(i));

			// And recursively paint the child
			PaintSubtree(g, node.getChild(i));
		}
	}


	/**
	 * Adds a single node to the search tree canvas.
	 *
	 * @param node	The node to add to the canvas.
	 */

	public void
	Add(PuzzleNode node) {
		synchronized (m_root) {
			if (m_imageBuffer != null) {
				// Draw the line.
				Rectangle clipRect = DrawArc(m_imageBufferGraphics, node);

				// ...and repaint only in the clip area
				repaint(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
			} else {
				// Just paint the whole tree from scratch
				repaint();
			}
		}
	}


	/**
	 * Draw the specified arc on the search tree.
	 *
	 * @param g		The Graphics context into which to paint the arc
	 * @param node	The node of the search tree to draw
	 * @return	The clipping rectangle for repaint's use
	 */

	private Rectangle
	DrawArc(Graphics g, PuzzleNode node) {

		// Scale the (x, y) coordinates of the nodes to the canvas
		int cx = m_originX + (int)(node.getX() * m_scale);
		int cy = m_originY + (int)(node.getY() * m_scale);

		// Update the boundaries if the child extends past the current max/min
		boolean updateScrollbars = false;
		if (cx < m_xMin) { m_xMin = cx; updateScrollbars = true; }
		if (cx > m_xMax) { m_xMax = cx; updateScrollbars = true; }
		if (cy < m_yMin) { m_yMin = cy; updateScrollbars = true; }
		if (cy > m_yMax) { m_yMax = cy; updateScrollbars = true; }


		// If the node has no parent, then it'll be painted from the origin,
		// otherwise, it'll be painted from the parent's (x, y) coordinates.

		int px, py;

		if (node.getParent() == null) {
			px = m_originX;
			py = m_originY;
		} else {
			px = m_originX + (int)(node.getParent().getX() * m_scale);
			py = m_originY + (int)(node.getParent().getY() * m_scale);
		}

		// Draw a line for the node
		g.setColor(Color.gray);
		g.drawLine(px, py, cx, cy);

		// Draw a point at the parent end of the line
		int dx = (2 * POINT_SIZE) + 1;
		int dy = (2 * POINT_SIZE) + 1;

		g.setColor(Color.green);
		g.fillRect(px - POINT_SIZE, py - POINT_SIZE, dx, dy);

		// Compute the clipping rectangle
		int left, right, top, bottom;

		if (px < cx) {
			left  = px - POINT_SIZE;
			right = cx + POINT_SIZE;
		} else {
			right = px + POINT_SIZE;
			left  = cx - POINT_SIZE;
		}

		if (py < cy) {
			top    = py - POINT_SIZE;
			bottom = cy + POINT_SIZE;
		} else {
			bottom = py + POINT_SIZE;
			top    = cy - POINT_SIZE;
		}

		// Do a clipped repaint
		return new Rectangle(left, top, right - left, bottom - top);
	}


	/**
	 * Zoom in or out on the search tree. Forces a repaint in the window.
	 *
	 * @param scale		The scaling factor to use; for example, 2.0 to zoom
	 *					in by 2x, 0.5 to zoom out by 2x.
	 */

	public void Zoom(double scale) {
		m_scale *= scale;

		// To force a full re-paint
		m_imageBuffer = null;

		repaint();
	}



	/**
	 * Set the origin to the specified coordinates
	 */

	public void
	SetOrigin(int x, int y) {
		m_originX = x;
		m_originY = y;

		// To force a full re-paint
		m_imageBuffer = null;

		repaint();
	}
}




