package algorithmsVisualiser;

import java.util.ArrayList;
import processing.core.*;
import ddf.minim.*;

public class Visualiser extends PApplet {

	PApplet papplet;
	public static ArrayList<MenuObject> menuObjects = new ArrayList<MenuObject>();
	ArrayList<Clickable> clickables = new ArrayList<Clickable>();
	public static ArrayList<Integer> a = new ArrayList<Integer>();
	public static ArrayList<Integer> b = new ArrayList<Integer>();
	boolean first = true;
	
	Minim minim;
	AudioOutput out;
	
	
	// button controls
	boolean random = false;
	boolean asc = false;
	boolean desc = false;
	boolean heap = false;
	boolean bubble = false;
	boolean about = false;
	
	// heap controls
	boolean makeH = false;
	boolean sortH = false;
	// bubble controls
	boolean sortBubble = false;
	boolean swaps = false;
	
	boolean end = false;
	int speed;
	int size;
	int volume;
	int time = 0;
	int position;
	
	public static void main(String[] args) {
		String[] a = { "MAIN" };
		PApplet.runSketch(a, new Visualiser());
	}
	
	public void settings() {
		size(1280, 800);
		noSmooth();
		fullScreen();
		//size(300, 200);
	}
	
	public void setup() {
		background(0);
		//noStroke();
		stroke(255, 0);
		papplet = new PApplet();
		
		Menu menu = new Menu(
				0.0f, 
				0.0f, 
				width, 
				height, 
				47, 48, 57, this
				);
		Screen screen = new Screen(
				width * 0.0242f, 
				height * 0.03875f, 
				width * 0.72890625f,// everything is scaled to the screen size. That's what these hard-coded long floats are for.
				height * 0.9225f,
				31, 34, 41, this
				);
		SidePanel sidepanel = new SidePanel(
				width * 0.778125f,
				0.0f + menu.border * 2,
				width * 0.19921875f, 
				height * 0.85f,
				47, 48, 57, this
				);
		Button bRun = new Button(
				"Run", 0.0f + sidepanel.pos.x, 
				0.0f + sidepanel.pos.y, 
				width * 0.0875f, 
				height * 0.05f, 
				58, 65, 73, this
				);
		Button bReset = new Button(
				"Reset", 0.0f + bRun.pos.x + bRun.dim.x + menu.border, 
				0.0f + sidepanel.pos.y,
				width * 0.0875f, 
				height * 0.05f, 
				58, 65, 73, this
				);
		Button bRandom = new OptionButton(
				"Random", 0.0f + sidepanel.pos.x, 
				height * 0.73375f,
				width * 0.0875f, 
				height * 0.05f, 
				58, 65, 73, this
				);
		Button bAsc = new OptionButton(
				"Asc", 0.0f + sidepanel.pos.x, 
				height * 0.805f,
				width * 0.0875f, 
				height * 0.05f, 
				58, 65, 73, this
				);
		Button bDesc = new OptionButton(
				"Desc", 0.0f + sidepanel.pos.x, 
				height * 0.87625f,
				width * 0.0875f, 
				height * 0.05f, 
				58, 65, 73, this
				);
		Button bHeap = new OptionButton2(
				"Heap", 0.0f + bRun.pos.x + bRun.dim.x + menu.border, 
				height * 0.73375f,
				width * 0.0875f, 
				height * 0.05f, 
				58, 65, 73, this
				);
		Button bBubble = new OptionButton2(
				"Bubble", 0.0f + bRun.pos.x + bRun.dim.x + menu.border, 
				height * 0.805f,
				width * 0.0875f, 
				height * 0.05f, 
				58, 65, 73, this
				);
		Button bAbout = new OptionButton2(
				" ", 0.0f + bRun.pos.x + bRun.dim.x + menu.border, 
				height * 0.87625f,
				width * 0.0875f, 
				height * 0.05f, 
				58, 65, 73, this
				);
		Slider sSize = new Slider(
				"Array Size", 0.0f + sidepanel.pos.x,
				0.0f + height * 0.2725f,
				width * 0.19921875f,
				height * 0.01125f,
				58, 65, 73, this
				);
		Slider sSpeed = new Slider(
				"Speed", 0.0f + sidepanel.pos.x, 
				0.0f + height * 0.45f,
				sidepanel.dim.x, 
				height * 0.01125f, 
				58, 65, 73, this
				);
		Slider sVolume = new Slider(
				"Volume", 0.0f + sidepanel.pos.x, 
				0.0f + height * 0.6275f, 
				sidepanel.dim.x, 
				height * 0.01125f, 
				58, 65, 73, this
				);
		Indicator iSize = new Indicator(
				0.0f + sidepanel.pos.x, 
				height * 0.2525f, 
				width * 0.01171875f,
				height * 0.05125f,
				58, 65, 73,
				"Size", 10, 200,
				this
				);
		Indicator iSpeed = new Indicator(
				0.0f + sidepanel.pos.x, 
				iSize.pos.y + height * 0.1775f, 
				iSize.dim.x, 
				iSize.dim.y, 
				58, 65, 73,
				"Speed", 1, 100,
				this
				);
		Indicator iVolume = new Indicator(
				0.0f + sidepanel.pos.x, 
				iSpeed.pos.y + height * 0.1775f,
				iSize.dim.x, 
				iSize.dim.y, 
				58, 65, 73,
				"Volume", 1, 100,
				this
				);
		

		menuObjects.add(menu);
		menuObjects.add(screen);
		menuObjects.add(sidepanel);
		menuObjects.add(bRun);
		menuObjects.add(bReset);
		menuObjects.add(bRandom);
		menuObjects.add(bAsc);
		menuObjects.add(bDesc);
		menuObjects.add(bHeap);
		menuObjects.add(bBubble);
		menuObjects.add(bAbout);
		menuObjects.add(sSize);
		menuObjects.add(sSpeed);
		menuObjects.add(sVolume);
		menuObjects.add(iSize);
		menuObjects.add(iSpeed);
		menuObjects.add(iVolume);
		
		clickables.add(bRun);
		clickables.add(bReset);
		clickables.add(bRandom);
		clickables.add(bAsc);
		clickables.add(bDesc);
		clickables.add(bHeap);
		clickables.add(bBubble);
		clickables.add(bAbout);
		clickables.add(iSize);
		clickables.add(iSpeed);
		clickables.add(iVolume);
		
		//Give the indicators various starting positions
		iSize.scale = 100;
		iSpeed.scale = 90;
		iVolume.scale = 10;
		((OptionButton)bRandom).active = true;
		((OptionButton2)bHeap).active = true;
		random = true;
		heap = true;
		//Update each indicator once to move them to their starting scale positions
		for(int i = clickables.size()-1; i >=0; i--)
		{
			Clickable c = clickables.get(i);
			c.update();
		}
		//Initialise the array
		setArray(iSize.scale);
		
		
	}
	
	public void draw() {
		
		background(0);
		time++;
		
		if(makeH || sortH)heap();
		if(sortBubble)bubble();
		if(end)end();

		for (int i = 0; i <= menuObjects.size() - 1; i++) {
			MenuObject mo = menuObjects.get(i);
			mo.update();
			mo.render();
		}
		
		if(mousePressed && (mouseButton == LEFT))
		{
			for (int i = 0; i <= clickables.size() - 1; i++) {
				Clickable cl = clickables.get(i);
				cl.MousePressed();
			}
		}
	}//end draw
	
	public void mouseClicked()
	{
		// check if a button has been clicked and perform corresponding task
		for (int i = 0; i <= clickables.size() - 1; i++) {
			Clickable cl = clickables.get(i);
			//Button has been clicked, check which button it is using the label
			if(cl instanceof Button)
			{
				((Button) cl).checkPos();
				if(((Button) cl).clicked)
				{
					// If Reset button
					if(((Button) cl).label.matches("Reset"))
					{
						setArray(get("Size"));
						resetFlags();
						enableRun();
					}//end reset button
					//If Run button
					if(((Button) cl).label.matches("Run") && ((Button) cl).enabled)
					{
						if(!first)
						{
							setArray(get("Size"));
						}
						else
						{
							first = false;
						}
						speed = (int) map(get("Speed"), 1, 100, 60, 1);
						((Button) cl).enabled = false;
						
						if(heap)
						{
							makeH = true;
							position = a.size() / 2 - 1;
						}
						if(bubble)
						{
							position = 0;
							sortBubble = true;
						}
					}//End Run button
				}//End if button clicked
				((Button) cl).clicked = false;
			}//end if instanceof Button
			
			if(cl instanceof OptionButton)
			{
				((OptionButton) cl).checkPos();
				//OptionButton has been clicked, check which button it is using the label
				if(((OptionButton) cl).clicked)
				{
					if(((Button) cl).label.matches("Random"))
					{
						clearOptions(1);
						random = true;
						((OptionButton) cl).active = true;
					}
					
					if(((Button) cl).label.matches("Asc"))
					{
						clearOptions(1);
						asc = true;
						((OptionButton) cl).active = true;
					}
					
					if(((Button) cl).label.matches("Desc"))
					{
						clearOptions(1);
						desc = true;
						((OptionButton) cl).active = true;
					}
					
					
				}
			}
			
			if(cl instanceof OptionButton2)
			{
				((OptionButton2) cl).checkPos();
				//Button has been clicked, check which button it is using the label
				if(((OptionButton2) cl).clicked)
				{
					if(((Button) cl).label.matches("Heap"))
					{
						clearOptions(2);
						heap = true;
						((OptionButton2) cl).active = true;
					}
					
					if(((Button) cl).label.matches("Bubble"))
					{
						clearOptions(2);
						bubble = true;
						((OptionButton2) cl).active = true;
					}
					
					if(((Button) cl).label.matches("About"))
					{
						clearOptions(2);
						about = true;
						((OptionButton2) cl).active = true;
					}
				}
			}
			
		}//end clickable loop
	}//end mouseClicked
	
	// fill the array with values based on what button is selected (random, asc, desc)
	public void setArray(int n)
	{
		// first, clear the array of old values
		for(int i = a.size()-1; i >=0; i--)
		{
			Integer x = a.get(i);
			a.remove(x);
			x = b.get(i);
			b.remove(X);
		}
		if(random)
		{
			//n = size of array (taken from Indicator class "scale")
			for (int i = 0; i < n; i++) {
				double x;
				int r;
				x = (Math.random() * (n+1));
				r = (int) x;
				while (a.contains(r) || r==0) {
					x = (Math.random() * (n+1));
					r = (int) x;
				}
				a.add(r);
				b.add(0);
			}
		}
		else if(asc)
		{
			for (int i = 1; i <= n; i++) {
				a.add(i);
				b.add(0);
			}
		}
		else if(desc)
		{
			for (int i = n-1; i > 0; i--) {
				a.add(i);
				b.add(0);
			}
		}
	}
	
	// make the whole array white
	public void clearColours()
	{
		for (int i = 0; i < b.size(); i++)
		{
			b.set(i, 0);
		}
	}

	// when an algorithm is done, this colours it green to show it's done
	public void end()
	{
		if(position >= a.size()-1 && time % 1 == 0)
		{
			clearColours();
			end = false;
			enableRun();
			return;
		}
		if(time % 1 == 0)
		{
			clearColours();
			b.set(position, 2);
			b.set(position+1, 2);
			position+=2;
		}
	}
	
	// change enable flag of Run button
	public void enableRun()
	{
		for (int j = 0; j <= clickables.size() - 1; j++) {
			Clickable cl2 = clickables.get(j);
			if(cl2 instanceof Button)
			{
				if(((Button) cl2).label.matches("Run"))
				{
					((Button) cl2).enabled = true;
				}
			}
		}
	}
	
	// get the scale value from an Indicator object, given it's label
	public int get(String label)
	{
		for (int j = 0; j <= clickables.size() - 1; j++) {
			Clickable cl2 = clickables.get(j);
			if(cl2 instanceof Indicator)
			{
				if(((Indicator)cl2).id.matches(label))
				{
					return ((Indicator)cl2).scale;
				}
			}
		}
		return 10;
	}

	// reset the flags for all algorithms
	public void resetFlags()
	{
		makeH = false;
		sortH = false;
		sortBubble = false;
		end = false;
		first = true;
	}
	
	// for the button in the bottom right, reset the flags and colours
	public void clearOptions(int x)
	{
		if(x == 1)
		{
			random = false;
			asc = false;
			desc = false;
		}
		else if(x==2)
		{
			heap = false;
			bubble = false;
			about = false;
		}
		for (int i = 0; i <= clickables.size() - 1; i++) {
			Clickable cl = clickables.get(i);
			if(cl instanceof OptionButton && x==1)
			{
				((OptionButton) cl).active = false;
			}
			else if(cl instanceof OptionButton2 && x==2)
			{
				((OptionButton2) cl).active = false;
			}
		}
	}
	
	public void bubble()
	{
		if(sortBubble && time % speed == 0)
		{
			clearColours();
			b.set(position, 1);
			if(position < a.size()-1)
			{
				b.set(position+1, 1);
				if(a.get(position) > a.get(position+1))
				{
					int temp = a.get(position + 1);
					a.set(position + 1, a.get(position));
					a.set(position, temp);
					swaps = true;
				}
			}
			else
			{
				if(swaps)
				{
					position = 0;
					swaps = false;
					return;
				}
				else
				{
					sortBubble = false;
					end = true;
					position = 0;
				}
			}
			position++;
		}
		
	}
	
	public void heap()
	{
		// heap sort controller
		if(makeH==true && time%speed == 0)
		{
			// make a heap
			clearColours();
			b.set(position, 1);
			siftDown(position, a.size());
			position--;
		}
		if(position<0)
		{
			clearColours();
			makeH = false;
			position = a.size()-1;
			sortH = true;
		}
		if(sortH == true && time%speed ==0)
		{
			// sort the heap
			clearColours();
			int v = a.get(0);
			a.set(0, a.get(position));
			siftDown(0, position);	// here, counter represents heap size (shrinking as values are sorted and removed)
			a.set(position, v);		// removed value is placed outside of heap
			b.set(position, 2);
			position--;
		}
		if(position<1 && !makeH)
		{
			sortH = false;
			end = true;
			position = 0;
		}
	}
	
	public void siftDown(int k, int N) {	// k is the node being sifted
		int v = Visualiser.a.get(k);		// v is the value being sifted
		int j = 2*k+1;						// j is left child of k
		while (j <= N-1) {					// while in heap
			if (j < N-1 && a.get(j+1) > a.get(j)) { 	// find the smaller child
				j++;														// switch from left to right if needed
			}
			if (a.get(j) <= v) { 			// if child is smaller than parent, j is position to store v
				b.set(j, 1);
				break;
			}
			// if parent is smaller than the child
			a.set(k, a.get(j));			// copy child j to parent location k
			b.set(j, 1);
			k = j; 						// k is the node being sifted
			j = 2*k+1; 					// j is left child of k
		}
		a.set(k, v);			// move value to the end of the array (outside of heap)
		b.set(k, 1);
		
		if(N==0) {
			a.set(k, 0);
		}
	}// end siftDown
	
	public static int[] mergeSort(int[] list)
	{
		if(list.length <=1)
		{
			return list;
		}
		
		// split list[] into two halves
		int[] first = new int[list.length/2];
		int[] second = new int[list.length-first.length];
		//copy values over to each half
		for(int i=0; i<first.length; i++)
		{
			first[i]=list[i];
		}
		for(int i=0; i<second.length; i++)
		{
			second[i]=list[i+first.length];
		}
		
		// sort each half
		mergeSort(first);
		mergeSort(second);
		
		// merge halves together
		merge(first, second, list);
		return list;
	}
	
	public static void merge(int[] first, int[] second, int[] result)
	{
		// keep track of each half counter and result counter
		int iFirst = 0;
		int iSecond = 0;
		int iMerged = 0;
		
		// copy the lower value from each half onto result
		while(iFirst<first.length && iSecond<second.length)
		{
			if(first[iFirst] < second[iSecond])
			{
				result[iMerged] = first[iFirst];
				iFirst++;
			}
			else
			{
				result[iMerged] = second[iSecond];
				iSecond++;
			}
			iMerged++;
		}
		
		// copy the leftover onto result (in case of odd number)
		for(int i=0; i<first.length-iFirst; i++)
		{
			result[iMerged+i] = first[iFirst+i];
		}
		for(int i=0; i<second.length-iSecond; i++)
		{
			result[iMerged+i] = second[iSecond+i];
		}
	}

}
