/*
 * @(#)InsertRedBlackAnimation.java
 *
 * Last Modified: 9/15/01
 */

 import java.util.*;
 import java.awt.*;
 import java.awt.geom.*;
  import java.text.*;

/** *
 	* The Animation object that defines the Insertion into a Red-Black Tree. Two constructors exist,
 	* one setting the animator and animation color Schemes. <p>
	*
	* @author  Corey Sanders
	* @version 1.2 9/15/01
 	*/

public class InsertRedBlackAnimation extends InsertBSTAnimation implements AnimationListener {

	/**
	 * The location for the rotations to begin.
	 */
	 private double WAITING_ROTATIONS;

	/**
	 * The location for beginning of checking rotations.
	 */
	 protected static final int BEGIN_CHECK = 0;

	/**
	 * The location for the left checking of rotations.
	 */
	 protected static final int LEFT_CHECK = 1;

	 /**
	 * The location for the right checking of rotations.
	 */
	 protected static final int RIGHT_CHECK = 2;

	/**
	 * The location for the rotations to check.
	 */
	 private int rotationCheck;



	/**
	 * Holds the links that must be changed to red.
	 */
	LinkedList redChangeLinks = new LinkedList();
	/**
	 * Holds the links that must be changed to black.
	 */
	LinkedList blackChangeLinks = new LinkedList();


	/**
	 * The location for the rotations to begin.
	 */
	 private int ROTATIONS;

	/**
	 * Refers to the current Rotation being drawn.
	 */
	 RotationBSTAnimation currentRotation = null;

	/**
	 * The current index of the search through the tree.
	 */
	private int currentIndexSearch;


	/**
	 * The constructor which initiates the status and prepares the colorSchemes. The node
	 * which is animating must be passed. The first step added is the identity step.
	 *
	 * @param node the BSTTree which is animated during the animation.
	 * @param AnimationSchemeLeft the <code>NodeSettings</code> associated with a color scheme according to NodeSettings for the left Animation.
	 * @param AnimationSchemeRight the <code>NodeSettings</code> associated with a color scheme according to NodeSettings for the right Animation.
	 * @param AnimatorScheme the <code>NodeSettings</code> associated with a color scheme according to NodeSettings.
	 * @param KeyAnimatorScheme the <code>KeySettings</code> associated with a color scheme according to KeySettings.
	 * @param startingCmd the Animation command that this should start.
	 * @param stepTime the time for each step of the Animation. Sets the initial value.
	 */
	public InsertRedBlackAnimation(BSTTree node, NodeSettings AnimationSchemeLeft, NodeSettings AnimationSchemeRight, NodeSettings AnimatorScheme, KeySettings KeyAnimatorScheme, String startingCmd, int stepTime) {
		super(node, AnimationSchemeLeft, AnimationSchemeRight, AnimatorScheme, KeyAnimatorScheme, startingCmd, stepTime);

	}

	/**
	 * The constructor which initiates the status and sets the colorSchemes to null. No colors
	 * will be change using this animation. The node
	 * which is animating must be passed. The first step added is the identity step.
	 *
	 * @param node the BSTTree which is animated during the animation.
	 */
	public InsertRedBlackAnimation(BSTTree node) {
		this(node, null, null, null , null , Animation.PLAY, DEFAULT_STEP);
	}



	/*********************/
	/* Animation methods */
	/*********************/

	/**
	 * Draws the animation of the next step, using the status of the animation (Animation.PLAY, Animation.PAUSE and so forth).
	 * After completing the drawing, the Animation sends an AnimationEvent to all its listeners, indicating
	 * any information that the listerners may wish to use.
	 *
	 * @param g2 the graphics to which the animation step should be drawn.
	 * @param startingStatus the status used as the starting command of animation, if needed.
	 */
	public void drawAnimation(Graphics2D g2, String startingStatus) {

		setStartingCommand(startingStatus);

		// FINISH status
		if (getStatus().equals(Animation.FINISH)) {

			if (getCurrentLocation() < WAITING_ROTATIONS) {

				int size = getNodeMovements().size();

				for(int i = (int)Math.floor(getCurrentLocation()); i < size; i++) {
					RedBlackTree currentNode = ((RedBlackTree)getNodeMovements().get(i));

					if (currentNode != null) {
						// Fixes 4-nodes
						if (((RedBlackTree)currentNode.getLeftTree()).isRedLink() && ((RedBlackTree)currentNode.getRightTree()).isRedLink()) {
							currentNode.setRedLink(true);
							((RedBlackTree)currentNode.getLeftTree()).setRedLink(false);
							((RedBlackTree)currentNode.getRightTree()).setRedLink(false);

						}
					}

				}


				restore();

				setCurrentLocation(ROTATIONS);

				currentRotation = null;

				currentIndexSearch = (getNodeMovements().size()-1);

				rotationCheck = BEGIN_CHECK;

				messageAction(Animation.REDRAW);

				setBlackChangeLinks();
				setRedChangeLinks();

			}

			if (currentRotation != null) {
				currentRotation.setStatus(Animation.FINISH);
				currentRotation.drawAnimation(g2, Animation.FINISH);
				setBlackChangeLinks();
				setRedChangeLinks();
			}

			while(true) {
				for( ; currentIndexSearch>=0; currentIndexSearch--) {
					if (getNodeMovements().get(currentIndexSearch) != null) {
						makeCurrentRotation((RedBlackTree)getInsertNode(), (RedBlackTree)getNodeMovements().get(currentIndexSearch));
					}
				}

				animationAction();
				return;

			}
		}

		// BEGIN status
		if (getStatus().equals(Animation.BEGIN)) {
			setCurrentLocation(0);
			setPreviousLocation(0);

			//animator scheme exists
			getInsertNode().saveSettings();
			getInsertNode().setNodeSettings(getAnimatorScheme());

			((DrawingKey)getInsertNode().getValue()).saveSettings();
			((DrawingKey)getInsertNode().getValue()).setKeySettings(getKeyAnimatorScheme());


			animationAction();

			messageAction(Animation.BEGIN + " Insertion of "+getInsertNode().getKey().toString());

			// set starting status
			setStatus(getStartingCommand());

			WAITING_ROTATIONS = getMovements().size();
			ROTATIONS = getMovements().size() + 1;
			return;

		}
		// Currently on a step and no changes have occured. Return to starting command
		if (getStatus().equals(Animation.STEP)) {
			setStatus(getStartingCommand());
		}

		// PLAY status
		if (getStatus().equals(Animation.PLAY)) {

			messageAction(Animation.PLAY);

			setPreviousLocation(getCurrentLocation());


			if (getCurrentLocation() < WAITING_ROTATIONS) {

				if(getStep()) { // Skip middle animation steps.
					setCurrentLocation(Math.ceil(getCurrentLocation()) + getStepSize());
				}
				else { // Normal step
					setCurrentLocation(getCurrentLocation() + getStepSize());
				}
				// Set node positions.
				nextNodeIndex = (int)Math.ceil(getCurrentLocation());
				previousNodeIndex = (int)Math.floor(getCurrentLocation());

				// Completed Animation
				if (getCurrentLocation() >= (getMovements().size()-1)) {
					setStatus(Animation.STEP);

					restore();

					messageAction(Animation.REDRAW);

					currentIndexSearch = (getNodeMovements().size()-1);

					setCurrentLocation(WAITING_ROTATIONS);

					rotationCheck = BEGIN_CHECK;

				}
				else {
					// Finished a step in the Animation.
					if (Math.ceil(getPreviousLocation()) == Math.floor(getCurrentLocation()) && getPreviousLocation() != 0) {

						setStatus(Animation.STEP);

						comparisonCount++;

						RedBlackTree previousNode = ((RedBlackTree)getNodeMovements().get(previousNodeIndex));

						if (previousNode != null) {
							// Fixes 4-nodes
							if (((RedBlackTree)previousNode.getLeftTree()).isRedLink() && ((RedBlackTree)previousNode.getRightTree()).isRedLink()) {
								previousNode.setRedLink(true);
								((RedBlackTree)previousNode.getLeftTree()).setRedLink(false);
								((RedBlackTree)previousNode.getRightTree()).setRedLink(false);
								messageAction(Animation.REDRAW);
								messageAction("Four-node split, by setting the parent red bit.");
							}
						}
					}
					// Set movements
					getMovements().set(previousNodeIndex, resetPosition(previousNodeIndex));
					getMovements().set(nextNodeIndex, resetPosition(nextNodeIndex));

					// Draw just the node without links.
					getInsertNode().drawNode(g2, getMovements().getTransformStep(getCurrentLocation()));

				}

			}
			if (getCurrentLocation() < ROTATIONS && getCurrentLocation() >= WAITING_ROTATIONS) {
				if(getStep()) { // Skip middle animation steps.
					setCurrentLocation(ROTATIONS);
				}
				else { // Normal step
					setCurrentLocation(getCurrentLocation() + getStepSize());
				}

			}
			if (getCurrentLocation() >= ROTATIONS) {

				if (getPreviousLocation() < ROTATIONS) {

					for( ; currentIndexSearch>=0; currentIndexSearch--) {
						if (getNodeMovements().get(currentIndexSearch) != null) {
							makeCurrentRotation((RedBlackTree)getInsertNode(), (RedBlackTree)getNodeMovements().get(currentIndexSearch));
							if (currentRotation != null)
								break;
						}
					}

					if (currentIndexSearch < 0) {
						setStatus(Animation.FINISH);
					}
					else {
						// Draw rotation animation
						currentRotation.drawAnimation(g2, Animation.PLAY);

						messageAction(Animation.REDRAW);

						currentRotation.addAnimationListener(this);

						if (rotationCheck == BEGIN_CHECK) {
							currentIndexSearch--;
						}



					}
				}
				else {


					// Set values for animation
					currentRotation.setStepTime(getStepTime());
					currentRotation.setStep(getStep());

					// Set play status
					currentRotation.setStatus(Animation.PLAY);

					// Draw rotation animation
					currentRotation.drawAnimation(g2, Animation.PLAY);



					if (currentRotation.getStatus().equals(Animation.FINISH)) {
						setBlackChangeLinks();
						setRedChangeLinks();


						messageAction(Animation.REDRAW);

						setCurrentLocation(WAITING_ROTATIONS);

						currentRotation.removeAnimationListener(this);
						currentRotation = null;

					}
				}
			}
		}

		// REWIND status
		if (getStatus().equals(Animation.REWIND)) {
			messageAction(Animation.REWIND);

			setPreviousLocation(getCurrentLocation());


			if (getCurrentLocation() < WAITING_ROTATIONS) {

				if(getStep()) { // Skip middle animation steps.
					setCurrentLocation(Math.floor(getCurrentLocation()) - getStepSize());
				}
				else { // Normal step
					setCurrentLocation(getCurrentLocation() - getStepSize());
				}
				// Set node positions.
				nextNodeIndex = (int)Math.ceil(getCurrentLocation());
				previousNodeIndex = (int)Math.floor(getCurrentLocation());

				// Beginning of Animation
				if (getCurrentLocation() <= 0) {
					setStatus(Animation.PAUSE);
					setCurrentLocation(0);
					animationAction();
					return;
				}

				// Finished a step in the Animation.
				if (Math.ceil(getPreviousLocation()) == Math.floor(getCurrentLocation()) && getPreviousLocation() != 0) {

					setStatus(Animation.STEP);

					comparisonCount--;

				}

				// Set movements
				getMovements().set(previousNodeIndex, resetPosition(previousNodeIndex));
				getMovements().set(nextNodeIndex, resetPosition(nextNodeIndex));

				// Draw just the node without links.
				getInsertNode().drawNode(g2, getMovements().getTransformStep(getCurrentLocation()));


			}
			if (getCurrentLocation() < ROTATIONS && getCurrentLocation() >= WAITING_ROTATIONS) {
				messageAction("Cannot Rewind : Beginning of Rotation");
				setCurrentLocation(getPreviousLocation());
				// Pause status
				setStatus(Animation.PAUSE);
			}
			if (getCurrentLocation() >= ROTATIONS) {

				// Set values for animation
				currentRotation.setStepTime(getStepTime());
				currentRotation.setStep(getStep());

				// Set play status
				currentRotation.setStatus(Animation.REWIND);

				// Draw rotation animation
				currentRotation.drawAnimation(g2, Animation.REWIND);



				if (currentRotation.getStatus().equals(Animation.PAUSE)) {
					messageAction("Cannot Rewind : Beginning of Rotation");
					// Pause status
					setStatus(Animation.PAUSE);
				}

			}
		}


		// PAUSE status
		if (getStatus().equals(Animation.PAUSE)) {

			if (getCurrentLocation() < WAITING_ROTATIONS) {
				nextNodeIndex = (int)Math.ceil(getCurrentLocation());
				previousNodeIndex = (int)Math.floor(getCurrentLocation());
				// Set movements
				getMovements().set(previousNodeIndex, resetPosition(previousNodeIndex));
				getMovements().set(nextNodeIndex, resetPosition(nextNodeIndex));

				// Draw just the node without links.
				getInsertNode().drawNode(g2, getMovements().getTransformStep(getCurrentLocation()));


			}
			if (getCurrentLocation() < ROTATIONS && getCurrentLocation() >= WAITING_ROTATIONS) {

			}
			if (getCurrentLocation() >= ROTATIONS) {

				// Set play status
				currentRotation.setStatus(Animation.PAUSE);

				// Draw rotation animation
				currentRotation.drawAnimation(g2, Animation.PAUSE);

			}

			messageAction(Animation.PAUSE);

		}

		// STOP status
		if (getStatus().equals(Animation.STOP)) {

			if (getCurrentLocation() < WAITING_ROTATIONS) {


			}
			if (getCurrentLocation() < ROTATIONS && getCurrentLocation() >= WAITING_ROTATIONS) {

			}
			if (getCurrentLocation() >= ROTATIONS) {

				// Set play status
				currentRotation.setStatus(Animation.STOP);

				// Draw rotation animation
				currentRotation.drawAnimation(g2, Animation.STOP);

			}

			messageAction(Animation.STOP);


		}


		// FINISH status
		if (getStatus().equals(Animation.FINISH)) {

			NumberFormat nf = NumberFormat.getNumberInstance();
			nf.setMaximumFractionDigits(3);

			messageAction(Animation.FINISH);
			messageAction("*--------Insertion of "+getInsertNode().getKey().toString()+"--------*\n Comparisons: "+
								comparisonCount+"\n Average Insertion: "+nf.format(((BSTTreeHead)getInsertNode().getHead()).averageInsertion(insertionSize))+
								"\n Worst Case Insertion: "+nf.format(((BSTTreeHead)getInsertNode().getHead()).worstCaseInsertion(insertionSize) ));

			animationAction();
			return;
		}


		// Call listeners
		animationAction();

	}


	/**
	 * Restores the settings of all nodes encountered during the animation. Usually called at
	 * the end of the animation (Animation.FINISH) to restore all settings changed throughout
	 * the animation.
	 *
	 * <p>This also restores the animator node. It also passes the animation action
	 * to all nodes of Animation.FINISH.
	 */
	protected void restore() {
		super.restore();

		AnimationEvent animationEvent = new AnimationEvent(this, AnimationEvent.INSERT_RED_BLACK_ANIMATION, Animation.FINISH);


		((RedBlackTree)getInsertNode()).animationEventPerformed(animationEvent);
		((RedBlackTree)getInsertNode()).setRedLink(true);
		messageAction("New Nodes have red bit set upon insertion.");

	}



	/**
	 * Constructs the current Rotation according to the current tree, and the defined rotationCheck.
	 * This method called out of context results in unforeseen errors.
	 *
	 * @param newTree RedBlackTree that represents the newly added node.
	 * @param node RedBlackTree that represents the current checking node.
	 *
	 */
	protected void makeCurrentRotation(RedBlackTree newTree, RedBlackTree node) {

		if (rotationCheck == BEGIN_CHECK) {
			// Keeps Red-Link integrity
			if ((newTree.getKey()).compareTo(node.getKey()) < 0) { // Go to the left
	  			rotationCheck = LEFT_CHECK;

	  			if (node != node.getHead().getChild()) {
					// Two consequetive red links.
					if ((node.isRedLink()) && (((RedBlackTree)node.getLeftTree()).isRedLink())) {

						// Same Direction
						if (node != ((RedBlackTree)node.getParentTree()).getLeftTree()) {

							currentRotation = (RotationBSTAnimation)((RedBlackTreeHead)node.getHead()).makeRotationAnimation((RedBlackTree)node.getLeftTree());

							if (currentRotation == null) {
								((RedBlackTreeHead)node.getHead()).rotateUpTreeType((RedBlackTree)node.getLeftTree());
								messageAction(Animation.REDRAW);
							}
							else {
								return;
							}

						}
					}
				}
			}
			else {

				rotationCheck = RIGHT_CHECK;

				if (node != node.getHead().getChild()) {
					// Two consequetive red links.
					if ((node.isRedLink()) && (((RedBlackTree)node.getRightTree()).isRedLink())) {

						// Same Direction
						if (node != ((RedBlackTree)node.getParentTree()).getRightTree()) {
							currentRotation = (RotationBSTAnimation)((RedBlackTreeHead)node.getHead()).makeRotationAnimation((RedBlackTree)node.getRightTree());

							if (currentRotation == null) {
								((RedBlackTreeHead)node.getHead()).rotateUpTreeType((RedBlackTree)node.getRightTree());
								messageAction(Animation.REDRAW);
							}
							else {
								return;
							}

						}
					}
				}
			}
		}
		if (rotationCheck == LEFT_CHECK) {
			rotationCheck =  BEGIN_CHECK;
			if (!node.getLeftTree().isEmpty()) {
				if ((((RedBlackTree)node.getLeftTree()).isRedLink()) && (((RedBlackTree)node.getLeftTree().getLeftTree()).isRedLink()) ) {
					// Reference to rotated up parent.
					RedBlackTree newParent = (RedBlackTree)node.getLeftTree();

					currentRotation = (RotationBSTAnimation)((RedBlackTreeHead)node.getHead()).makeRotationAnimation((RedBlackTree)node.getLeftTree());

					blackChangeLinks.add(newParent);
					redChangeLinks.add(node);

					if (currentRotation == null) {
						((RedBlackTreeHead)node.getHead()).rotateUpTreeType((RedBlackTree)node.getLeftTree());
						setBlackChangeLinks();
						setRedChangeLinks();
						messageAction(Animation.REDRAW);
					}





				}
			}
		}
		if (rotationCheck == RIGHT_CHECK) {
			rotationCheck =  BEGIN_CHECK;
			if (!node.getRightTree().isEmpty()) {
				if ((((RedBlackTree)node.getRightTree()).isRedLink()) && (((RedBlackTree)node.getRightTree().getRightTree()).isRedLink()) ) {
					// Reference to rotated up parent.
					RedBlackTree newParent = (RedBlackTree)node.getRightTree();

					currentRotation = (RotationBSTAnimation)((RedBlackTreeHead)node.getHead()).makeRotationAnimation((RedBlackTree)node.getRightTree());

					blackChangeLinks.add(newParent);
					redChangeLinks.add(node);

					if (currentRotation == null) {
						((RedBlackTreeHead)node.getHead()).rotateUpTreeType((RedBlackTree)node.getRightTree());
						setBlackChangeLinks();
						setRedChangeLinks();
						messageAction(Animation.REDRAW);
					}

				}
			}
		}
	}

	/**
	 * Fixes the redChangeLinks by settings all the nodes links within the list to red. Then
	 * the nodes are removed from the list, always leaving it clear after this call.
	 */
	 public void setRedChangeLinks() {
		ListIterator list = redChangeLinks.listIterator(0);
		while (list.hasNext()) {
			RedBlackTree currentNode = (RedBlackTree)list.next();
			if (!currentNode.isEmpty()) {

				// Reverses the link
				currentNode.setRedLink(true);
			}
		}

		redChangeLinks.clear();
	}

	/**
	 * Fixes the blackChangeLinks by settings all the nodes links within the list to black. Then
	 * the nodes are removed from the list, always leaving it clear after this call.
	 */
	 public void setBlackChangeLinks() {
		ListIterator list = blackChangeLinks.listIterator(0);
		while (list.hasNext()) {
			RedBlackTree currentNode = (RedBlackTree)list.next();

			if (!currentNode.isEmpty()) {

			// Reverses the link
			currentNode.setRedLink(false);
			}
		}

		blackChangeLinks.clear();
	}

	/**
	 * Implements <code>AnimationListener</code> which requires the following method.
	 * The only status of animation it listens for is <code>Animation.ANIMATION_MESSAGE</code>, to pass
	 * the message on.
	 *
	 * @param e AnimationEvent that represents the information of the Animation.
	 */
	public void animationEventPerformed(AnimationEvent e) {

		if (e.getStatus().equals(Animation.ANIMATION_MESSAGE)) {
			messageAction(e.getAnimationDescription());
		}
		if (e.getStatus().equals(Animation.STEP)) {
			animationAction(Animation.STEP, null);
		}

	}

	/**
	 * Calls all of the listeners of the current Animation and passed information regarding the
	 * progress and status of the current Animation. Additionally, the id of the type of animation is
	 * passed. Within, the <code>animationEventPerformed</code> method is called.
	 *
	 * @param cmd String Animation command passed instead of the current Status.
	 * @param description String description for messages.
	 */
	protected void animationAction(String cmd, String description) {
		super.animationAction(AnimationEvent.INSERT_RED_BLACK_ANIMATION, cmd, description, getCurrentLocation() / (double)getMovements().size());
	}




}
