package net.beadsproject.touch.old;

import java.awt.Color;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import math.geom2d.Point2D;
import net.beadsproject.touch.examples.Example;
import net.beadsproject.touch.perform.PerformNode;
import processing.core.PApplet;


/**
 * Test code, used to work out the repulsive particle simulation.
 * This code removes the other physics models, and adds the additional constraint of particle "groups".
 * 
 * @author ben
 */
public class ParticleHierarchyTest extends PApplet {	
	static int NUM_PARTICLES; // determined below
	
	// particle physics models
	static final int MODE_EXPONENTIAL = 2;
	static final int MODE = MODE_EXPONENTIAL;	
	
	static class Particle
	{
		public Point2D position; 
		public float radius;
		public PerformNode pn;
		
		// depre
		public int group;
		
		// cache the tree distance to each other particle
		public Map<Particle,Integer> treeDistanceMap;
		
		public Particle(Point2D pos, float rad)
		{
			position = pos; radius = rad;
			treeDistanceMap = new HashMap<Particle,Integer>();
		}
		public static float overlap(Particle a, Particle b)
		{
			return (float) (a.position.distance(b.position) - (a.radius+b.radius));
		}
	}
	
	List<Particle> particles;
	Map<PerformNode,Particle> pnToParticleMap;
	
	List<Set<Particle>> particleGroups;
	Color[] groupColours;
	
	boolean shouldIUpdateParticles = false;
	
	public void setup()
	{
		size(500,500);		
		ellipseMode(PApplet.CENTER);
		//smooth();		
		frameRate(50);
		
		pnToParticleMap = new HashMap<PerformNode,Particle>();
		
		// String filename = "D:\\audio\\crash test audio\\chopped live sounds\\Bongos\\BONGOROL.WAV";
		// String filename = "D:\\Music\\Alog\\Amateur\\02 A Throne For The Common Man.mp3";
		String filename = "D:\\Music\\gammaBrosTheme.mp3";
		NUM_PARTICLES = 100;
		final int DEPTH = 2;
		final int NARY = 5;
		
		// PerformTree pt = ExampleTree.exampleScatterPartsTreeUniform(DEPTH,NARY,(int) (NUM_PARTICLES/(Math.pow(DEPTH,NARY))));
		PerformNode pt = Example.example1();
		pt.computeUniqueValue(0, 1);
		System.out.println(pt.toString());
		loadTree(pt);		
		NUM_PARTICLES = particles.size();
	}
	
	public void draw()
	{
		background(225);		
		if (shouldIUpdateParticles)
			updateParticles(0.001f);
		for(Particle p: particles)
		{
			stroke(255);
			strokeWeight(500.f/NUM_PARTICLES);			
			Color c = new Color(Color.HSBtoRGB(p.pn.uniqueValue(), 0.6f, 0.8f));			
			fill(c.getRed(),c.getGreen(),c.getBlue(),150);
			ellipse((float)p.position.x,(float)p.position.y,2*p.radius,2*p.radius);
			
			noStroke();
			fill(0);			
			ellipse((float)p.position.x,(float)p.position.y,5,5);
		}
		
		for(Particle p: particles)
		{			
			if (p.pn.getChildren().isEmpty())
			{			
				// draw a line to the centroid of all pn's siblings
				List<PerformNode> childlessSiblings = new LinkedList<PerformNode>();
				// filter to choose only those that have no children themself
				List<PerformNode> siblings = p.pn.getSiblings();
				if (siblings!=null)
				{
					for(PerformNode s: p.pn.getSiblings())
					{
						if (s.getChildren().isEmpty())
							childlessSiblings.add(s);
					}
					
					if (childlessSiblings!=null)
					{
						Point2D centroid = new Point2D(p.position);
						for(PerformNode pn: childlessSiblings)
						{
							Particle s = pnToParticleMap.get(pn);
							if (s!=null)
							{
								centroid = centroid.plus(s.position);
							}
						}
						centroid = centroid.scale(1.f/(1+childlessSiblings.size()));
					
						stroke(0,150);
						strokeWeight(2);
						line((float)p.position.x,(float)p.position.y,
								(float)centroid.x,(float)centroid.y);				
						
					}
				}
			}
		}
	}
	
	public void mousePressed()
	{	
		shouldIUpdateParticles = !shouldIUpdateParticles;
	}
		
	public void updateParticles(float dt)
	{
		updateParticleTreeAttraction(dt);
		updateParticlesExponential(dt);
		updateParticlesBoundary(dt);
	}	
	
	public void updateParticleTreeAttraction(float dt)
	{
		// perform an attraction step between all pairs...
		// for each overlapping particle, move it one "step" away, i.e., a little bit
		ListIterator<Particle> it = particles.listIterator();
		while(it.hasNext())
		{
			Particle p = it.next();
			ListIterator<Particle> it2 = particles.listIterator();
			while(it2.hasNext())
			{
				Particle q = it2.next();
				if (p==q) continue;
				
				int treedist = p.treeDistanceMap.get(q);
				if (treedist<=1)
				{				
					Point2D v = p.position.minus(q.position);
					double mag = v.distance(0,0);				
					// force should be proportional to the distance from the centroid
					v = v.scale(-0.0001*dt*mag); // let the force of repulsion equal the overlapsquared
					p.position = p.position.plus(v);
					q.position = q.position.minus(v);
				}
			}
		}
	}		
	
	public void updateParticlesExponential(float dt)
	{
		// for each overlapping particle, move it one "step" away, i.e., a little bit
		ListIterator<Particle> it = particles.listIterator();
		while(it.hasNext())
		{
			Particle p = it.next();
			ListIterator<Particle> it2 = particles.listIterator();
			while(it2.hasNext())
			{
				Particle q = it2.next();
				if (p==q) continue;
				
				float o = Particle.overlap(p,q);
				if (o<0)
				{
					// p and q away from each other
					
					if (p.position.distanceSq(q.position) < 2)
					{
						// then perturb each position randomly
						final double PERTURB = 2;
						p.position = p.position.plus(new Point2D(PERTURB*Math.random() - PERTURB/2,PERTURB*Math.random() - PERTURB/2));
						q.position = q.position.plus(new Point2D(PERTURB*Math.random() - PERTURB/2,PERTURB*Math.random() - PERTURB/2));						
					}
					else
					{
						Point2D v = p.position.minus(q.position);
						v = v.scale((o*o)*dt/v.distance(0,0)); // let the force of repulsion equal the overlapsquared
						p.position = p.position.plus(v);
						q.position = q.position.minus(v);
					}
				}
			}
		}
	}
	
	void updateParticlesBoundary(float dt)
	{
		// for each overlapping particle, move it one "step" away, i.e., a little bit
		ListIterator<Particle> it = particles.listIterator();
		while(it.hasNext())
		{
			Particle p = it.next();
		
		
			// also force the boundary constraint
			final double BOUNDFORCE = 0.2;
			if ((p.position.x-p.radius) < 0)
			{
				p.position.x += BOUNDFORCE*dt*(p.position.x-p.radius)*(p.position.x-p.radius);
			}
			else if ((p.position.x+p.radius) > width)
			{
				p.position.x -= BOUNDFORCE*dt*((width-p.position.x) - p.radius)*((width-p.position.x) - p.radius);
			}
			
			if ((p.position.y - p.radius) < 0)
			{
				p.position.y += BOUNDFORCE*dt*(p.position.y-p.radius)*(p.position.y-p.radius);	
			}
			else if ((p.position.y+p.radius) > height)
			{
				p.position.y -= BOUNDFORCE*dt*((height-p.position.y) - p.radius)*((height-p.position.y) - p.radius);
			}
			
		}
	}	
	
	
	void loadTree(PerformNode pt)
	{
		particles = new LinkedList<Particle>();
		
		PerformNode root = pt.getRoot();
		if (root==null) return;
		BRV_addPNAndChildren(root,width/2,height/2,width/2);
		//vs.buildPointsAndTheirPolys();
		
		// cache tree dist
		// for each overlapping particle, move it one "step" away, i.e., a little bit
		ListIterator<Particle> it = particles.listIterator();
		while(it.hasNext())
		{
			Particle p = it.next();
			ListIterator<Particle> it2 = particles.listIterator();
			while(it2.hasNext())
			{
				Particle q = it2.next();
				if (p==q) continue;
				
				p.treeDistanceMap.put(q, PerformNode.getTreeDistance(p.pn, q.pn));				
			}
		}
	}
	
	void BRV_addPNAndChildren(PerformNode pn, double x, double y, double r)
	{
		List<PerformNode> children = pn.getChildren();
		int num = children.size();
		// add self 
		// addPerformNode(pn,x,y);
		if (num==0)
		{
			addPerformNode(pn, x, y);
		}
		if (num==1)
		{
			// add sole child just above it
			PerformNode child = children.get(0);
			BRV_addPNAndChildren(child, x, y+r/2, (float) (r*0.8));
		}
		else
		{
			// distribute the circles equally around the center
			double dtheta = 2.0*Math.PI/num;
			double theta = 0;
			double distFromCenter = r * .5;
	
			// compute the maximum radius the subcircles can be
			double radius = Math.min(r-distFromCenter,distFromCenter*Math.sin(dtheta/2));
			for(PerformNode child: children)
			{
				double cx = distFromCenter*Math.cos(theta) + x;
				double cy = distFromCenter*Math.sin(theta) + y;
				theta += dtheta;	
	
				BRV_addPNAndChildren(child, cx, cy, radius);				
			}			
		}		
	}
	
	void addPerformNode(PerformNode pn, double x, double y)
	{
		final float minRadius = (float) (width/(2.0*Math.sqrt(NUM_PARTICLES)));
		final float maxRadius = (float) (width/(1.5*Math.sqrt(NUM_PARTICLES)));
		float radius = (float)(Math.random()*(maxRadius-minRadius) + minRadius);		
		
		Particle p = new Particle(new Point2D(x,y), radius);
		p.pn = pn;		
		particles.add(p);
		pnToParticleMap.put(p.pn,p);
	}
}
