package main;

import geometry.Circle;
import geometry.DoublePoint;
import geometry.DoubleVector;
import geometry.NotACircleException;
import geometry.PenStroke;
import geometry.Vertex;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.jdesktop.swingx.VerticalLayout;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;

import pointstream.GuidePoint;
import pointstream.LaplacianPointStream;
import pointstream.LinearPointStream;
import pointstream.QuadricCirclePointStream;
import pointstream.QuadricPointStream;
import pointstream.GuideStream;
import ui.JSynchronizedCheckBox;
import ui.JSynchronizedComboBox;
import ui.JSynchronizedSlider;

public class ShoeLaceFrame extends JFrame {
  DrawingCanvas canvas;  

  public static final int STROKE_CAP_TYPE = BasicStroke.CAP_ROUND;
  public static final int STROKE_JOIN_TYPE = BasicStroke.JOIN_BEVEL;
  
  public static final int PIXELS_TRAVERSED_PER_MOVE = 1;
  
  private static ShoeLaceFrame instance;
  
  public JLabel streamLabel = new JLabel(" >> "); //Yannick : This cannot contain an empty string, as it will mess up the height of the drawing panel.

  public JSynchronizedSlider responsivenessSlider = new JSynchronizedSlider(JSlider.HORIZONTAL,0,100,50);
  
  public JSynchronizedSlider maxSpeedResponsivenessSlider = new JSynchronizedSlider(JSlider.HORIZONTAL,0,100,10);
  
  public JSynchronizedSlider minSpeedResponsivenessSlider = new JSynchronizedSlider(JSlider.HORIZONTAL,0,100,80);
  
  public JSynchronizedSlider bgImageTransparencySlider = new JSynchronizedSlider(JSlider.HORIZONTAL,0,100,100);
  
  public JSynchronizedSlider guideCurveSampleSlider = new JSynchronizedSlider(JSlider.HORIZONTAL,0,100,30);
  
  public JSynchronizedSlider guideCurveDistanceSampleSlider = new JSynchronizedSlider(JSlider.HORIZONTAL,10,200,20);
  
  public JSynchronizedSlider guideCurvePauseTimeSlider = new JSynchronizedSlider(JSlider.HORIZONTAL,0,1000,500);
  
  public JSynchronizedSlider updateTimeSlider = new JSynchronizedSlider(JSlider.HORIZONTAL,0,3000,0);
  
  public JSynchronizedSlider strokeThicknessSlider = new JSynchronizedSlider(JSlider.HORIZONTAL,0,20,8);
  
  public JSynchronizedComboBox curveTypeComboBox = new JSynchronizedComboBox();
  public JSynchronizedCheckBox drawCurvatureSpeedPointsCheckBox = new JSynchronizedCheckBox("Draw Curvature Speed Points ? ");
  public JSynchronizedCheckBox drawGuideLinesCheckBox = new JSynchronizedCheckBox("Draw Guide Lines  ? ");
  public JSynchronizedCheckBox drawResultLinesCheckBox = new JSynchronizedCheckBox("Draw Resulting Lines  ? ");
  public JSynchronizedCheckBox drawGuidePointsCheckBox = new JSynchronizedCheckBox("Draw Guide Points ? ");
  public JSynchronizedCheckBox drawResultPointsCheckBox = new JSynchronizedCheckBox("Draw Resulting Points ? ");
  public JSynchronizedCheckBox transparentGuideCurveCheckBox = new JSynchronizedCheckBox("Transparent Guide curve ? ");
  public JSynchronizedCheckBox drawBGImageCheckBox = new JSynchronizedCheckBox("Draw Background Image ?");
  public JSynchronizedCheckBox drawLagCheckBox = new JSynchronizedCheckBox("Draw Lag ? ");
  
  public JColorChooser generatedStrokeColorChooser = new JColorChooser(Color.BLACK);
  
  private ShoeLaceFrame() 
  {
    super("ELASTICURVE");
    
    Container container = getContentPane();
    
    JPanel panel = new JPanel();
    panel.setLayout(new GridLayout(1, 2));
    
    panel.add(streamLabel);
    
    container.add(panel, BorderLayout.SOUTH);
    
    JPanel panel2 = new JPanel();
    panel2.setLayout(new VerticalLayout());
    
//    JLabel tempLabel = new JLabel("Stroke Speed : ");
//    JSlider tempSlider = new JSlider(JSlider.VERTICAL, 0,100, 50);
//    //Create the label table
//    Hashtable labelTable = new Hashtable();
//    labelTable.put( new Integer( 0 ), new JLabel("Slow") );
//    labelTable.put( new Integer( 100 ), new JLabel("Fast") );
//    tempSlider.setLabelTable(labelTable);
//    tempSlider.setPaintTicks(true);
//    tempSlider.setMajorTickSpacing(50);
//    tempSlider.setMinorTickSpacing(50);
//    tempSlider.setPaintLabels(true);
//    
//    JPanel panel3 = new JPanel();
//    panel3.add(tempSlider);
//    
//    panel2.add(tempLabel);
//    panel2.add(panel3);

    
    if(GuideStream.USE_TIMING_FOR_GUIDE_CURVE)
    {
	    JLabel slugLabel = new JLabel("Responsiveness [0-1] : ");
	    responsivenessSlider.setPaintTicks(true);
	    responsivenessSlider.setMajorTickSpacing(10);
	    panel2.add(slugLabel);
	    panel2.add(responsivenessSlider);
    }
    
    JLabel bgLabel = new JLabel("Background Image Transparency [0-1] : ");
    bgImageTransparencySlider.setPaintTicks(true);
    bgImageTransparencySlider.setMajorTickSpacing(10);
    panel2.add(bgLabel);
    panel2.add(bgImageTransparencySlider);
    
    if(GuideStream.USE_TIMING_FOR_GUIDE_CURVE)
    {
		JLabel guideCurveSampleLabel = new JLabel("Time between guide curve samples (ms) [0-100] : ");
		guideCurveSampleSlider.setPaintTicks(true);
		guideCurveSampleSlider.setMajorTickSpacing(10);
		guideCurveSampleSlider.setEnabled(true);
		panel2.add(guideCurveSampleLabel);
		panel2.add(guideCurveSampleSlider);
  
	    JLabel guideCurvePauseTimeLabel = new JLabel("Additional Time for Pause to be detected (ms) [0-1000] : ");
	    guideCurvePauseTimeSlider.setPaintTicks(true);
	    guideCurvePauseTimeSlider.setMajorTickSpacing(100);
	    guideCurvePauseTimeSlider.setEnabled(true);
	    panel2.add(guideCurvePauseTimeLabel);
	    panel2.add(guideCurvePauseTimeSlider);
    }
    
    if(GuideStream.USE_DISTANCE_FOR_GUIDE_CURVE)
    {
    	JLabel guideCurveDistanceSampleLabel = new JLabel("Distance between guide curve samples (pixels) [10-200] : ");
    	guideCurveDistanceSampleSlider.setPaintTicks(true);
    	guideCurveDistanceSampleSlider.setMajorTickSpacing(10);
    	guideCurveDistanceSampleSlider.setEnabled(true);
		panel2.add(guideCurveDistanceSampleLabel);
		panel2.add(guideCurveDistanceSampleSlider);
		
		JLabel maxSlugLabel = new JLabel("Max Speed Responsiveness [0-1] : ");
		maxSpeedResponsivenessSlider.setPaintTicks(true);
		maxSpeedResponsivenessSlider.setMajorTickSpacing(10);
	    panel2.add(maxSlugLabel);
	    panel2.add(maxSpeedResponsivenessSlider);
	    
	    JLabel minSlugLabel = new JLabel("Min Speed Responsiveness [0-1] : ");
		minSpeedResponsivenessSlider.setPaintTicks(true);
		minSpeedResponsivenessSlider.setMajorTickSpacing(10);
	    panel2.add(minSlugLabel);
	    panel2.add(minSpeedResponsivenessSlider);
    }
	    
    JLabel updateTimeLabel = new JLabel("Update Time(ms) [0-3000] : ");
    updateTimeSlider.setPaintTicks(true);
    updateTimeSlider.setMajorTickSpacing(100);
    panel2.add(updateTimeLabel);
    panel2.add(updateTimeSlider);
    
    JLabel curveTypeLabel = new JLabel("Curve Type : ");
    curveTypeComboBox.addItem("LINEAR");
    curveTypeComboBox.addItem("QUADRIC");
    curveTypeComboBox.addItem("LAPLACIAN");
    curveTypeComboBox.addItem("CIRCLE");
    curveTypeComboBox.setSelectedIndex(3);
    panel2.add(curveTypeLabel);
    panel2.add(curveTypeComboBox);
    
    if(GuideStream.USE_DISTANCE_FOR_GUIDE_CURVE && GuideStream.USE_CURVATURE_SPEED)
    {
	    drawCurvatureSpeedPointsCheckBox.setSelected(false);
	    panel2.add(drawCurvatureSpeedPointsCheckBox);
    }
	    
	drawGuideLinesCheckBox.setSelected(true);
    panel2.add(drawGuideLinesCheckBox);
    drawResultLinesCheckBox.setSelected(true);
    panel2.add(drawResultLinesCheckBox);
    drawGuidePointsCheckBox.setSelected(true);
    panel2.add(drawGuidePointsCheckBox);
    drawResultPointsCheckBox.setSelected(false);
    panel2.add(drawResultPointsCheckBox);
    drawBGImageCheckBox.setSelected(true);
    panel2.add(drawBGImageCheckBox);
    drawLagCheckBox.setSelected(true);
    panel2.add(drawLagCheckBox);
    //postGenerateCurvesCheckBox.setSelected(false);
    //panel2.add(postGenerateCurvesCheckBox);
    
    transparentGuideCurveCheckBox.setSelected(false);
    panel2.add(transparentGuideCurveCheckBox);
    
    panel2.add(new ClearButton());
    panel2.add(new LoadBackgroundImageButton());
    
    panel2.add(new SaveScreenshotButton());
    panel2.add(new SaveScreenshotToSVGButton());
    panel2.add(new SaveQStreamButton());
    panel2.add(new LoadQStreamButton());
    panel2.add(new SaveCurvesInMayaFormatButton());
    panel2.add(new LoadCurvesInMayaFormatButton());
    
    GridLayout layout = new GridLayout(2,3);
    JPanel movementPad = new JPanel(layout);
    
    movementPad.add(new ZoomButton("Zoom In", ZoomButton.ZOOM_IN));
    movementPad.add(new MoveButton("Move Up", MoveButton.MOVE_UP));
    movementPad.add(new ZoomButton("Zoom Out", ZoomButton.ZOOM_OUT));
    movementPad.add(new MoveButton("Move Left", MoveButton.MOVE_LEFT));
    movementPad.add(new MoveButton("Move Down", MoveButton.MOVE_DOWN));
    movementPad.add(new MoveButton("Move Right", MoveButton.MOVE_RIGHT));
    
    panel2.add(movementPad);
    
    StrokePreviewPanel prevPanel = new StrokePreviewPanel(generatedStrokeColorChooser.getColor(),strokeThicknessSlider.getValue());
    
    JLabel strokeThicknessSliderLabel = new JLabel("Stroke Thickness : ");
    strokeThicknessSlider.setPaintTicks(true);
    strokeThicknessSlider.setMajorTickSpacing(2);
    strokeThicknessSlider.setEnabled(true);
    strokeThicknessSlider.addChangeListener(prevPanel);
    panel2.add(strokeThicknessSliderLabel);
    panel2.add(strokeThicknessSlider);
    
    generatedStrokeColorChooser.removeChooserPanel(generatedStrokeColorChooser.getChooserPanels()[0]);
    generatedStrokeColorChooser.getSelectionModel().addChangeListener(prevPanel);
    generatedStrokeColorChooser.setPreviewPanel(new JPanel());
    panel2.add(generatedStrokeColorChooser);
    panel2.add(prevPanel);
    
    container.add(panel2,BorderLayout.EAST);    
    
    canvas = new DrawingCanvas();
    container.add(canvas);

    addWindowListener(new WindowEventHandler());
    pack();
    setVisible(true);
  }
  
  class WindowEventHandler extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
      System.exit(0);
    }
  }

  public static ShoeLaceFrame getInstance()
  {
	  if(instance == null)
		  instance = new ShoeLaceFrame();
	  
	  return instance;
  }
  
  public String getColorHexString(Color c)
  {
	  String rStr = Integer.toHexString(c.getRed());
	  if(rStr.length() < 2)
		  rStr = "0" + rStr;
	  
	  String gStr = Integer.toHexString(c.getGreen());
	  if(gStr.length() < 2)
		  gStr = "0" + gStr;
	  
	  String bStr = Integer.toHexString(c.getBlue());
	  if(bStr.length() < 2)
		  bStr = "0" + bStr;
	  
	  return rStr + gStr + bStr; 
  }
  
  public void setConsoleText(String s)
  {
	  this.streamLabel.setText(" >> " + s );
  }
  
  public void showMessage(String s)
  {
	  JOptionPane.showMessageDialog(this, s);
  }
  
  public static void main(String arg[]) 
  {
      ShoeLaceFrame.getInstance();
  }

  class StrokePreviewPanel extends JComponent implements ChangeListener {
	  Color curColor;
	  float thickness;
	  
	  public StrokePreviewPanel(Color sColor, float sThickness) 
	  {
		this.curColor = sColor;
		this.thickness = sThickness;
	    setPreferredSize(new Dimension(100, 50));
	  }
	  
	  public void paint(Graphics g) 
	  {
		  Graphics2D g2D = (Graphics2D)g;
		  
		  g2D.setColor(curColor);
		  g2D.setStroke(new BasicStroke(thickness,ShoeLaceFrame.STROKE_CAP_TYPE, ShoeLaceFrame.STROKE_JOIN_TYPE));
		  g2D.drawLine(this.getWidth()/2 - (this.getWidth()/2 - 10), this.getHeight()/2, this.getWidth()/2 + (this.getWidth()/2 - 10), this.getHeight()/2);
	  }
	  
	  public void stateChanged(ChangeEvent arg0) 
	  {
		  if(arg0.getSource().equals(generatedStrokeColorChooser.getSelectionModel()))
		  {
			  curColor = generatedStrokeColorChooser.getColor();
			  repaint();
		  }
		  
		  if(arg0.getSource().equals(strokeThicknessSlider))
		  {
			  thickness = strokeThicknessSlider.getSyncValue();
			  repaint();
		  }
	  }
	}
  
  class MoveButton extends JButton implements ChangeListener
  {
	  private int type;

	  public static final int MOVE_UP = 0;
	  public static final int MOVE_RIGHT = 1;
	  public static final int MOVE_DOWN = 2;
	  public static final int MOVE_LEFT = 3;

	  public MoveButton(String s, int type)
	  {
		  super(s);
		  this.type = type;
		  this.addChangeListener(this);
	  }

	  public void stateChanged(ChangeEvent arg0) 
	  {
		  ButtonModel myModel = this.getModel();

		  if(myModel.isArmed())
		  {
			  switch(type)
			  {
				  case 0:
					  canvas.movingUp = true;
					  break;
				  case 1:
					  canvas.movingRight = true;
					  break;
				  case 2:
					  canvas.movingDown = true;
					  break;
				  case 3:
					  canvas.movingLeft = true;
					  break;
			  }
		  }
		  else
		  {
			  switch(type)
			  {
				  case 0:
					  canvas.movingUp = false;
					  break;
				  case 1:
					  canvas.movingRight = false;
					  break;
				  case 2:
					  canvas.movingDown = false;
					  break;
				  case 3:
					  canvas.movingLeft = false;
					  break;
			  }
		  }
	  }
  }
  
  class ZoomButton extends JButton implements ChangeListener
  {
	private int type;
	
	public static final int ZOOM_IN = 0;
	public static final int ZOOM_OUT = 1;
	
	public ZoomButton(String s, int type)
	{
		super(s);
		this.type = type;
		this.addChangeListener(this);
	}
	  
	public void stateChanged(ChangeEvent arg0) 
	{
		ButtonModel myModel = this.getModel();
		
		if(myModel.isArmed())
		{
			switch(type)
			{
				case 0:
					canvas.zoomingIn = true;
					break;
				case 1:
					canvas.zoomingOut = true;
					break;
			}
		}
		else
		{
			switch(type)
			{
				case 0:
					canvas.zoomingIn = false;
					break;
				case 1:
					canvas.zoomingOut = false;
					break;
			}
		}
	}  
  }
  
  class ClearButton extends JButton implements ActionListener
  {
	  public ClearButton()
	  {
		  super("Clear Surface");
		  this.addActionListener(this);
	  }

	  public void actionPerformed(ActionEvent arg0) 
	  {
		  canvas.clearCanvas();
	  }
  }
  
  class SaveScreenshotButton extends JButton implements ActionListener
  {

	public SaveScreenshotButton()
	{
		super("Save Screenshot");
		this.addActionListener(this);
	}
	  
	public void actionPerformed(ActionEvent arg0) 
	{
		canvas.saveScreenshot();
	}	  
  }
  
  class SaveScreenshotToSVGButton extends JButton implements ActionListener
  {
	  public SaveScreenshotToSVGButton()
	  {
		  super("Save Screenshot to SVG");
		  this.addActionListener(this);
	  }

	  public void actionPerformed(ActionEvent arg0) 
	  {
		  // Get a DOMImplementation.
	      DOMImplementation domImpl =
	          GenericDOMImplementation.getDOMImplementation();
	      
	      // Create an instance of org.w3c.dom.Document.
	      String svgNS = "http://www.w3.org/2000/svg";
	      Document document = domImpl.createDocument(svgNS, "svg", null);

	      // Create an instance of the SVG Generator.
	      SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
	      canvas.paintComponent(svgGenerator);
	      
	      // Finally, stream out SVG to the standard output using
	      // UTF-8 encoding.
	      FileWriter w = null;
			
	      try 
	      {
	    	  w = new FileWriter(new File(System.currentTimeMillis() + ".svg"));
	    	  BufferedWriter writer = new BufferedWriter(w);
		      
		      boolean useCSS = true; // we want to use CSS style attributes
		      svgGenerator.stream(writer, useCSS);
	      } catch (IOException e) {
	    	  // TODO Auto-generated catch block
	    	  e.printStackTrace();
	      }
	      
		  //canvas.paintToSVG(System.currentTimeMillis() + ".svg");
	  }
  }
  
  class SaveCurvesInMayaFormatButton extends JButton implements ActionListener
  {
	public SaveCurvesInMayaFormatButton()
	{
		super("Save Curves In Maya Format");
		this.addActionListener(this);
	}
	
	public void actionPerformed(ActionEvent arg0) 
	{
		canvas.generateSpecificCurve();
		
//		JFileChooser fc = new JFileChooser("./");
//		
//		int returnVal = fc.showSaveDialog(ShoeLaceFrame.this);
//		
//		if (returnVal == JFileChooser.APPROVE_OPTION) 
//        {
//      	  File file = fc.getSelectedFile();
//      	  canvas.getStreamMonitor().savePStreamsAsMayaCurves(file.getPath());
//        }	
	}
  }
  
  class LoadCurvesInMayaFormatButton extends JButton implements ActionListener
  {
	public LoadCurvesInMayaFormatButton()
	{
		super("Load Maya Curves");
		this.addActionListener(this);
	}
	
	public void actionPerformed(ActionEvent arg0) 
	{
		JFileChooser fc = new JFileChooser("./");
		
		int returnVal = fc.showOpenDialog(ShoeLaceFrame.this);
		
		if (returnVal == JFileChooser.APPROVE_OPTION) 
        {
      	  File file = fc.getSelectedFile();
      	  canvas.getStreamMonitor().loadQStreamsFromMayaCurves(file.getPath());
        }	
	}
  }
  
  class SaveQStreamButton extends JButton implements ActionListener
  {
	public SaveQStreamButton()
	{
		super("Save Canvas");
		this.addActionListener(this);
	}
	
	public void actionPerformed(ActionEvent arg0) 
	{
		JFileChooser fc = new JFileChooser("./");
		
		int returnVal = fc.showSaveDialog(ShoeLaceFrame.this);
		
		if (returnVal == JFileChooser.APPROVE_OPTION) 
        {
      	  File file = fc.getSelectedFile();
      	  canvas.getStreamMonitor().saveQStreams(file.getPath());
        }
	}
  }
  
  class LoadBackgroundImageButton extends JButton implements ActionListener
  {
	  public LoadBackgroundImageButton()
	  {
		  super("Load Background Image");
		  this.addActionListener(this);
	  }
	  
	  public void actionPerformed(ActionEvent arg0)
	  {
		  JFileChooser fc = new JFileChooser("./");
		  
		  int returnVal = fc.showOpenDialog(ShoeLaceFrame.this);

          if (returnVal == JFileChooser.APPROVE_OPTION) 
          {
        	  File file = fc.getSelectedFile();
        	  canvas.loadBackgroundImage(file.getPath());
          }
	  }
  }
  
  class LoadQStreamButton extends JButton implements ActionListener
  {
	  public LoadQStreamButton()
	  {
		  super("Load Canvas");
		  this.addActionListener(this);
	  }
	  
	  public void actionPerformed(ActionEvent arg0)
	  {
		  JFileChooser fc = new JFileChooser("./");
		  
		  int returnVal = fc.showOpenDialog(ShoeLaceFrame.this);

          if (returnVal == JFileChooser.APPROVE_OPTION) 
          {
        	  File file = fc.getSelectedFile();
        	  canvas.getStreamMonitor().loadQStreams(file.getPath());
          }
	  }
  }
  
  class DrawingCanvas extends JPanel implements Runnable {

	StreamMonitor streams;
    
    private boolean drawing, editing, zoomingIn, zoomingOut, movingLeft, movingRight, movingUp, movingDown;
    
    private int pointEditingIndex;
	private GuideStream streamEditing;
    
//	private Point lastMousePosition;
	
    private boolean saveScreenshot;
    
    private Thread renderThread,mousePollerThread, pointAdderThread;
    
    private double scaleX,scaleY;
    private double transX,transY;
    private double origX, origY;
    
    private BufferedImage bgImage;
    
    public DrawingCanvas() {    	
    	
      streams = new StreamMonitor();
      
      setPointEditingIndex(-1);
	  setStreamEditing(null);
      
	  editing = false;
      drawing = false;
      zoomingIn = false;
      zoomingOut = false;
      movingLeft = false;
      movingRight = false;
      movingUp = false;
      movingDown = false;
      saveScreenshot = false;
      
      scaleX = 1;//0.5;
      scaleY = 1;//0.5;
      
      transX = 0;
      transY = 0;
      
      this.setIgnoreRepaint(true);
      
      setPreferredSize(new Dimension(1152,768));
      setBackground(Color.white);
      MyMouseListener list = new MyMouseListener();
      addMouseListener(list);
      addMouseMotionListener(list);
      
      AbstractAction keyMapper = new KeyMappingAction();
      
      this.getInputMap().put(KeyStroke.getKeyStroke("1"),"1 Key");
      this.getActionMap().put("1 Key", keyMapper);
      
      addComponentListener(new CanvasComponentListener());
    }

    public synchronized StreamMonitor getStreamMonitor()
    {
    	return this.streams;
    }
    
    public synchronized int getPointEditingIndex() 
    {
		return pointEditingIndex;
	}

	public synchronized void setPointEditingIndex(int pointEditingIndex) 
	{
		this.pointEditingIndex = pointEditingIndex;
	}

	public synchronized GuideStream getStreamEditing() 
	{
		return streamEditing;
	}

	public synchronized void setStreamEditing(GuideStream streamEditing) 
	{
		this.streamEditing = streamEditing;
	}
    
	public synchronized void zoomIn(double factor)
	{
		this.scaleX += factor;
		this.scaleY += factor;
	}
	
	public synchronized void zoomOut(double factor)
	{
		this.scaleX -= factor;
		this.scaleY -= factor;
		
		if(this.scaleX < 0.3)
			this.scaleX = 0.3;
		
		if(this.scaleY < 0.3)
			this.scaleY = 0.3;
	}
	
	public synchronized void move(double dx, double dy)
	{
		this.transX += dx;
		this.transY += dy;
	}
	
	public synchronized AffineTransform getCurrentTransform()
	{
		AffineTransform transform = new AffineTransform();
		
		transform.translate(canvas.getWidth()/2, canvas.getHeight()/2);
		transform.scale(scaleX, scaleY); //Scale about the center of the current viewing frame.
		transform.translate(-canvas.getWidth()/2, -canvas.getHeight()/2);
		
		transform.translate(origX, origY); //Translate to the origin.
		transform.translate(transX, transY); //Translate an additional specified amount.
		
//		System.out.println("Scale : " + scaleX + " , " + scaleY);
//		System.out.println("Trans : " + transform.getTranslateX() + " , " + transform.getTranslateY());
		
		return transform;
	}
	
    public void update(Graphics g) {
    	paint(g);
    }
    
    public void addNotify() {
    	super.addNotify();
        renderThread = new Thread(this);
        renderThread.start();
        mousePollerThread = new Thread(new MousePoller());
        mousePollerThread.start();
        pointAdderThread = new Thread(new PointAdder());
        pointAdderThread.start();
    }
    
	public void run() 
    {	
		long beforeTime, timeDiff, sleep;
        
        while (true) 
        {	
        	beforeTime = System.currentTimeMillis();
        	
            repaint();
            
            timeDiff = System.currentTimeMillis() - beforeTime;
            
            try 
            {
            	
                 if (timeDiff <= 33) 
                 {
                	//System.out.println("Sleep time : " + (33 - timeDiff));
                    Thread.sleep(33 - timeDiff);
                 } 
            } 
            catch (Exception e) {}
        }
	}
	
	/**
	 * Will render to an .svg file specified in path.
	 * @param path
	 */
//	public void paintToSVG(String path)
//	{
//		FileWriter w = null;
//		
//		try 
//		{
//			w = new FileWriter(new File(path));
//		} catch (IOException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//		
//		BufferedWriter writer = new BufferedWriter(w);
//		
//		try {
//			writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> ");
//			writer.newLine();
//			writer.write("<svg width=\"" + this.getWidth() + "\" height=\"" + this.getHeight() + "\" id=\"svg2\" version=\"1.1\">");
//			writer.newLine();
//			
//			//Fill in with what we wish to draw.
//			AffineTransform t = this.getCurrentTransform();
//			writer.write("<g transform=\"translate(" + t.getTranslateX() + "," + t.getTranslateY() + ")\"> ");
//			writer.newLine();
//			
//			writer.write("<g transform=\"scale(" + t.getScaleX() + "," + t.getScaleY() + ")\"> ");
//			writer.newLine();
//			
//			if(drawGuideLinesCheckBox.isSyncSelected())
//				streams.renderQStreamsToSVG(writer, drawGuidePointsCheckBox.isSyncSelected());
//			
//			if(drawResultLinesCheckBox.isSyncSelected())
//				streams.renderPStreamsToSVG(writer, drawResultPointsCheckBox.isSyncSelected());
//			
//			writer.write("</g> ");
//			writer.newLine();
//			
//			writer.write("</g> ");
//			writer.newLine();
//			
//			writer.write("</svg> ");
//			
//			writer.close();
//		} catch (IOException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//	}
	
    public void paintComponent(Graphics g) 
    {
    	super.paintComponent(g);
    	
		Graphics2D g2D;
		BufferedImage myImage = null;
		
		if(this.saveScreenshot)
		{
			myImage = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_ARGB);
			
			g2D = (Graphics2D)myImage.getGraphics();
			g2D.setBackground(Color.WHITE);
		}
		else
			g2D = (Graphics2D)g;//this.getBufferStrategy().getDrawGraphics();
		
	  //Yannick : Set the transform to enable zooming and translation. 
      AffineTransform renderTransform = getCurrentTransform();
      
	  g2D.setTransform(renderTransform);
		
      g2D.setRenderingHint( RenderingHints.KEY_ANTIALIASING,
              RenderingHints.VALUE_ANTIALIAS_ON );
      
      g2D.setStroke(new BasicStroke(strokeThicknessSlider.getSyncValue(),ShoeLaceFrame.STROKE_CAP_TYPE, ShoeLaceFrame.STROKE_JOIN_TYPE));
      
      DoublePoint topLeftCorner = getTransformedPoint(0,0);
      DoublePoint bottomRightCorner = getTransformedPoint(this.getWidth(),this.getHeight());
      
      double screenHeight = Math.abs(bottomRightCorner.getY() - topLeftCorner.getY());
      double screenWidth = Math.abs(bottomRightCorner.getX() - topLeftCorner.getX());
      
      Rectangle2D.Double clearRect = new Rectangle.Double(topLeftCorner.getX(), -topLeftCorner.getY(), screenWidth, screenHeight);//-y to reflect back into pixel plane.     
      
      g2D.setColor(Color.WHITE);
      g2D.fill(clearRect);
      
      if(drawBGImageCheckBox.isSelected() && bgImage != null)
      {
    	  // Draw the background image with the appropriate transparency.
    	  int bgW = bgImage.getWidth(this);
    	  int bgH = bgImage.getHeight(this);
    	  
    	  if(bgW > 0 && bgH > 0)
    	  {
	    	  BufferedImage drawImage = new BufferedImage(bgW, bgH, BufferedImage.TYPE_INT_ARGB);
	    	  Graphics2D imgGraphics = (Graphics2D)drawImage.getGraphics();
	    	  imgGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OUT, ((float)bgImageTransparencySlider.getValue())/100.0f));
	    	  imgGraphics.drawRenderedImage(bgImage,null);
	    	  g2D.drawImage(drawImage, -drawImage.getWidth(this)/2, -drawImage.getHeight(this)/2, this);
    	  }
      }
      
      // Draw the springy connector.
      if(drawLagCheckBox.isSyncSelected())
      {
    	  double maxLength = topLeftCorner.distanceTo(bottomRightCorner);  
    	  getStreamMonitor().renderSpringyConnector(g2D, maxLength);
      }
      
      if(drawGuideLinesCheckBox.isSyncSelected())
      {    	  
    	  getStreamMonitor().renderQStreams(g2D, drawGuidePointsCheckBox.isSyncSelected());
      }
       
      if(drawResultLinesCheckBox.isSyncSelected())
      {
    	  getStreamMonitor().renderPStreams(g2D, drawResultPointsCheckBox.isSyncSelected());
      }
         
      if(this.saveScreenshot)
      {
	      g2D = (Graphics2D)g;//this.getBufferStrategy().getDrawGraphics();
	      g2D.drawImage(myImage, 0, 0, null);
	      
	      boolean saved = false;
	      File f = new File("screenshot" + System.currentTimeMillis() + ".png");
	      try {
			ImageIO.write(myImage, "png", f);
			saved = true;
	      } catch (IOException e) {
	    	  saved = false;
	      }
	      
	      if(saved)
	    	  this.saveScreenshot = false;
      }
      
      g2D.dispose();
    }

    private synchronized void drawEqLine(Graphics2D g2D)
    {
    	DoublePoint piMinus1 = new DoublePoint(-200,0);
    	DoublePoint pi = new DoublePoint(-50,0);
    	DoublePoint qiPlus1 = new DoublePoint(-100,100);
    	DoublePoint midPoint = new DoublePoint((pi.getX() + qiPlus1.getX())/2, (pi.getY() + qiPlus1.getY())/2);
    	
    	DoubleVector v1 = new DoubleVector(pi,qiPlus1);
    	DoubleVector dir1 = v1.perp();
    	dir1.unit();
    	
    	DoubleVector v2 = new DoubleVector(piMinus1, pi);
    	DoubleVector dir2 = v2.perp();
    	dir2.unit();
    	
    	DoublePoint circleCenter = new DoublePoint(0,0);
    	
    	try {
			circleCenter = MathHelper.lineIntersect(midPoint, dir1, pi, dir2);
		} catch (NoIntersectionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    	
		Circle c = new Circle(circleCenter.getX(), circleCenter.getY(), pi.distanceTo(circleCenter));
		Circle c2 = null;
		try {
			c2 = new Circle(piMinus1.getX(), piMinus1.getY(), pi.getX(), pi.getY(), qiPlus1.getX(), qiPlus1.getY());
		} catch (NotACircleException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
//    	double a = 2*qiPlus1.getX() - 2*pi.getX();
//    	double b = pi.getX()*pi.getX() + pi.getY()*pi.getY() - qiPlus1.getX()*qiPlus1.getX() - qiPlus1.getY()*qiPlus1.getY();
//    	double denom = 2*pi.getY() - 2*qiPlus1.getY();
//    	
//    	double x1 = -100.0;
//    	double x2 = 200.0;
//    	
//    	double y1 = (a*x1 + b)/denom;
//    	double y2 = (a*x2 + b)/denom;
    	
    	double x1 = midPoint.getX() + -200.0*dir1.getX();
    	double y1 = midPoint.getY() + -200.0*dir1.getY();
    	
    	double x2 = midPoint.getX() + 200.0*dir1.getX();
    	double y2 = midPoint.getY() + 200.0*dir1.getY();
    	
    	DoublePoint p1 = new DoublePoint(x1,y1);
    	DoublePoint p2 = new DoublePoint(x2,y2);
    	
    	c.renderCircle(g2D, Color.red);
    	c2.renderCircle(g2D, Color.red);
    	
    	g2D.setColor(Color.black);
    	
    	Ellipse2D cir = new Ellipse2D.Double(piMinus1.getX()-2, -piMinus1.getY()-2, 2*2, 2*2);//-y to reflect back into pixel plane. 
		g2D.fill(cir);
    	
    	cir = new Ellipse2D.Double(pi.getX()-2, -pi.getY()-2, 2*2, 2*2);//-y to reflect back into pixel plane. 
		g2D.fill(cir);
		
		cir = new Ellipse2D.Double(qiPlus1.getX()-2, -qiPlus1.getY()-2, 2*2, 2*2);//-y to reflect back into pixel plane. 
		g2D.fill(cir);
		
		Line2D.Double line = new Line2D.Double(p1.getX(),-p1.getY(),p2.getX(),-p2.getY());//-y to reflect back into pixel plane.
		g2D.draw(line);
		
		line = new Line2D.Double(pi.getX(),-pi.getY(),piMinus1.getX(),-piMinus1.getY());//-y to reflect back into pixel plane.
		g2D.draw(line);
    }
    
    public synchronized void generateALotOfCurves()
    {
		for(int i = 0; i < 1000; i++)
		{
			getStreamMonitor().createStreamPair();
			DoublePoint p1 = new DoublePoint(0,i);
			DoublePoint p2 = new DoublePoint(100,i);
			DoublePoint p3 = new DoublePoint(200,i);
			
			getStreamMonitor().addQPoint(p1, 100, 0, i);
			getStreamMonitor().addQPoint(p2, 200, 100, i);
			getStreamMonitor().addQPoint(p3, 300, 200, i);
		}
    }
    
    public synchronized void generateSpecificCurve()
    {
    	if(transparentGuideCurveCheckBox.isSyncSelected())
    		generatedStrokeColorChooser.setColor(Color.MAGENTA);
    	getStreamMonitor().createStreamPair();
		
    	
    	DoublePoint p1 = new DoublePoint(-100,-200);
        DoublePoint p2 = new DoublePoint(0,0);
        DoublePoint p3 = new DoublePoint(50,0);
        DoublePoint p4 = new DoublePoint(100,-50);
        DoublePoint p5 = new DoublePoint(110,-50);
        DoublePoint p6 = new DoublePoint(200,50);
        DoublePoint p7 = new DoublePoint(250,50);
        
        getStreamMonitor().addQPoint(p1,1000, 400, 0);
        getStreamMonitor().addQPoint(p2,2000, 380, 123);
        getStreamMonitor().addQPoint(p3,3000, 323, 235);
        getStreamMonitor().addQPoint(p4,4000, 235, 323); 
        getStreamMonitor().addQPoint(p5,5000, 123, 380);
        getStreamMonitor().addQPoint(p6,6000, 0, 400);
        getStreamMonitor().addQPoint(p7,7000, -123, 380);
    	
//    	DoublePoint p1 = new DoublePoint(-400,-400);
//        DoublePoint p2 = new DoublePoint(-350,-370);
//        DoublePoint p3 = new DoublePoint(-200,-250);
//        DoublePoint p4 = new DoublePoint(-100,-200);
//        DoublePoint p5 = new DoublePoint(-50,-150);
//        DoublePoint p6 = new DoublePoint(0,-100);
//        DoublePoint p7 = new DoublePoint(5, -95);
//        DoublePoint p8 = new DoublePoint(10,-90);
//        DoublePoint p9 = new DoublePoint(15,-85);
//        DoublePoint p10 = new DoublePoint(20,-80);
//        DoublePoint p11 = new DoublePoint(25, -75);
//        DoublePoint p12 = new DoublePoint(35, -70);
//        DoublePoint p13 = new DoublePoint(40,-65);
//        DoublePoint p14 = new DoublePoint(100,50);
//        DoublePoint p15 = new DoublePoint(200,100);
//        DoublePoint p16 = new DoublePoint(300,200);
//        DoublePoint p17 = new DoublePoint(400, 400);
//        
//        getStreamMonitor().addQPoint(p1,1000, 400, 0);
//        getStreamMonitor().addQPoint(p2,2000, 380, 123);
//        getStreamMonitor().addQPoint(p3,3000, 323, 235);
//        getStreamMonitor().addQPoint(p4,4000, 235, 323); 
//        getStreamMonitor().addQPoint(p5,5000, 123, 380);
//        getStreamMonitor().addQPoint(p6,6000, 0, 400);
//        getStreamMonitor().addQPoint(p7,7000, -123, 380);
//        getStreamMonitor().addQPoint(p8,8000, -235, 323);
//        getStreamMonitor().addQPoint(p9,9000, -323, 235);
//        getStreamMonitor().addQPoint(p10,10000, -380, 123);
//        getStreamMonitor().addQPoint(p11,11000, -380, 123);
//        getStreamMonitor().addQPoint(p12,12000, -380, 123);
//        getStreamMonitor().addQPoint(p13,13000, -380, 123);
//        getStreamMonitor().addQPoint(p14,14000, -380, 123);
//        getStreamMonitor().addQPoint(p15,15000, -380, 123);
//        getStreamMonitor().addQPoint(p16,16000, -380, 123);
//        getStreamMonitor().addQPoint(p17,17000, -380, 123);
    	
//    	DoublePoint p1 = new DoublePoint(-400,-400);
//        DoublePoint p2 = new DoublePoint(-350,-200);
//        DoublePoint p3 = new DoublePoint(-200,-300);
//        DoublePoint p4 = new DoublePoint(-100,-200);
//        DoublePoint p5 = new DoublePoint(-50,-150);
//        DoublePoint p6 = new DoublePoint(0,-100);
//        DoublePoint p7 = new DoublePoint(5, -95);
//        DoublePoint p8 = new DoublePoint(10,-90);
//        DoublePoint p9 = new DoublePoint(15,-85);
//        DoublePoint p10 = new DoublePoint(20,-80);
//        DoublePoint p11 = new DoublePoint(25, -75);
//        DoublePoint p12 = new DoublePoint(35, -70);
//        DoublePoint p13 = new DoublePoint(40,-65);
//        DoublePoint p14 = new DoublePoint(100,50);
//        DoublePoint p15 = new DoublePoint(200,100);
//        DoublePoint p16 = new DoublePoint(300,200);
//        DoublePoint p17 = new DoublePoint(400,0);
//        
//        getStreamMonitor().addQPoint(p1,1000, 400, 0);
//        getStreamMonitor().addQPoint(p2,2000, 380, 123);
//        getStreamMonitor().addQPoint(p3,3000, 323, 235);
//        getStreamMonitor().addQPoint(p4,4000, 235, 323); 
//        getStreamMonitor().addQPoint(p5,5000, 123, 380);
//        getStreamMonitor().addQPoint(p6,6000, 0, 400);
//        getStreamMonitor().addQPoint(p7,7000, -123, 380);
//        getStreamMonitor().addQPoint(p8,8000, -235, 323);
//        getStreamMonitor().addQPoint(p9,9000, -323, 235);
//        getStreamMonitor().addQPoint(p10,10000, -380, 123);
//        getStreamMonitor().addQPoint(p11,11000, -380, 123);
//        getStreamMonitor().addQPoint(p12,12000, -380, 123);
//        getStreamMonitor().addQPoint(p13,13000, -380, 123);
//        getStreamMonitor().addQPoint(p14,14000, -380, 123);
//        getStreamMonitor().addQPoint(p15,15000, -380, 123);
//        getStreamMonitor().addQPoint(p16,16000, -380, 123);
//        getStreamMonitor().addQPoint(p17,17000, -380, 123);
//        
//        getStreamMonitor().clearCurrentStreams();
        
//        if(transparentGuideCurveCheckBox.isSyncSelected())
//        {
//	        curveTypeComboBox.setSelectedIndex(0);
//	        generatedStrokeColorChooser.setColor(Color.BLUE);
//	        getStreamMonitor().createStreamPair();
//	        
//	        p1 = new DoublePoint(-400,-400);
//	        p2 = new DoublePoint(-350,-200);
//	        p3 = new DoublePoint(-200,-300);
//	        p4 = new DoublePoint(-100,-200);
//	        p5 = new DoublePoint(-50,-150);
//	        p6 = new DoublePoint(0,-100);
//	        p7 = new DoublePoint(5, -95);
//	        p8 = new DoublePoint(10,-90);
//	        p9 = new DoublePoint(15,-85);
//	        p10 = new DoublePoint(20,-80);
//	        p11 = new DoublePoint(25, -75);
//	        p12 = new DoublePoint(35, -70);
//	        p13 = new DoublePoint(40,-65);
//	        p14 = new DoublePoint(100,50);
//	        p15 = new DoublePoint(200,100);
//	        p16 = new DoublePoint(300,200);
//	        p17 = new DoublePoint(400,0);
//	        
//	        getStreamMonitor().addQPoint(p1,1000, 400, 0);
//	        getStreamMonitor().addQPoint(p2,2000, 380, 123);
//	        getStreamMonitor().addQPoint(p3,3000, 323, 235);
//	        getStreamMonitor().addQPoint(p4,4000, 235, 323); 
//	        getStreamMonitor().addQPoint(p5,5000, 123, 380);
//	        getStreamMonitor().addQPoint(p6,6000, 0, 400);
//	        getStreamMonitor().addQPoint(p7,7000, -123, 380);
//	        getStreamMonitor().addQPoint(p8,8000, -235, 323);
//	        getStreamMonitor().addQPoint(p9,9000, -323, 235);
//	        getStreamMonitor().addQPoint(p10,10000, -380, 123);
//	        getStreamMonitor().addQPoint(p11,11000, -380, 123);
//	        getStreamMonitor().addQPoint(p12,12000, -380, 123);
//	        getStreamMonitor().addQPoint(p13,13000, -380, 123);
//	        getStreamMonitor().addQPoint(p14,14000, -380, 123);
//	        getStreamMonitor().addQPoint(p15,15000, -380, 123);
//	        getStreamMonitor().addQPoint(p16,16000, -380, 123);
//	        getStreamMonitor().addQPoint(p17,17000, -380, 123);
//        }
        
//        DoublePoint p1 = new DoublePoint(400,0);
//        DoublePoint p2 = new DoublePoint(380.4226,123.6068);
//        DoublePoint p3 = new DoublePoint(323.6068,235.1141);
//        DoublePoint p4 = new DoublePoint(235.1141,323.6068);
//        DoublePoint p5 = new DoublePoint(123.6068,380.4226);
//        DoublePoint p6 = new DoublePoint(0,400);
//        DoublePoint p7 = new DoublePoint(-123.6068, 380.4226);
//        DoublePoint p8 = new DoublePoint(-235.1141,323.6068);
//        DoublePoint p9 = new DoublePoint(-323.6068,235.1141);
//        DoublePoint p10 = new DoublePoint(-380.4226,123.6068);
//        DoublePoint p11 = new DoublePoint(-400,0);
//        DoublePoint p12 = new DoublePoint(180,-50);
//        DoublePoint p13 = new DoublePoint(190,-50);
//        DoublePoint p14 = new DoublePoint(200,-50);
//        
//        getStreamMonitor().addQPoint(p1,1000, 400, 0);
//        getStreamMonitor().addQPoint(p2,2000, 380, 123);
//        getStreamMonitor().addQPoint(p3,3000, 323, 235);
//        getStreamMonitor().addQPoint(p4,4000, 235, 323); 
//        getStreamMonitor().addQPoint(p5,5000, 123, 380);
//        getStreamMonitor().addQPoint(p6,6000, 0, 400);
//        getStreamMonitor().addQPoint(p7,7000, -123, 380);
//        getStreamMonitor().addQPoint(p8,8000, -235, 323);
//        getStreamMonitor().addQPoint(p9,9000, -323, 235);
//        getStreamMonitor().addQPoint(p10,10000, -380, 123);
//        getStreamMonitor().addQPoint(p11,11000, -400, 0);
//        getStreamMonitor().addQPoint(p12,1200, 180, -50);
//        getStreamMonitor().addQPoint(p13,1300, 190, -50);
//        getStreamMonitor().addQPoint(p14,1400, 200, -50);
    }
    
    public synchronized void clearCanvas()
    {   	
    	this.getStreamMonitor().clearStreams();
    }
    
    public synchronized void loadBackgroundImage(String path)
    {
      try 
      {
    	  bgImage = ImageIO.read(new File(path));
      } 
      catch (IOException e) {
    	  bgImage = null;
    	  return;
      }
      
      if(bgImage == null)
    	  JOptionPane.showMessageDialog(this, "Only .gif, .jpg, .jpeg and .png formats are supported. No image was loaded.");
    }
    
    public void saveScreenshot()
    {
    	this.saveScreenshot = true;
    }
    
    private synchronized void editCurve(int x, int y)
    {
    	GuideStream editStream = getStreamEditing();
    	int editPointIndex = getPointEditingIndex();
    	
    	if(editStream != null && editPointIndex != -1)
    	{
			DoublePoint newPosit =  getTransformedPoint(x,y);//new DoublePoint(x, canvas.getHeight() - y);
			editStream.setPoint(editPointIndex,newPosit);
			//setPointEditingIndex(newPosit);
    	}
    }
    
    public synchronized DoublePoint getTransformedPoint(int x, int y)
    {
    	// Transforms a pixel position into its associated world coordinates
    	AffineTransform transform = this.getCurrentTransform();
    	Point2D newP = null;
    	
    	try {
			transform.invert();
		} catch (NoninvertibleTransformException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    	
    	newP = transform.transform(new Point2D.Double(x,y), null);
		
    	return new DoublePoint(newP.getX(),-newP.getY());  // Reflect to match up pointing y axis.
    }
    
    public synchronized double getAppropriateMoveDistance()
    {
    	DoublePoint p1 = this.getTransformedPoint(0, 0);
    	DoublePoint p2 = this.getTransformedPoint(PIXELS_TRAVERSED_PER_MOVE, 0);
    	
    	return p1.distanceTo(p2);
    }
    
    class CanvasComponentListener extends ComponentAdapter
    {
    	public void componentResized(ComponentEvent e) 
    	{
    		origX = getWidth()/2.0;
    	    origY = getHeight()/2.0;
    	}
    }
    
    class PointAdder implements Runnable
    {
    	
		public void run() {
			long beforeTime,sleep, timeDiff, updateTime;
			
			updateTime = System.currentTimeMillis();
			
			while(true)
			{
				//System.out.println("Adder Running");
				
	            beforeTime = System.currentTimeMillis();
				
				if((System.currentTimeMillis() - updateTime) > updateTimeSlider.getSyncValue())
		    	{		    		
					//System.out.println("Adding P Point");
					getStreamMonitor().addPPoints();
					//System.out.println("Done Adding P Point");
		    		
		    		updateTime = System.currentTimeMillis();
		    	}
		    	
				timeDiff = System.currentTimeMillis() - beforeTime;
	            //sleep = DELAY - timeDiff;
	            
				//System.out.println("Time Diff : " + timeDiff);
				
	            try 
	            {
	                 if (timeDiff < 2) 
	                 {
	                	//System.out.println("Sleep time : " + (20 - timeDiff));
	                    Thread.sleep(2 - timeDiff);
	                 } 
                } 
	            catch (Exception e) {}
			}
		}	
    }
    
    class MousePoller implements Runnable
    {
		public void run() 
		{		        
			long beforeTime,sleep, timeDiff;	
			
			while(true)
			{	
				//System.out.println("Poller Running");
				
				beforeTime = System.currentTimeMillis();
				
				Point cLoc = canvas.getMousePosition();
				
	        	if(drawing && cLoc != null)
	        	{
	        		//System.out.println("Adding Q Point");
	        		
	        		getStreamMonitor().addQPoint(getTransformedPoint(cLoc.x,cLoc.y),System.currentTimeMillis(), cLoc.x, cLoc.y);
	        		
	        		//System.out.println("Done Adding Q Point");
	        	}
	        	else if(editing && cLoc != null)
	        	{
	        		editCurve(cLoc.x, cLoc.y);
	        	}
	        	else if(zoomingIn)
	        	{
	        		zoomIn(0.01);
	        	}
	        	else if(zoomingOut)
	        	{
	        		zoomOut(0.01);
	        	}
	        	else if(movingLeft)
	        	{
	        		move(getAppropriateMoveDistance(), 0);
	        	}
	        	else if(movingRight)
	        	{
	        		move(-getAppropriateMoveDistance(), 0);
	        	}
	        	else if(movingUp)
	        	{
	        		move(0, getAppropriateMoveDistance());
	        	}
	        	else if(movingDown)
	        	{
	        		move(0, -getAppropriateMoveDistance());
	        	}
	        	
	        	timeDiff = System.currentTimeMillis() - beforeTime;
	            
	            try 
	            {
	                 if (timeDiff < 2) 
	                 {
	                	//System.out.println("Sleep time : " + (20 - timeDiff));
	                    Thread.sleep(2 - timeDiff);
	                 } 
                } 
	            catch (Exception e) {}  
			}
		}
    }
    
    class KeyMappingAction extends AbstractAction
    {
    	public void actionPerformed(ActionEvent arg0) 
    	{	    		
//    		GuidePoint.USE_SPEED_MODIFIED_COLOR = !GuidePoint.USE_SPEED_MODIFIED_COLOR;
    		
    		streams.saveQStreams("Test");
    		streams.loadQStreams("Test.elasti");
  		}
    }
    
    class MyMouseListener extends MouseAdapter implements MouseMotionListener 
    {	
      public void mousePressed(MouseEvent e) 
      {  
    	  requestFocusInWindow();
    	  
    	  if(e.getButton() == MouseEvent.BUTTON1)
    	  {  
    		  drawing = true;
    		  getStreamMonitor().createStreamPair();
    	  }
    	  else if(e.getButton() == MouseEvent.BUTTON3)
    	  {  

    		  getStreamMonitor().clearStream(getTransformedPoint(e.getX(),e.getY()));

    		  //Yannick : No Editing in this version.
    		  //	    		  int cPointIndex = -1;
    		  //	    		  if(cStream != null)
    		  //	    			  cPointIndex = cStream.getClickedPoint(getTransformedPoint(e.getX(),e.getY())/*new DoublePoint(e.getX(), canvas.getHeight() - e.getY())*/);
    		  //	    		  
    		  //	    		  if(cPointIndex != -1)
    		  //	    		  {
    		  //	    			  setPointEditingIndex(cPointIndex);
    		  //	    			  setStreamEditing(cStream);
    		  //	    			  editing = true;
    		  //	    		  }
    	  }
      }
      
      public void mouseReleased(MouseEvent e) 
      {    	  
    	  if(e.getButton() == MouseEvent.BUTTON1)
    	  {
    		  drawing = false;
    		  
    		  getStreamMonitor().clearCurrentStreams();
    	  } 
    	  else if(e.getButton() == MouseEvent.BUTTON3)
    	  {
    		  setPointEditingIndex(-1);
			  setStreamEditing(null);
			  editing = false;
			  zoomingIn = false;
			  zoomingOut = false;
			  movingLeft = false;
			  movingRight = false;
			  movingUp = false;
			  movingDown = false;
    	  }
      }
      
      public void mouseDragged(MouseEvent e) 
      {
      }

      public void mouseMoved(MouseEvent e) 
      {
      }
    }
  }
}


