/*
 * @(#)NodeAndLinkAnimatingJPanel.java
 *
 * Last Modified: 9/15/01
 */
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import java.beans.*;

/**
 *
 * <p>
 * @author  Corey Sanders
 * @version 1.4 9/15/01
 */
public class NodeAndLinkAnimatingJPanel extends DrawingJPanel implements ActionListener{


	/**
	 * Node kept for the animating current Panel.
	 */
	private DrawingTree animatingNode;

	/**
	 * Refers to the list of AffineTransforms used to move the animating node.
	 */
	private AffineTransformList movingTransforms;

	/**
	 * Refers to the currentLocation of the animation.
	 */
	private double currentLocation = 0;

	/**
	 * Refers to the currentStep of the animation.
	 */
	private double currentStep = .05;


	/**
	 * Timer for the animation.
	 */
	javax.swing.Timer animationTimer;

	/**
	 * The delay rate for the animation, set to DEFAULT_DELAY.
	 */
	private int delayRate;

	/**
	 * The default delay for animation (90).
	 */
	public static final int DEFAULT_DELAY = 90;

	/**
	 * Boolean flag whether the panel is drawingLines or not.
	 */
	private boolean drawingLines;

	/**
	 * The Paint for the left line.
	 */
	Paint leftLinePaint;
	/**
	 * The Paint for the right line.
	 */
	Paint rightLinePaint;


	/**
	 * Sole Constructor for the JPanel that paints the node using the AnimatingTree drawNode method.
	 * The JPanel only makes a new image on a resize and otherwise, double-buffers the graphics by
	 * leaving the image unmodified.
	 */
	public NodeAndLinkAnimatingJPanel() {
		super();

		// Set delay rate
		setDelayRate(DEFAULT_DELAY);

		// Initiates timer
		setAnimationTimer(new javax.swing.Timer(delayRate, this));

		movingTransforms = new AffineTransformList();

		// Set transforms list
		AffineTransform currentTransform = new AffineTransform();

		// Make initial moving transforms list.
		movingTransforms.add(currentTransform);
		movingTransforms.add(currentTransform);
		movingTransforms.add(currentTransform);
		movingTransforms.add(currentTransform);
		movingTransforms.add(currentTransform);
		movingTransforms.add(currentTransform);
		movingTransforms.add(currentTransform);
		movingTransforms.add(currentTransform);
		movingTransforms.add(currentTransform);


		getAnimationTimer().start();

    }

	/**
 	 ********************
	 * Accesssor methods*
	 ********************
	 */

	/**
	 * Gets whether the animation is drawing lines or a node. If the panel is drawing
	 * lines, returns true.
	 *
	 * @return true if the panel is drawing lines.
	 */
	public boolean isDrawingLines() {
		return drawingLines;
	}


    /**
     * Gets the head of the tree currently drawn in the Panel.
     *
     * @return TreeHead the tree head.
     */
	public DrawingTree getAnimatingNode() {
		return animatingNode;
	}


	/**
	 * Gets the animation timer for the animation of the panel.
	 *
	 * @return javax.swing.Timer defining the steps of animation.
	 */
	protected javax.swing.Timer getAnimationTimer() {
		return animationTimer;
	}

	/**
	 * Gets the paint for the right line drawing.
	 *
	 * @return Paint right Line Paint.
	 */
	public Paint getRightLinePaint() {
		return rightLinePaint;
	}

	/**
	 * Gets the paint for the left line drawing.
	 *
	 * @return Paint left Line Paint.
	 */
	public Paint getLeftLinePaint() {
		return leftLinePaint;
	}

	/**
 	 *******************
	 * Mutator methods *
	 *******************
	 */

	/**
	 * Sets whether the animation is drawing lines or a node. If the panel is drawing
	 * lines, sets true.
	 *
	 * @param drawingLines true if the panel is drawing lines.
	 */
	public void setDrawingLines(boolean drawingLines) {
		this.drawingLines = drawingLines;
	}

	/**
	 * Sets the paint for the right line drawing.
	 *
	 * @param rightLinePaint right Line Paint.
	 */
	public void setRightLinePaint(Paint rightLinePaint) {
		this.rightLinePaint = rightLinePaint;
	}

	/**
	 * Sets the paint for the left line drawing.
	 *
	 * @param leftLinePaint left Line Paint.
	 */
	public void setLeftLinePaint(Paint leftLinePaint) {
		this.leftLinePaint = leftLinePaint;
	}


	/**
	 * Sets the delay rate for the timer for the animation.
	 *
	 * param t int delay rate.
	 */
    public void setDelayRate(int t) {
		delayRate = (int)((t*-2) + 200);
		if (getAnimationTimer() != null) {
			getAnimationTimer().setDelay(delayRate);
		}
	}

	/**
	 * Sets the animation timer for the animation of the panel.
	 *
	 * @param animationTimer javax.swing.Timer defining the steps of animation.
	 */
	protected void setAnimationTimer(javax.swing.Timer animationTimer) {
		this.animationTimer = animationTimer;
	}



	/**
	 * Set the node for drawing. This method should be called for each change in node.
	 * @param node node used for drawing within the panel.
	 */
    public void setAnimatingNode(DrawingTree animatingNode) {
		this.animatingNode = animatingNode;
		setDrawTree(true);
		repaint();
	}

	/**
	 * Sets whether the tree is shown or not.
	 *
	 * @param componentShown boolean flag as to whether the tree is shown.
	 */
	public void setComponentShown(boolean componentShown) {
		super.setComponentShown(componentShown);

		if (componentShown) {
			getAnimationTimer().start();
		}
		else {
			getAnimationTimer().stop();
		}

	}




	/**
 	 *******************
	 * Drawing methods *
	 *******************
	 */

	/**
	 * Makes the drawing graphics. Uses the image made from makeDrawTreeImage. This is overiden,
	 * to remake the transforms.
	 */
	 protected void makeDrawTreeGraphics() {
		 super.makeDrawTreeGraphics();

		// Correct scaling
		AffineTransform scaleTransform = AffineTransform.getScaleInstance((getDrawTreeGraphics().getClipBounds().getWidth() / (2.0) ), (getDrawTreeGraphics().getClipBounds().getHeight() / (2.0)) );

		AffineTransform scaleSmallTransform = ((AffineTransform)scaleTransform.clone());
		scaleSmallTransform.scale(.4, .4);

		AffineTransform scaleLargeTransform = ((AffineTransform)scaleTransform.clone());
		scaleLargeTransform.scale(2.5, 2.5);

		AffineTransform translateBottomTransform = AffineTransform.getTranslateInstance(getDrawTreeGraphics().getClipBounds().getX() + getDrawTreeGraphics().getClipBounds().getWidth()/2.0 - scaleTransform.getScaleX()/2.0, getDrawTreeGraphics().getClipBounds().getY() + getDrawTreeGraphics().getClipBounds().getHeight() - scaleTransform.getScaleY());
		AffineTransform translateLeftTransform = AffineTransform.getTranslateInstance(getDrawTreeGraphics().getClipBounds().getX(), getDrawTreeGraphics().getClipBounds().getY() + getDrawTreeGraphics().getClipBounds().getHeight()/2.0 - scaleTransform.getScaleY()/2.0);
		AffineTransform translateTopTransform = AffineTransform.getTranslateInstance(getDrawTreeGraphics().getClipBounds().getX() + getDrawTreeGraphics().getClipBounds().getWidth()/2.0 - scaleTransform.getScaleX()/2.0, getDrawTreeGraphics().getClipBounds().getY());
		AffineTransform translateRightTransform = AffineTransform.getTranslateInstance(getDrawTreeGraphics().getClipBounds().getX() + getDrawTreeGraphics().getClipBounds().getWidth() - scaleTransform.getScaleX(), getDrawTreeGraphics().getClipBounds().getY() + getDrawTreeGraphics().getClipBounds().getHeight()/2.0 - scaleTransform.getScaleY()/2.0);
		AffineTransform translateCenterTransform = AffineTransform.getTranslateInstance(getDrawTreeGraphics().getClipBounds().getX() + getDrawTreeGraphics().getClipBounds().getWidth()/2.0 - scaleTransform.getScaleX()/2.0, getDrawTreeGraphics().getClipBounds().getY() + getDrawTreeGraphics().getClipBounds().getHeight()/2.0 - scaleTransform.getScaleY()/2.0);
		AffineTransform translateSmallTransform = AffineTransform.getTranslateInstance(getDrawTreeGraphics().getClipBounds().getX() + getDrawTreeGraphics().getClipBounds().getWidth()/2.0 - scaleSmallTransform.getScaleX()/2.0, getDrawTreeGraphics().getClipBounds().getY() + getDrawTreeGraphics().getClipBounds().getHeight()/2.0 - scaleSmallTransform.getScaleY()/2.0);
		AffineTransform translateLargeTransform = AffineTransform.getTranslateInstance(getDrawTreeGraphics().getClipBounds().getX() + getDrawTreeGraphics().getClipBounds().getWidth()/2.0 - scaleLargeTransform.getScaleX()/2.0, getDrawTreeGraphics().getClipBounds().getY() + getDrawTreeGraphics().getClipBounds().getHeight()/2.0 - scaleLargeTransform.getScaleY()/2.0);



		translateBottomTransform.concatenate(scaleTransform);
		translateLeftTransform.concatenate(scaleTransform);
		translateTopTransform.concatenate(scaleTransform);
		translateRightTransform.concatenate(scaleTransform);
		translateCenterTransform.concatenate(scaleTransform);
		translateSmallTransform.concatenate(scaleSmallTransform);
		translateLargeTransform.concatenate(scaleLargeTransform);


		// Make initial moving transforms list.
		movingTransforms.set(0, translateBottomTransform);
		movingTransforms.set(1, translateLeftTransform);
		movingTransforms.set(2, translateTopTransform);
		movingTransforms.set(3, translateRightTransform);
		movingTransforms.set(4, translateBottomTransform);

		movingTransforms.set(5, translateCenterTransform);
		movingTransforms.set(6, translateCenterTransform);
		movingTransforms.set(7, translateSmallTransform);
		movingTransforms.set(8, translateLargeTransform);

	 }



	/**
	 * Method actually called to complete the drawing of the panel. The node is animated to fill the
	 * entire graphics given within the panel.
	 *
	 * @param g2 Graphics2D to animate the node within.
	 */
	protected void animate(Graphics2D g2) {
		if (getAnimatingNode() != null) {

			currentLocation += currentStep;

			if (currentLocation > 8) {
				currentStep = -.05;
				currentLocation += currentStep;
			}

			if (currentLocation < 0) {
				currentStep = .05;
				currentLocation += currentStep;
			}


			AffineTransform currentTransform = movingTransforms.getTransformStep(currentLocation);


			// Right line
			Line2D.Double right = new Line2D.Double( (currentTransform.getTranslateX() + (3*currentTransform.getScaleX())/4.0) , (currentTransform.getTranslateY() + currentTransform.getScaleY()/2.0) , g2.getClipBounds().getX() + g2.getClipBounds().getWidth(), g2.getClipBounds().getY() + g2.getClipBounds().getHeight());

			g2.setComposite(getAnimatingNode().getSettings().getRightLinkComposite());
			g2.setStroke(getAnimatingNode().getSettings().getRightLinkStroke());
			g2.setPaint(getAnimatingNode().getSettings().getRightLinkPaint());

			g2.draw(right);


			// Draw the left and right links
			Line2D.Double left = new Line2D.Double( (currentTransform.getTranslateX() + currentTransform.getScaleX()/4.0) , (currentTransform.getTranslateY() + currentTransform.getScaleY()/2.0) , g2.getClipBounds().getX(), g2.getClipBounds().getY() + g2.getClipBounds().getHeight());

			// Set graphics information
			g2.setComposite(getAnimatingNode().getSettings().getLeftLinkComposite());
			g2.setStroke(getAnimatingNode().getSettings().getLeftLinkStroke());
			g2.setPaint(getAnimatingNode().getSettings().getLeftLinkPaint());

			g2.draw(left);



			getAnimatingNode().drawNode(g2, currentTransform);

			if (isDrawingLines()) {
				drawLines(g2);
			}


		}
	}

	/**
	 * Draws the lines in the given panel using the Graphics2D.
	 *
	 * @param g2 Graphics2D to draw the lines within.
	 */
	public void drawLines(Graphics2D g2) {

		double distance = (currentLocation/4.0);

		BasicStroke line = new BasicStroke(5.0f);
		g2.setStroke(line);

		if (distance < 1) {

			// Makes the left dotted line
			Line2D.Double crossLeft = new Line2D.Double(
		    	(g2.getClipBounds().getX()) ,
		 		(g2.getClipBounds().getY()) ,
				(g2.getClipBounds().getX() + (distance) * g2.getClipBounds().getWidth()) ,
				(g2.getClipBounds().getY() + (distance) * g2.getClipBounds().getHeight())
			);


			g2.setPaint(getLeftLinePaint());
			g2.draw(crossLeft);
		}
		else {

			distance -= 1;

			// Makes the left dotted line
			Line2D.Double crossLeft = new Line2D.Double(
		    	(g2.getClipBounds().getX()) ,
		 		(g2.getClipBounds().getY()) ,
				(g2.getClipBounds().getX() + g2.getClipBounds().getWidth()) ,
				(g2.getClipBounds().getY() + g2.getClipBounds().getHeight())
			);


			g2.setPaint(getLeftLinePaint());
			g2.draw(crossLeft);

			// Makes the right dotted line
			Line2D.Double crossRight = new Line2D.Double(
				(g2.getClipBounds().getX() + g2.getClipBounds().getWidth()) ,
		 		(g2.getClipBounds().getY()) ,
				(g2.getClipBounds().getX() + (1 - distance) * (g2.getClipBounds().getWidth())) ,
				(g2.getClipBounds().getY() + (distance) * g2.getClipBounds().getHeight())
			);

			g2.setPaint(getRightLinePaint());
			g2.draw(crossRight);
		}
	}

	/**
	 * Overides paintComponenet and is called whenever the Panel needs to be painted. The
	 * painting includes painting the image and then painting the animation (if any).
	 *
	 * @param g Graphics to which the component is drawn.
	 */
	public void paintComponent(Graphics g) {



		// Reset Graphics
		super.paintComponent(g);


		Graphics2D g2 = (Graphics2D)g;


		// Rendering hints.
		(g2).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		animate(g2);
	}


	/************************************************/
	/* Implements Action Listener              		*/
	/************************************************/

	/**
	 * Listens to action events.
	 *
	 * @param e ActionEvent that contains information about the tree.
	 */
	public void actionPerformed(ActionEvent e) {
		repaint();
	}



}

