/*

	InteractivePointSet.java
	by Richard Unger, March 1998

	Extends basic point-set to support commands and drawing.

*/


import java.util.StringTokenizer;
import java.util.Random;
import java.awt.event.*;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Enumeration;

final public class InteractivePointSet extends PointSet implements Artiste, CommandExecutor, MouseMotionListener{

	// interface / interaction variables
	CommandParser commander;	// commands object
	Spoint closest = null;		// closest spoint to cursor
	Spoint addC = null;			// point at cursor for LIVEADD mode
	RoboCanvas viewPane;		// drawing canvas
	PointMapper mapper;			// mapping between canvas coordinates and spoints
	Spoint selectHead;			// head of selected spoints, if any...


// constructor

	public InteractivePointSet(CommandParser commands,RoboCanvas canv){
		// creates a drawable point set
		super();

		commander = commands;
		viewPane = canv;
		mapper = canv.GetMapper();
		selectHead = null;
		addC = null;
		
		// register some commands
		commander.AddCommand("add pt <dbl>,<dbl>",this);
		commander.AddCommand("delete all",this);
		commander.AddCommand("delete sel",this);
		commander.AddCommand("add random <int>",this);
		commander.AddCommand("sel pt <dbl>,<dbl>",this);
		commander.AddCommand("sel add <dbl>,<dbl>",this);
		commander.AddCommand("graph <+-> <str>",this);
		
		// add some states
		commander.GetStates().AddState("AUTOUPDATE");
		commander.GetStates().AddState("SHOWINDEX",false);
		
		// start off interactiveness
		viewPane.addMouseMotionListener(this);
		
		}


// command executor

	public String performCommand(String command){
		// perhaps need to implement more stringent checking later
		StringTokenizer toke = new StringTokenizer(command," ,");
		
		if (command.startsWith("add pt")){
			double d1,d2;
			toke.nextToken();
			toke.nextToken();
			
			// add a point...
			d1 = Double.valueOf(toke.nextToken()).doubleValue();
			d2 = Double.valueOf(toke.nextToken()).doubleValue();
			
			try {
				AddPoint(new Spoint(d1,d2));
				if (commander.GetStates().GetStateValue("AUTOUPDATE"))
					commander.SendQuietCommand("update");
				}
			catch (DuplicatePointException e){
				return "Unable to add duplicate pt: "+e.conflicter;
				}
			return null;
			}
		if (command.startsWith("sel")){		// sel pt & sel add
			double d1,d2;
			toke.nextToken();
			toke.nextToken();
			
			if (command.equals("sel clear"))
				ClearSelection();
			else{
				d1 = Double.valueOf(toke.nextToken()).doubleValue();
				d2 = Double.valueOf(toke.nextToken()).doubleValue();
				
				Spoint prox = FindClosestPoint(new Spoint(d1,d2));
				
				if (command.startsWith("sel pt"))
					ClearSelection();
				selectHead = prox.ToggleSelection(selectHead);
				}
			if (commander.GetStates().GetStateValue("AUTOUPDATE"))
				commander.SendQuietCommand("update");
			return null;
			}
		if (command.startsWith("delete all")){
			DeleteAllPoints();
			selectHead = closest = addC = null;
			if (commander.GetStates().GetStateValue("AUTOUPDATE"))
				commander.SendQuietCommand("update");
			return null;
			}
		if (command.startsWith("delete sel")){
			while (selectHead != null){
				Spoint temp = selectHead.selNext;
				DeletePoint(selectHead);
				if (closest == selectHead)
					closest = null;
				selectHead = temp;
				}
			if (commander.GetStates().GetStateValue("AUTOUPDATE"))
				commander.SendQuietCommand("update");
			return null;
			}
		if (command.startsWith("add random")){
			toke.nextToken();
			toke.nextToken();
			AddRandomPoints(Integer.parseInt(toke.nextToken()));
			return null;
			}
		if (command.startsWith("graph")){
			command = command.substring(5).trim();	 // get rid of 'graph'
			boolean plmi = (command.charAt(0)=='+'); // extract +/-
			command = command.substring(1).trim();	 // get rid of +/-

			if (command.equals("nn")){
				SetNNGraph(plmi);
				if (commander.GetStates().GetStateValue("AUTOUPDATE"))
					commander.SendQuietCommand("update");
				return null;
				}
			if (command.equals("cc")){
				SetCCGraph(plmi);
				if (commander.GetStates().GetStateValue("AUTOUPDATE"))
					commander.SendQuietCommand("update");
				return null;
				}
			if (command.equals("si")){
				SetSIGraph(plmi);
				if (commander.GetStates().GetStateValue("AUTOUPDATE"))
					commander.SendQuietCommand("update");
				return null;
				}

			return "No such graph: " + command;
			}
		return null;
		}


// commands implementation

	public void ClearSelection(){
		while (selectHead != null)
			selectHead = selectHead.UnselectPoint(selectHead);
		}
	
	
	public void AddRandomPoints(int howMany){
		// functions adds 'howMany' random points to the point-set.
		// all points generated are in the rectangle (-200,-200,200,200)
		Random rand = new Random();
		double maxw = mapper.AWT2Dist(viewPane.getBounds().width);
		double maxh = mapper.AWT2Dist(viewPane.getBounds().height);
		Spoint viewPaneO = mapper.AWT2Spoint(new Point(viewPane.getBounds().width/2,viewPane.getBounds().height/2));
		
		for (int i=0;i<howMany;i++){
			double x,y;
			x = (rand.nextDouble()-0.5)*maxw + viewPaneO.x;
			y = (rand.nextDouble()-0.5)*maxh + viewPaneO.y;
			try {
				AddPoint(new Spoint(x,y));
				}
			catch (DuplicatePointException e){
				i--;	// insert another point...
				}
			}
		if (commander.GetStates().GetStateValue("AUTOUPDATE"))
			commander.SendQuietCommand("update");
		}


// drawing

	public void doDraw(RoboCanvas c){
		// draw all the linkage...
		
		if ((nn||cc||si) && (numPoints > 1)){
			for (Spoint it = listHead;it != null;it = it.next){
				Point awtIt = mapper.Spoint2AWT(it);
				if (si && (it.siEdges != null)){
					Enumeration e = it.siEdges.elements();
					while (e.hasMoreElements()){
						Spoint oth = (Spoint)e.nextElement();
						viewPane.DrawRedLine(awtIt,mapper.Spoint2AWT(oth));
						}
					}
				if (cc) c.DrawCircle(awtIt,mapper.Dist2AWT(it.Distance(it.closest)));
				if (nn) c.DrawLine(awtIt,mapper.Spoint2AWT(it.closest));
				}
			}
		
		// draw all the points
		if (commander.GetStates().GetStateValue("SHOWINDEX")){
			for (Spoint it = listHead;it != null;it = it.next)
				c.DrawLabledPoint(mapper.Spoint2AWT(it),Long.toString(it.name),it.selected);
			}
		else{
			for (Spoint it = listHead;it != null;it = it.next)
				c.DrawPoint(mapper.Spoint2AWT(it),it.selected);
			}  
		if (closest != null)
			c.DrawPointSelection(mapper.Spoint2AWT(closest));
		}


	public void DrawPoint(Spoint it){
		if (commander.GetStates().GetStateValue("SHOWINDEX"))
			viewPane.DrawLabledPoint(mapper.Spoint2AWT(it),Long.toString(it.name),it.selected);
		else
			viewPane.DrawPoint(mapper.Spoint2AWT(it),it.selected);	
		}


	public void DrawPointAndLinkage(Spoint it){
		// draw the linkage

		Point awtIt = mapper.Spoint2AWT(it);
		if (si && (it.siEdges != null)){
			Enumeration e = it.siEdges.elements();
			while (e.hasMoreElements()){
				Spoint oth = (Spoint)e.nextElement();
				viewPane.DrawRedLine(awtIt,mapper.Spoint2AWT(oth));
				}
			}
		if ((cc || nn) && (it.closest != null)){
			if (cc) viewPane.DrawCircle(awtIt,mapper.Dist2AWT(it.Distance(it.closest)));
			if (nn) viewPane.DrawLine(awtIt,mapper.Spoint2AWT(it.closest));
			DrawPoint(it.closest);
			}
		if ((nn || cc) && (it.imClosest != null)){
			for (int x=0;x<it.imClosest.size();x++){
				Spoint to = (Spoint)it.imClosest.elementAt(x);
				//System.err.println("redrawing line at: "+to);
				if (cc) viewPane.DrawCircle(mapper.Spoint2AWT(to),mapper.Dist2AWT(to.Distance(to.closest)));
				if (nn) viewPane.DrawLine(mapper.Spoint2AWT(to),mapper.Spoint2AWT(it));
				DrawPoint(to);
				}
			}
			
		// draw the point
		DrawPoint(it);
		}
		
	public void doDrawRange(Double from,Double to){
		if (treeRoot == null)
			return;
		Spoint it = (from==null)?listHead:treeRoot.VirtualInsert(new Spoint(from.doubleValue(),0));
		Spoint stopAt = (to==null)?null:treeRoot.VirtualInsert(new Spoint(to.doubleValue(),0));
		if (it.prev != null)
			it = it.prev;
		if (stopAt!=null)
			if (stopAt.next!=null)
				stopAt = stopAt.next;
		if (it == stopAt)
			stopAt = it.next;
		do{
			DrawPointAndLinkage(it);
			it = it.next;
			}while ((it != stopAt));
		if (stopAt!=null)
			DrawPointAndLinkage(stopAt);

		if (closest != null)
			viewPane.DrawPointSelection(mapper.Spoint2AWT(closest));
		}
		


	public void FastUpdateViewPane(Spoint oldClosest,Spoint newClosest){
	// erase the old highlight...

		if (oldClosest != null){
			Point awtOld = mapper.Spoint2AWT(oldClosest);
			Rectangle r = viewPane.EraseRectAroundPoint(awtOld);
			doDrawRange(new Double(mapper.AWT2X(r.x-r.width)),new Double(mapper.AWT2X(r.x+2*r.width)));
			}
		else{
			if (newClosest != null)
				viewPane.DrawPointSelection(mapper.Spoint2AWT(newClosest));
			}
		viewPane.repaint();
		}

		
	public void FastEraseCircle(Spoint it){
		// erase Rect around it...
		// draw the circle
		// draw points and linkage
		if (it.closest == null)
			return;
		double dist = it.Distance(it.closest) + mapper.AWT2Dist(2);
		
		viewPane.EraseThisRect(mapper.Spoint2AWT(it),mapper.Dist2AWT(dist));
		doDrawRange(new Double(it.x-dist-viewPane.POINTMAXRADIUS),new Double(it.x+dist+viewPane.POINTMAXRADIUS));
		}
		
	public void FastDrawCircle(Spoint it){
		if (it.closest == null)
			return;
		double dist = it.Distance(it.closest);
		viewPane.DrawLiveCircle(mapper.Spoint2AWT(it),mapper.Dist2AWT(dist));
		doDrawRange(new Double(it.x-dist-viewPane.POINTMAXRADIUS),new Double(it.x+dist+viewPane.POINTMAXRADIUS));
		}
		
// mouse motion event listener implementation - clicking and dragging in our points...
	 
	public void mouseDragged(MouseEvent e){
	 	
	 	if (commander.GetStates().GetStateValue("SELECTMODE") == false){
	 		}
	 	
	 	}
	 
	 
	public void mouseMoved(MouseEvent e){
	 	
	 	Spoint oldC = closest;
	 	
	 	// if we're in select-mode
	 	if (commander.GetStates().GetStateValue("SELECTMODE") == true){
	 		if (addC!= null){
	 			FastEraseCircle(addC);
	 			addC = null;
	 			}
		 	// find closest point to mouse
		 	Spoint whereIsMouse = mapper.AWT2Spoint(e.getPoint());
		 	Spoint prox = FindClosestPoint(whereIsMouse); 	
		 	closest = prox;
			FastUpdateViewPane(oldC,closest);
		 	}
		 else{
			if (closest != null) {
				closest = null;
				FastUpdateViewPane(oldC,null);
				}
			if (commander.GetStates().GetStateValue("LIVEADD") == true){
			 	Spoint whereIsMouse = mapper.AWT2Spoint(e.getPoint());
			 	Spoint prox = FindClosestPoint(whereIsMouse);
				
				// erase last circle, if any
				if (addC != null){
					FastEraseCircle(addC);
					}
				
				// update new circle,
				if (prox != null){
					addC = whereIsMouse;
					addC.closest = prox;
					FastDrawCircle(addC);
					}
				else
					addC=null;
				viewPane.repaint();
				}
			else{
				if (addC!=null){
		 			FastEraseCircle(addC);
		 			addC = null;
		 			viewPane.repaint();
					}
				}
			}
	 	}


}




/*
Commands supported:
	add pt <dbl>,<dbl>	-  adds the point specified by the euclidean coordinates to pointset
	delete all          -  clears all points
	add random <int>    -  adds <int> points randomly to the set of points
	sel pt <dbl>,<dbl>	-  selects the point closest to specified point, clearing other selections
	delete sel			-  clears the selected points...
	sel add <dbl>,<dbl>	-  adds/removes point closest to specified point from current selection
	sel clear			-  clears the selection...
	graph <+-> <str>	-  if +, will draw the graph in question, if - will not draw the graph
	
States Used:
	AUTOUPDATE - if true, will automatically update the screen...
	SHOWINDEX - if true, will print the name of the points next to the point...
	
*/