package net.beadsproject.touch.old;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

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 net.beadsproject.touch.perform.PerformTree;
import net.beadsproject.touch.surface.Surface;
import processing.core.PApplet;

import delaunay.DelaunayTriangulation;
import delaunay.Pnt;
import delaunay.Simplex;

public class VoronoiSurface extends Surface {
	
	private DelaunayTriangulation dt;     // The Delaunay triangulation
    private Simplex<Pnt> initialTriangle; // The large initial triangle
    private int initialSize = 1000;      // Controls size of initial triangle    
    
    private HashMap<PNPnt, Simplex<Pnt>> pointsAndTheirPolys;
    private List<PNPnt> points;
    
    private Simplex<Pnt> highlight = null;
    private List<Pnt> highlightPoints = null;
    
    private Map<SurfacePlayer,PNPnt> activePoints;
    
    private Map<PerformNode, PNPnt> inverseMap;
    
    private PerformTree performTree;
    
    public class PNPnt extends Pnt 
    {
    	public PerformNode pn;
    	
    	public PNPnt(PerformNode pn, double... c)
    	{
    		super(c);
    		this.pn = pn;
    	}
    	
    	public PerformNode pn()
    	{
    		return this.pn;
    	}
    }    
    
	public VoronoiSurface()
	{
		initialTriangle = new Simplex<Pnt>(new PNPnt(null,-initialSize, -initialSize),
                new PNPnt(null, initialSize, -initialSize),
                new PNPnt(null,           0,  initialSize));
		dt = new DelaunayTriangulation(initialTriangle);
		pointsAndTheirPolys = new HashMap<PNPnt, Simplex<Pnt>>();
		points = new LinkedList<PNPnt>();
		highlightPoints = new LinkedList<Pnt>();
		activePoints = new HashMap<SurfacePlayer,PNPnt>();
		inverseMap = new HashMap<PerformNode,PNPnt>();
	}
			
	public void addPerformNode(PerformNode pn, double x, double y)
	{
		PNPnt point = new PNPnt(pn,x,y);
	    dt.delaunayPlace(point);
	    
	    points.add(point);
	    inverseMap.put(pn,point);
	}
	
	public int numPoints()
	{
		return points.size();
	}
	
	public List<PNPnt> getPoints()
	{
		return points;
	}	
	
	static public double sqdistance(double x1, double y1, double x2, double y2)
	{
		return (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2);
	}
	
	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.coord(0),p.coord(1),x,y);
			if (fd < distF)
			{
				target = p;
				distF = fd;
			}			
		}		
		return target;
	}

	@Override
	public void mouseMoved(float fromX, float fromY, float toX, float toY) {
		// doesn't work!
		/*
		// check intersection with each line		
		Simplex<Pnt> from = dt.locate(new Pnt(fromX, fromY));
		Simplex<Pnt> to = dt.locate(new Pnt(toX,toY));
		highlight = to;
		
		// we find each closest point for from and to, and update appropriately
		Pnt fromP = null;
		double dist = 100;
		for(Pnt p: from)
		{
			if (((PNPnt)p).pn()==null) continue;
			
			double d = sqdistance(p.coord(0),p.coord(1),fromX,fromY);
			if (d < dist)
			{
				fromP = p;
				dist = d;
			}			
		}
		
		Pnt toP = null;
		dist = 100;
		for(Pnt p: to)
		{
			if (((PNPnt)p).pn()==null) continue;
			
			double d = sqdistance(p.coord(0),p.coord(1),toX,toY);
			if (d < dist)
			{
				toP = p;
				dist = d;
			}			
		}
		
		highlightPoints.clear();
		assert(fromP!=null);
		assert(toP!=null);		
		highlightPoints.add(fromP);
		highlightPoints.add(toP);
		
		if (fromP != toP)
		{
			PerformNode fromPN = ((PNPnt)fromP).pn();
			PerformNode toPN = ((PNPnt)fromP).pn();
			
			event(new ExitEvent(this,fromPN,fromX,fromY));
			event(new EnterEvent(this,toPN,toX,toY));
		}
		*/
		
		PNPnt from = null, to = null;
		double distF = 100, distT = 100;
		for(PNPnt p: points)
		{
			if (p.pn()==null) continue;
			
			double fd = sqdistance(p.coord(0),p.coord(1),fromX,fromY), td = sqdistance(p.coord(0),p.coord(1),toX,toY);
			if (fd < distF)
			{
				from = p;
				distF = fd;
			}
			if (td < distT)
			{
				to = p;
				distT = td;
			}
		}
		
		if (from != to)
		{
			PerformNode fromPN = from.pn();
			PerformNode toPN = to.pn();
			
			event(new SwitchEvent(this,fromPN,toPN,toX,toY));
		}
		
	}
	
	@Override
	public void mousePressed(float x, float y) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseReleased(float x, float y) {
		// TODO Auto-generated method stub

	}

	public void draw(PApplet app)
	{	    
		app.background(backgroundColour[0],backgroundColour[1],backgroundColour[2]);
		
		/*
		app.stroke(borderColour[0],borderColour[1],borderColour[2]);
	    for (Simplex<Pnt> triangle: dt)
	    {	
	    	for (Simplex<Pnt> other: dt.neighbors(triangle)) {
	            Pnt p = Pnt.circumcenter(triangle.toArray(new Pnt[0]));
	            Pnt q = Pnt.circumcenter(other.toArray(new Pnt[0]));
	            app.line((float)p.coord(0),(float)p.coord(1),
	            		 (float)q.coord(0),(float)q.coord(1));
	    	}
	    }
	    
	    app.stroke(0,0,255,100);
	    drawTreeLines(app,points.get(0).pn().getRoot());
	    */
	    
	    // draw the actual triangulation
	    /*
	    app.stroke(borderColour[0],borderColour[1],borderColour[2],100);
	    for (Simplex<Pnt> triangle: dt)
	    {	
	    	for(Set<Pnt> edge: triangle.facets())
	    	{
	    		Pnt p = (Pnt) edge.toArray()[0];
	    		Pnt q = (Pnt) edge.toArray()[1];
	    		app.line((float)p.coord(0),(float)p.coord(1),
	            		 (float)q.coord(0),(float)q.coord(1));	    			    		
	    	}
	    }*/
	    
	    /* draw the filled regions */
	    
	    app.stroke(borderColour[0],borderColour[1],borderColour[2]);
	    for (Simplex<Pnt> triangle: dt)
	    {
	    	for (Simplex<Pnt> other: dt.neighbors(triangle)) {
	    		// locate the common edge
	    		List<Set<Pnt>> edges = other.facets();
	    		edges.retainAll(triangle.facets());
	    		Set<Pnt> edge = edges.get(0);
	    		//System.out.println(edge);
	    		
	    		// now get the two points not in the edge
	    		Pnt a = null, b = null;
	    		for(Pnt p: triangle)
	    		{
	    			if (!edge.contains(p)) 
	    			{
	    				a = p;
	    				break;
	    			}
	    		}
	    		for(Pnt p: other)
	    		{
	    			if (!edge.contains(p)) 
	    			{
	    				b = p;
	    				break;
	    			}
	    		}	    		
	    		
	    		Pnt p = Pnt.circumcenter(triangle.toArray(new Pnt[0]));
		        Pnt q = Pnt.circumcenter(other.toArray(new Pnt[0]));
		        Pnt center = p.add(q);
	    		
	    		app.stroke(0,255);
	    		app.line((float)p.coord(0), (float)p.coord(1), 
	    				(float)q.coord(0), (float)q.coord(1));
	    		
	    		app.stroke(0,50);
	    		app.line((float)a.coord(0), (float)a.coord(1), 
	    			(float)center.coord(0)/2, (float)center.coord(1)/2);
	    		

	    		PNPnt pna = ((PNPnt)a);
	    		PNPnt pnb = ((PNPnt)b);
	    		
	    		app.noStroke();
	    		/*
	    		if (pna.pn()!=null)
	    		{	    		
		    		// draw triangles apq, and bpq
		    		app.fill(pna.pn().uniqueValue()*255);
		    		app.triangle((float)p.coord(0), (float)p.coord(1), 
		    				(float)q.coord(0), (float)q.coord(1),
		    				(float)a.coord(0), (float)a.coord(1));
	    		}
	    		*/
	    		/*
	    		if (pnb.pn()!=null)
	    		{
		    		app.fill(pnb.pn().uniqueValue()*255);	    		
		    		app.triangle((float)p.coord(0), (float)p.coord(1), 
		    				(float)q.coord(0), (float)q.coord(1),
		    				(float)b.coord(0), (float)b.coord(1));
	    		}
	    		*/
	    	}
	    }
	    
	        
	    
	    /*
	    app.noStroke();
	    app.fill(highlightColour[0],highlightColour[1],highlightColour[2]);
	    for(Pnt p: activePoints.values())
	    {
	    	app.rect((float)p.coord(0)-0.01f, (float)p.coord(1)-0.01f,.02f,.02f);
	    }
	    */
	    
	    for(PNPnt p: points)
	    {
	    	app.noStroke();
	    	app.fill(255*p.pn().uniqueValue());
	    	app.rect((float)p.coord(0)-0.01f, (float)p.coord(1)-0.01f,.02f,.02f);
	    }
	}
	
	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((float)p.coord(0),(float)p.coord(1),
           		 (float)c.coord(0),(float)c.coord(1));
			drawTreeLines(app, child);
		}
	}

}
