package net.beadsproject.touch.surface;
import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.beadsproject.touch.SurfacePlayer;
import net.beadsproject.touch.event.EnterEvent;
import net.beadsproject.touch.event.ExitEvent;
import net.beadsproject.touch.event.SwitchEvent;
import net.beadsproject.touch.perform.PerformNode;

import megamu.mesh.MPolygon;
import megamu.mesh.Voronoi;
import processing.core.PApplet;


public class VoronoiSurfaceTwo extends Surface {
	
	private Map<PerformNode, PNPnt> inverseMap;
    

    
    public class PNPnt extends Point2D.Float 
    {
    	public PerformNode pn;
    	
    	public PNPnt(PerformNode pn, float x, float y)
    	{
    		super(x,y);
    		this.pn = pn;
    	}
    	
    	public PerformNode pn()
    	{
    		return this.pn;
    	}
    }
    
    List<PNPnt> points;    
    Voronoi voronoi;
    private Map<SurfacePlayer,PNPnt> activePoints;
    
    
	public VoronoiSurfaceTwo()
	{
		points = new LinkedList<PNPnt>();	
		activePoints = new HashMap<SurfacePlayer,PNPnt>();
		inverseMap = new HashMap<PerformNode,PNPnt>();
	}
			
	public void addPerformNode(PerformNode pn, float x, float y)
	{		
		PNPnt point = new PNPnt(pn,x,y);
		points.add(point);	    
		inverseMap.put(pn,point);
	}
	
	public void swapPoints(PNPnt a, PNPnt b)
	{
		PerformNode bpn = b.pn();
		b.pn = a.pn();
		a.pn = bpn;
		inverseMap.put(a.pn,a);
		inverseMap.put(b.pn,b);
	}
	
	public int numPoints()
	{
		return points.size();
	}
	
	public List<PNPnt> getPoints()
	{
		return points;
	}	
	
	public void buildVoronoi()
	{
		// parse data appropriately
		float[][] pts = new float[points.size()][2];
		int index = 0;
		for(PNPnt p: points)
		{
			pts[index][0] = p.x;
			pts[index][1] = p.y;
			index++;
		}
		
		voronoi = new Voronoi(pts);
	}
	
	static public double sqdistance(double x1, double y1, double x2, double y2)
	{
		return (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2);
	}
	
	static public double sqdistance(PNPnt a, PNPnt b)
	{
		return sqdistance(a.x,a.y,b.x,b.y);
	}
	
	
	public void playerMoved(SurfacePlayer sp, float fromX, float fromY, float toX, float toY)	
	{
		PNPnt from = findClosestPoint(fromX,fromY), to = findClosestPoint(toX,toY);
				
		if (from==null && to!=null)
		{
			event(new EnterEvent(this,to.pn(),toX,toY));
			activePoints.put(sp,to);
		}
		else if (to==null && from!=null)
		{
			event(new ExitEvent(this,from.pn(),toX,toY));
			activePoints.remove(sp);
		}
		else if (from != to)
		{	
			event(new SwitchEvent(this,from.pn(),to.pn(),toX,toY));
			activePoints.put(sp,to);
		}
	}
	
	public void playerPressed(SurfacePlayer sp, float x, float y)
	{
		PNPnt closest = findClosestPoint(x,y);
		if (closest!=null)
		{
			event(new EnterEvent(this,closest.pn(),x,y));
			activePoints.put(sp,closest);
		}
	}
	
	public void playerReleased(SurfacePlayer sp, float x, float y)
	{
		PNPnt closest = findClosestPoint(x,y);
		if (closest!=null)
		{
			event(new ExitEvent(this,closest.pn(),x,y));
			activePoints.remove(sp);
		}
	}
	
	private PNPnt findClosestPoint(float x, float y)
	{
		PNPnt target = null;
		double distF = 100;		
		for(PNPnt p: points)
		{
			if (p.pn()==null) continue;
			
			double fd = sqdistance(p.x,p.y,x,y);
			if (fd < distF)
			{
				target = p;
				distF = fd;
			}			
		}		
		return target;
	}
	
	public void draw(PApplet app)
	{	    
		app.background(backgroundColour[0],backgroundColour[1],backgroundColour[2]);
		
		app.noStroke();
		MPolygon[] regions = voronoi.getRegions();
		int index = 0;
		for(PNPnt p: points)
		{
			setUniqueFill(app,p.pn());
			regions[index].draw(app);
			index++;
		}
		
		app.stroke(borderColour[0],borderColour[1],borderColour[2]);
		float[][] myEdges = voronoi.getEdges();
		for(int i=0; i<myEdges.length; i++)
		{
			float startX = myEdges[i][0];
			float startY = myEdges[i][1];
			float endX = myEdges[i][2];
			float endY = myEdges[i][3];
			app.line( startX, startY, endX, endY );
		}
		
		app.stroke(255,255,255,50);
		drawTreeLines(app,points.get(0).pn().getRoot());
	}
	
	private void drawTreeLines(PApplet app, PerformNode pn)
	{
		PNPnt p = inverseMap.get(pn);
		for(PerformNode child: pn.getChildren())
		{
			// draw a line to child
			PNPnt c = inverseMap.get(child);
			app.line(p.x,p.y,c.x,c.y);
			drawTreeLines(app, child);
		}
	}
}
