package net.beadsproject.touch.old;
import java.awt.Color;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

import math.geom2d.Point2D;
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 ParticleGroupTest extends PApplet {	
	static final int NUM_PARTICLES = 100;
	static final int NUM_GROUPS_PER_AXIS = 3;
	
	// 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 int group;
		
		public Particle(Point2D pos, float rad){position = pos; radius = rad;}
		public static float overlap(Particle a, Particle b)
		{
			return (float) (a.position.distance(b.position) - (a.radius+b.radius));
		}
	}
	
	List<Particle> particles;
	List<Set<Particle>> particleGroups;
	Color[] groupColours;
	
	public void setup()
	{
		size(500,500);
		initialiseParticles();		
		
		strokeWeight(500.f/NUM_PARTICLES);
		
		ellipseMode(PApplet.CENTER);
		smooth();
		
		frameRate(50);
	}
	
	public void draw()
	{
		background(225);		
		updateParticles(0.015f);
		for(Particle p: particles)
		{
			stroke(255);
			Color c = groupColours[p.group];
			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);
		}
	}
	
	public void mousePressed()
	{
		Particle p = new Particle(new Point2D(mouseX,mouseY),(float) (width/((Math.random() + 1.75)*Math.sqrt(NUM_PARTICLES))));
		p.group = (int)(Math.random()*particleGroups.size());
		ListIterator<Set<Particle>> li = particleGroups.listIterator();
		int ind = 0;
		while(li.hasNext())
		{
			Set<Particle> s = li.next();
			if (ind==p.group)
			{
				s.add(p);
				break;
			}
			ind++;
		}
		
		particles.add(p);		
	}
	
	public void initialiseParticles()
	{
		particles = new LinkedList<Particle>();
		particleGroups = new LinkedList<Set<Particle>>();
		
		final float minRadius = (float) (width/(2.0*Math.sqrt(NUM_PARTICLES)));
		final float maxRadius = (float) (width/(1.5*Math.sqrt(NUM_PARTICLES)));
		
		// divide the screen into regions, each of which contains a group
		float dwidth = (float)width / NUM_GROUPS_PER_AXIS;
		float dheight = (float)height / NUM_GROUPS_PER_AXIS;
		final int numParticlesPerGroup = (NUM_PARTICLES)/(NUM_GROUPS_PER_AXIS*NUM_GROUPS_PER_AXIS); 
		
		int groupIndex = 0;
		for(int dw = 0; dw < NUM_GROUPS_PER_AXIS; dw++)
		{
			for(int dh = 0; dh < NUM_GROUPS_PER_AXIS; dh++)
			{
				float left = dw*dwidth, right = (dw+1)*dwidth;
				float top = dh*dheight, bottom = (dh+1)*dheight;
				
				float cwidth = right-left;
				float cheight = bottom-top;
				
				Point2D lower = new Point2D(left,top);
				
				HashSet<Particle> group = new HashSet<Particle>();
				particleGroups.add(group);
				
				for(int i=0;i<numParticlesPerGroup;i++)
				{
					float radius = (float)(Math.random()*(maxRadius-minRadius) + minRadius);
					Particle p = new Particle(lower.plus(new Point2D(Math.random()*cwidth,Math.random()*cheight)), radius);
					p.group = groupIndex;
					
					particles.add(p);
					group.add(p);
				}
				
				groupIndex++;
			}			
		}
		
		groupColours = new Color[groupIndex];
		for(int i=0;i<groupIndex;i++)
		{
			groupColours[i] = new Color((int)(Math.random()*255),(int)(Math.random()*255),(int)(Math.random()*255));
		}
	}
	
	public void updateParticles(float dt)
	{
		updateParticleGroupAttraction2(dt);
		updateParticlesExponential(dt);
		updateParticlesBoundary(dt);
	}	
	
	public void updateParticleGroupAttraction2(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;
				
				if (p.group == q.group)
				{
					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.002*dt*mag); // let the force of repulsion equal the overlapsquared
					p.position = p.position.plus(v);
					q.position = q.position.minus(v);
				}
			}
		}
	}
	
	public void updateParticleGroupAttraction(float dt)
	{
		// for each group of particles
		// calculate the centroid
		// apply an exponential attractive force on each particle to its groups centroid
		
		for(Set<Particle> group: particleGroups)
		{
			Point2D centroid = new Point2D(0,0);
			for(Particle p: group)
				centroid = centroid.plus(p.position);				
			centroid = centroid.scale(1./group.size());
			
			for(Particle p: group)
			{
				Point2D v = p.position.minus(centroid);
				double mag = v.distance(0,0);				
				// force should be proportional to the distance from the centroid
				v = v.scale(-0.02*dt*mag); // let the force of repulsion equal the overlapsquared
				p.position = p.position.plus(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);
			}
			
		}
	}	
}
