package net.beadsproject.touch.examples;
import java.awt.Color;

import math.geom2d.Point2D;
import megamu.mesh.MPolygon;
import megamu.mesh.Voronoi;
import net.beadsproject.beads.core.AudioContext;
import net.beadsproject.touch.HVLayout;
import net.beadsproject.touch.ParticleSimulator;
import net.beadsproject.touch.SurfacePlayer;
import net.beadsproject.touch.ParticleSimulator.Particle;
import net.beadsproject.touch.event.BasicEventListener;
import net.beadsproject.touch.perform.PerformNode;
import net.beadsproject.touch.surface.PixelSurface;
import processing.core.PApplet;
import processing.core.PGraphics;

/**
 * Demonstrates the use of a particle simulation with some interaction.
 * Additionally provides a "Bake" mode that creates an interactable surface.
 * 
 * @author ben
 *
 */
public class ParticleSimulatorTestWithInteractionAndBake extends PApplet {	
	
	static final int DEPTH = 4;
	static final int NARY = 3;
	static int NUM_PARTICLES = 1;
	
	static PerformNode pt = null;
	ParticleSimulator simulator = null;
	
	boolean isSimulating = false; 
	ParticleSimulator.Particle draggingParticle = null;	
	
	boolean hasBeenBaked = false;
	PixelSurface ps;
	
	PGraphics tempImg;
	
	float ox, oy; // old mousex, mousey;
	SurfacePlayer sp; // the user
		
	public void setup()
	{		
		size(500,500);
		ellipseMode(CENTER);
		simulator = new ParticleSimulator();		
		
		println("Building tree");
		// pt = ExampleTree.exampleScatterPartsTreeUniform(DEPTH,NARY,NUM_PARTICLES);
		AudioContext ac = new AudioContext();
		pt = Example.example1();
		pt.computeUniqueValue(0,1);
		ac.start();
				
		println("Laying out and initialising simulator.");
		simulator.addHVPoints(HVLayout.layout(pt));
		simulator.computeTreeDistance();
		
		sp = new SurfacePlayer();
		
		println("Done");
	}	
		
	public void draw()
	{		
		background(200,100,100);
		if (!hasBeenBaked)
		{
			pushMatrix();
			scale(width,height);
			noStroke();
			for(ParticleSimulator.Particle p: simulator.getParticles())
			{
				Color c = new Color(Color.HSBtoRGB(p.pn.uniqueValue(),.8f,1f));			
				fill(c.getRed(),c.getGreen(),c.getBlue(),150);				
				ellipse((float)p.x,(float)p.y,(float)(p.radius*2),(float)(p.radius*2));
				
				fill(0);
				ellipse((float)p.x,(float)p.y,4.f/width,4.f/height);
			}
			
			if (mousePressed && draggingParticle!=null)
			{
				fill(255,255,200);
				ParticleSimulator.Particle p = draggingParticle;
				ellipse((float)p.x,(float)p.y,(float)(p.radius*2),(float)(p.radius*2));			
			}
			
			popMatrix();
			
			// now simulate by some amount
			simulator.step(0.005f);
		}		
		else // show baked mode
		{
			background(0);
			noFill();
			//image(tempImg,0,0);
			ps.draw(this);
		}
	}		
	
	public void mouseClicked()
	{
		// transform the mouse click into particle world coordinates
		double mx = (double)mouseX/width, my = (double)mouseY/height;
		
		// if the mouse is clicked then we locate the node under the mouse and mutate it by some amount
		for(ParticleSimulator.Particle p: simulator.getParticles())
		{
			if (p.contains(new Point2D(mx,my)))
			{
				ParticleSimulator.Particle q = new ParticleSimulator.Particle(p);
				q.setLocation(q.plus(new Point2D((1-2*Math.random())*p.radius,(1-2*Math.random())*p.radius)));
				q.pn = p.pn; // p.pn.reproduce();
				q.radius = p.radius;
				simulator.addParticle(q);
				break;
			}
		}		
	}
		
	public void mousePressed()
	{
		if (!hasBeenBaked)
		{
			// grab a particle and start to move it
			// transform the mouse click into particle world coordinates
			
			double mx = (double)mouseX/width, my = (double)mouseY/height;
			
			// if the mouse is clicked then we locate the node under the mouse and mutate it by some amount
			for(ParticleSimulator.Particle p: simulator.getParticles())
			{
				if (p.contains(new Point2D(mx,my)))
				{
					draggingParticle = p;
					break;
				}
			}
		}		
		else
		{
			float mx = (float)mouseX/width, my = (float)mouseY/height;
			ps.playerPressed(sp,mx,my);
			ox = mx;
			oy = my;
		}
	}
	
	public void mouseReleased()
	{
		float mx = (float)mouseX/width, my = (float)mouseY/height;
		
		draggingParticle = null;		
		
		if (hasBeenBaked)
		{
			ps.playerReleased(sp,mx,my);		
			ox = mx;
			oy = my;
		}
	}
	
	public void mouseDragged()
	{
		// transform the mouse click into particle world coordinates
		float mx = (float)mouseX/width, my = (float)mouseY/height;
		if (!hasBeenBaked)
		{
			draggingParticle.setLocation(new Point2D(mx,my));
		}		
		else // hasBeenBaked
		{
			// notify surface
			ps.playerMoved(sp,ox,oy,mx,my);
			
			// update old position
			ox = mx;
			oy = my;
		}
	}
	
	public void keyPressed()
	{
		if (key==' ' && !hasBeenBaked)
		{
			// bake the surface and render it
			hasBeenBaked = true;			
			
			println("Baking...");
			
			// first generate the voronoi diagram
			PerformNode[] pns = new PerformNode[simulator.getParticles().size()];
			float[][] pts = new float[simulator.getParticles().size()][2];
			ps = new PixelSurface(width,height);			
						
			int index = 0;
			for(Particle p: simulator.getParticles())
			{
				pts[index][0] = (float)p.x*width;
				pts[index][1] = (float)p.y*height;
				pns[index] = p.pn;
				index++;
			}
			Voronoi voronoi = new Voronoi(pts);
						
			// then render the surface using the index of the perform node as the color...
			tempImg = this.createGraphics(width,height,P2D);
			MPolygon[] regions = voronoi.getRegions();
			tempImg.beginDraw();
			tempImg.background(0);	
			tempImg.noStroke();
			index = 0;
			for(PerformNode pn: pns)
			{
				//tempImg.fill(index+1);
				int col  = index + 1;
				tempImg.fill((col >> 16)&0xFF,(col >> 8)&0xFF,col&0xFF);
				
				//tempImg.fill(index);
				regions[index].draw(tempImg);
				index++;
			}
			
			if (index > (256*256*256))
			{
				System.err.println("Error, too many regions! Has to be less than 256^3 atm.\nContinuing anyway..");
			}
			tempImg.endDraw();
			
			tempImg.loadPixels();
			for(int i=0;i<tempImg.pixels.length;i++)
			{
				int r = i/tempImg.width;
				int c = i%tempImg.width;
				int pni = tempImg.pixels[i];
				
				int rd = (pni >> 16) & 0xFF;
				int gr = (pni >> 8) & 0xFF;
				int bl = pni & 0xFF;
				
				int in = (int)((rd << 16) | (gr << 8) | bl) - 1;
				if (in<0)
					ps.set(c,r,null);
				else
					ps.set(c,r,pns[in]);
			}			
			tempImg.updatePixels();
			
			ps.bake(this);			
			
			println("All done.");
			
			ps.setEventListener(new BasicEventListener());
		}
	}
}
