/* 

	RoboCanvas.java
	by Francois Belair March 1997, modified April 1997 by Francois Belair & Richard Unger
	modified extensively for adaptation to Spheres of Influence by Richard unger March 1998

	Class for Robot drawing
	- no longer!
		
	Now a class allowing drawing of points and edges at integer coordinates within
	a double-buffered canvas.
	
	The canvas provides automatic double buffering, as well as all the primitive drawing
	drawing methods like DrawPoint and DrawLine. The canvas will handle updating in
	response to need by the AWT simply by updating the canvas from the contents of the
	buffer.
	The buffer is always kept the same size as the canvas. If the canvas is resized, the
	objects automatically allocates a new buffer witht he correct new size.
	When it comes time to redraw everything (information update, rather than jusst updating
	the canvas from the offscreen buffer) the canvas provides a method for everyone
	who wants to draw in it to register themselves with the canvas.
	The canvas will then call the draw method in each of these objects to perform the actual
	update...
	
*/


import java.awt.*;
import java.awt.event.*;
import java.util.Vector;
import java.util.Enumeration;
import java.util.StringTokenizer;

public class RoboCanvas extends Canvas implements CommandExecutor
{

// buffering variables 
	private Dimension bufSize  = null;
	private Image image = null;	
	private Graphics buffer = null;
	private Graphics directBuff;
	private double zoom;
	private Spoint viewOrigin;

// messageing variables
	private Vector artists;
	private CommandParser commander;

// support objects
	private RoboCanvasPointMapper mapper;

// constants
	static final private double MINZOOM = 0.0001;
	static final private double MAXZOOM = 10000;
	static final private double DEFAULTZOOM = 100;

// constructor
	public RoboCanvas(CommandParser commands)
	{
		super();
		
		// setup canvas
		//setBackground(Color.pink);
		bufSize = new Dimension(0, 0);		// initially we have 0 size...
		setFont(new Font("Monaco",Font.PLAIN,9));
		directBuff = getGraphics();
		image = null;
		zoom = DEFAULTZOOM;
		viewOrigin = new Spoint(0,0);
		
		// utility objects
		mapper = new RoboCanvasPointMapper();
		this.addMouseListener(new RoboCanvasMouseAdapter());
		
		// drawing queue
		artists = new Vector(10,10);
		commander = commands;
		commander.AddCommand("update",this);
		commander.AddCommand("zoom <dbl>",this);
		commander.AddCommand("zoom",this);
		commander.AddCommand("translate <dbl>,<dbl>",this);

		//setSize(600,350);
		
		System.out.println("Created canvas...");
		
	}


	public void init(){
		ReallocateBuffer();
		}

// Drawing Dispatch code...


	public void AddArtiste(Artiste who){
		artists.addElement(who);
		}


	public void RemoveArtiste(Artiste who){
		artists.removeElement(who);
		}


	private void DrawLoop(){
		// call each object that is registered to draw...
		Enumeration e = artists.elements();
		
		if (image == null)
			ReallocateBuffer();
		
		// clear buffer here
		EraseBuffer();
		
		while (e.hasMoreElements()){
			Artiste who = (Artiste)e.nextElement();
			who.doDraw(this);
			}
		}


	public void UpdateCanvas(){
		if (getSize().equals(bufSize))	// only redraw if the buffer is valid size...
			DrawLoop();
		repaint();
		}


// Drawing Primitives - plot simple objects to the buffer



	// global variables used by the drawing functions	
	private static final int POINTRADIUS = 2;
	private static final int POINTLABELXOFFSET = 5;
	private static final int POINTLABELYOFFSET = 6;
	private static final int SELECTRADIUS = 3;
	public static final int POINTMAXRADIUS = 10;


		// i'll fill these in as I need them...
	public void DrawPoint(Point p,boolean selected){
		buffer.setColor((selected)?Color.red:Color.orange);
		buffer.fillOval(p.x-POINTRADIUS,p.y-POINTRADIUS,2*POINTRADIUS,2*POINTRADIUS);
		buffer.setColor(Color.black);
		buffer.drawOval(p.x-POINTRADIUS,p.y-POINTRADIUS,2*POINTRADIUS,2*POINTRADIUS);
		}

	public void DrawLabledPoint(Point p,String label,boolean selected){
		DrawPoint(p,selected);
		buffer.drawString(label,p.x+POINTLABELXOFFSET,p.y+POINTLABELYOFFSET);
		}


	public void DrawPointSelection(Point p){
		buffer.setColor(Color.red);
		buffer.drawOval(p.x-POINTRADIUS,p.y-POINTRADIUS,2*POINTRADIUS,2*POINTRADIUS);
		buffer.drawOval(p.x - SELECTRADIUS,p.y-SELECTRADIUS,2*SELECTRADIUS,2*SELECTRADIUS);
		}


	public void DirectDrawPointSelection(Point p){
		directBuff.setColor(Color.red);
		directBuff.drawOval(p.x-POINTRADIUS,p.y-POINTRADIUS,2*POINTRADIUS,2*POINTRADIUS);
		directBuff.drawOval(p.x - SELECTRADIUS,p.y-SELECTRADIUS,2*SELECTRADIUS,2*SELECTRADIUS);
		directBuff.setColor(Color.black);
		}


	public void DrawLine(Point from,Point to){
		buffer.setColor(Color.green);
		buffer.drawLine(from.x,from.y,to.x,to.y);
		}

	public void DrawRedLine(Point from,Point to){
		buffer.setColor(Color.red);
		buffer.drawLine(from.x,from.y,to.x,to.y);
		}
	
	public void DrawLiveCircle(Point p,int radius){
		buffer.setColor(Color.gray);
		buffer.drawOval(p.x-radius,p.y-radius,2*radius,2*radius);
		}

	public void DrawCircle(Point p,int radius){
		buffer.setColor(Color.lightGray);
		buffer.drawOval(p.x-radius,p.y-radius,2*radius,2*radius);
		}
			
	public void DrawAxes(){
	
		}

	public void EraseBuffer(){
		Rectangle r = buffer.getClipBounds();
		buffer.clearRect(r.x,r.y,r.width,r.height);
		}
	
	public Rectangle EraseRectAroundPoint(Point p){
		Rectangle r = new Rectangle(p.x-POINTMAXRADIUS,p.y-POINTMAXRADIUS,2*POINTMAXRADIUS,2*POINTMAXRADIUS);
		buffer.clearRect(r.x,r.y,r.width,r.height);
		return r;
		}
		
	public void EraseThisRect(Point p,int r){
		buffer.clearRect(p.x-r,p.y-r,2*r,2*r);
		}
	

	public Graphics GetBuffer(){
		return buffer;
		}

	
	public PointMapper GetMapper(){
		return mapper;
		}


// canvas methods - keep the canvas up-todate, respond to AWT events, etc...

	private void ReallocateBuffer(){
		//deallocate image
		if (buffer != null) buffer.dispose();
		if (image != null) image.flush();
		
		//reallocate image
		bufSize = getSize();
		image = createImage(bufSize.width,bufSize.height);
		buffer = image.getGraphics();
		buffer.setColor(Color.black);
		
		System.out.println("Reallocating Buffer size: "+bufSize);
		}


	public void update (Graphics g){
		paint(g);
		}

	public void paint(Graphics g)
	{	
		// check if our buffer has the right size, if not, reallocate it
		if (!getSize().equals(bufSize)){
			ReallocateBuffer();
			DrawLoop();
			}

		// copy our buffer onto the screen...
  		g.drawImage(image, 0, 0, null);	
	}


// command code - reacting to commands and sending them out...

	public String performCommand(String command){
		if (command.equals("update")){
			UpdateCanvas();
			return null;
			}
		if (command.equals("zoom")){
			zoom = DEFAULTZOOM;
			UpdateCanvas();
			return null;
			}
		if (command.startsWith("translate")){
			StringTokenizer toke = new StringTokenizer(command," ,");
			toke.nextToken();
			double ox = new Double(toke.nextToken()).doubleValue();
			double oy = new Double(toke.nextToken()).doubleValue();
			viewOrigin = new Spoint(ox,oy);
			UpdateCanvas();
			return null;
			}
		if (command.startsWith("zoom")){
			command = command.substring(4,command.length()).trim();
			Double temp = null;
			try{
				temp = new Double(command);
				}
			catch (NumberFormatException e){
				commander.DisplayCommandError("zoom must be a double value");
				return null;
				}
			if ((temp.doubleValue()<MINZOOM) || (temp.doubleValue()>MAXZOOM))
				return "zoom must be between "+MINZOOM+" and "+MAXZOOM;
			zoom = temp.doubleValue();
			UpdateCanvas();
			return null;
			}
		return null;
		}
		
// inner class to capture mouse events	
	
	public class RoboCanvasMouseAdapter extends MouseAdapter{
	/*
	This fucking function does not work when the thing runs as an applet - debug me!
	*/
	
		public void mouseReleased(MouseEvent e){
			if (commander.GetStates().GetStateValue("SELECTMODE")==false)
				commander.SendCommand("add pt "+mapper.AWT2Spoint(e.getPoint()));
			else
				if (e.isShiftDown())
					commander.SendCommand("sel add "+mapper.AWT2Spoint(e.getPoint()));
				else
					commander.SendCommand("sel pt "+mapper.AWT2Spoint(e.getPoint()));
			}
/*			
		public void mousePressed(MouseEvent e){
			System.err.println("mouse down!");
			}
	
		public void mouseClicked(MouseEvent e){
			}
*/	
		}


// inner class to perform point mapping for canvas...


	public class RoboCanvasPointMapper extends PointMapper {
		
		/*
		This mapper currently maps points so that the origin is in the middle
		of the canvas (simple translation).
		*/
				
		public Point Spoint2AWT(Spoint sp){
			sp = sp.Subtract(viewOrigin);
			return new Point((int)Math.round( (sp.x * zoom) + bufSize.width/2  ),(int)Math.round( (-sp.y * zoom) + bufSize.height/2)   );
			}		
		public Spoint AWT2Spoint(Point p){
			return new Spoint( ((double)p.x - bufSize.width/2)/zoom ,-((double)p.y - bufSize.height/2)/zoom).Add(viewOrigin);
			}

		public double AWT2X(int x){
			return ((double)x - bufSize.width/2)/zoom + viewOrigin.x;
			}
		public int X2AWT(double x){
			x -= viewOrigin.x;
			return (int)Math.round( (x * zoom) + bufSize.width/2 );
			}
		public double AWT2Y(int y){
			return -((double)y - bufSize.height/2)/zoom + viewOrigin.y;
			}
		public int Y2AWT(double y){
			y -= viewOrigin.y;
			return (int)Math.round( (-y * zoom) + bufSize.height/2);
			}
		public int Dist2AWT(double d){
			return (int) (Math.round(d*zoom));
			}
		public double AWT2Dist(double d){
			return d/zoom;
			}

		}

}



/*

Commands implemented by RoboCanvas:

update - this command causes the canvas to update its buffer and draw it to the screen!
zoom <dbl> - sets the zoom-value
zoom	- restores default zoom value
translate <dbl>,<dbl> - sets viewing origin to point specified

Commands emitted by RoboCanvas:
	related to clicking of points - I will have to think about it...


*/