import processing.core.*;
import sorts.*;

import java.util.LinkedList;
import java.util.NoSuchElementException;

import java.net.*;
import java.applet.*;
import netscape.javascript.*;

public class VisualSorts extends PApplet {

	static final long serialVersionUID = 01234;

	final int appletWidth = 850;
	final int appletHeight = 500;

	private int fps = 30;

	final int graphWidth = 800;
	final int graphHeight = 500;
	private int elementCount = 100;
	private float barWidth = graphWidth / elementCount;

	private int holdHeight = 0;

	private int[] original;
	private int[] sortme;
	private int[] showme;
	private boolean sorting = false;

	private LinkedList<StepObject> steps = null;
	private int stepCount = 0;
	private int compareCount = 0;
	private int swapCount = 0;

	private int[] colors = new int[elementCount];
	private int highlight = 255;
	private int fade;

	final int SELECTION = 1;
	final int INSERTION = 2;
	final int BUBBLE = 3;

	private int sort = SELECTION;
	
	private PFont font;

	static public void main(String args[]) {
		PApplet.main(new String[] { "VisualSorts" });
	}

	public void setup() {

		size(appletWidth, appletHeight);
		smooth();
		background(255);
		setFramerate(fps);

		// The font must be located in the sketch's
		// "data" directory to load successfully
//		font = loadFont("ScalaSans-Caps-32.vlw");
//		textFont(font);

		setupArray();
	}

	private void setupArray() {

		original = new int[elementCount];

		// Random heights
		for (int i = 0; i < elementCount; i++) {
			original[i] = (int) (Math.ceil(graphHeight * Math.random()));
		}

		switchSort(sort);
	}

	// Gets called each frame
	public void draw() {
		if (sorting) {
			sortGraph();
		}
	}

	private void drawGraph() {
		background(255); // Clear bg
		noStroke();

		for (int i = 0; i < showme.length; i++) {
			fill(colors[i], 0, 0);
			rect(barWidth * i, graphHeight - showme[i], barWidth - 1, showme[i]);
		}

		if (holdHeight > 0) {
			fill(highlight, 0, 0);
			rect(appletWidth - barWidth, graphHeight - holdHeight,
					barWidth - 1, holdHeight);
		}

//		if (stepCount > 0) {
//			fill(0, 0, 0);
//			text(Integer.toString(stepCount), 0, 10, 50, 50);
//		}

	}

	private void sortGraph() {
		try {
			// This will trip if we are done
			steps.getFirst();

			// Find the elements
			int ia = steps.getFirst().a;
			int ib = steps.getFirst().b;

			// Change the colors
			for (int i = 0; i < colors.length; i++) {
				colors[i] = Math.max(0, colors[i] - fade);
			}

			if (steps.getFirst().compare) {
				colors[ia] = highlight;
				colors[ib] = highlight;
				// Count steps
				compareCount++;
			} else if (steps.getFirst().swap) {
				colors[ia] = highlight;
				colors[ib] = highlight;
				int hold = showme[ia];
				showme[ia] = showme[ib];
				showme[ib] = hold;
				// Count steps
				swapCount++;
			} else if (steps.getFirst().hold) {
				colors[ia] = highlight;
				holdHeight = showme[ia];
				// Count steps
				swapCount++;
			} else if (steps.getFirst().unhold) {
				colors[ia] = highlight;
				showme[ia] = holdHeight;
				holdHeight = 0;
				// Count steps
				swapCount++;
			}

			// This step is done
			steps.removeFirst();

			// Count steps
			stepCount++;

			drawGraph();

		} catch (NoSuchElementException e) {
			// We are done
			sorting = false;
			resetColors();
			drawGraph();

			// Alert
			callJS("output('sort completed with roughly "
					+ (compareCount + swapCount)
					+ " operations"
					+ (compareCount > 0 ? " (" + compareCount
							+ " comparisons and " + swapCount + " swaps)" : "")
					+ ", " + (stepCount / elementCount)
					+ " operations per element')");
		}
	}

	public void startSorting() {
		if (steps != null) {
			setFramerate(fps);
			sorting = true;
		}
	}

	/**
	 * Click to play or pause
	 */
	public void mouseReleased() {
		if (sorting)
			setFramerate(0);
		else
			startSorting();
	}

	// Setters

	public void setCount(int n) {
		if (n > 1 && n <= graphWidth / 2) {
			elementCount = n;
			barWidth = graphWidth / elementCount;
			setupArray();
		}
	}

	public void semiSorted(int n) {
		if (n > 1 && n <= graphWidth / 2) {
			setCount(n);

			for (int i = 0; i < elementCount; i++) {
				int sortish = (int) (((double) i) / (double) elementCount * (double) graphHeight);
				int changeby = (int) (Math.random() * 50);
				if (Math.random() > .5)
					original[i] = Math.min(sortish + changeby, graphHeight);
				else
					original[i] = Math.max(sortish - changeby, 1);
			}

			switchSort(sort);
		}
	}

	public void setFramerate(int n) {
		if (n == 0) {
			sorting = false;
			fade = 255;
			sortGraph();
		} else if (n > 0 && n < 500) {
			fps = n;

			frameRate(n);

			if (n < 30)
				fade = 255;
			else
				fade = 30;

			if (steps != null)
				sorting = true;
		}
	}

	public void switchSort(int type) {
		reset();

		switch (type) {
		case INSERTION:
			sort = INSERTION;
			steps = StepInsertionSort.getInstance().insertionSort(sortme);
			callJS("output('insertion sort selected')");
			break;
		case BUBBLE:
			sort = BUBBLE;
			steps = StepBubbleSort.getInstance().bubbleSort(sortme);
			callJS("output('bubble sort selected')");
			break;
		default:
			sort = SELECTION;
			steps = StepSelectionSort.getInstance().selectionSort(sortme);
			callJS("output('selection sort selected')");
			break;
		}

		drawGraph();
	}

	private void reset() {
		sorting = false;
		holdHeight = 0;
		steps = null;
		stepCount = 0;
		compareCount = 0;
		swapCount = 0;

		showme = original.clone();
		sortme = original.clone();

		resetColors();
		drawGraph();
	}

	private void resetColors() {
		for (int i = 0; i < colors.length; i++) {
			// All black
			colors[i] = 0;
		}
	}

	private void callJS(String s) {

		try {

			JSObject win = (JSObject) JSObject.getWindow(this);
			win.eval(s);

			/*
			 * AppletContext context = getAppletContext(); if (context != null) {
			 * context.showDocument(new URL("javascript:" + s), "_self"); }
			 */
		} catch (Exception ignore) {
			// If not an applet, or error, print message in console
			System.out.println(s);
		}

	}

}

