package pointstream;

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

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

import main.ShoeLaceFrame;

public class QuadricCirclePointStream extends LinearPointStream
{
	private ArrayList<Circle> pointCircles;
	
	private ArrayList<DoublePoint> renderPoints;
	
 	private final boolean DRAW_RENDERING_CIRCLES = false;
//	private final boolean USE_CIRCLE_BLENDING = false;
 	
	public QuadricCirclePointStream(Color c, float thickness, double responsiveness, boolean isSpeedInfluenced)
	{
		super(c,thickness,responsiveness,isSpeedInfluenced);
		pointCircles = new ArrayList<Circle>();
		renderPoints = new ArrayList<DoublePoint>();
		type = CIRCLE_STREAM;
	}
	
	/**
	 * Add a circle c to the circle points list of this Stream.  ONLY used this method if you have not used addPoint(DoublePoint p, Circle c)
	 * @param c
	 */
	private synchronized void addCircle(Circle c)
	{
		if(!this.isValid())
			this.pointCircles.add(c);
	}
	
	/**
	 * Add a point p with associated circle c to this Stream.
	 * @param p
	 * @param c
	 */
	private synchronized void addPoint(GeneratedPoint p, Circle c)
	{
		this.points.add(p);
		this.pointCircles.add(c);
	}
	
	public synchronized void saveRadiuses()
	{
		FileWriter w = null;
		
		try {
			w = new FileWriter(new File("Radius.txt"));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		BufferedWriter writer = new BufferedWriter(w);
		
		int i = 0;
		
		for(Circle c : this.pointCircles)
		{
			try {
				writer.write("" + i + " " + c.getRadius());
				writer.newLine();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			i++;
		}
		
		try {
			writer.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private synchronized boolean isValid()
	{
		if(this.points.size() < 1)
		{
			return true;
		}
		else
		{
			return this.pointCircles.size() >= (this.points.size() - 1);
		}
	}
	
	protected double getCurvature(int i)
	{
		if(i == 0 || i == this.size() - 1)
			return 0.0;
		
		if(i == 1 || i == this.size() - 2)
			if(this.pointCircles.get(i-1).getRadius() == 0)
				return 0;
			else
				return 1.0/this.pointCircles.get(i-1).getRadius();
		
		double radius1 = this.pointCircles.get(i - 2).getRadius();
		double radius2 = this.pointCircles.get(i - 1).getRadius();
		
		double avg = radius1+radius2/2.0;
		
		if(avg == 0.0)
			return 0.0;
		else
			return 1.0/avg;
	}
	
	public synchronized void clearCurve()
	{
		super.clearCurve();
		this.pointCircles.clear();
	}
	
	public synchronized void writeCurveToMayaFormat(BufferedWriter w)
	{
		try {
			
			ArrayList<DoublePoint> renderPoints = this.createRenderPoints();
			
			w.write("1 " + (renderPoints.size() - 1) + " 0 no 3");
			w.newLine();
			w.write(""+renderPoints.size());
			for(int i = 0; i < renderPoints.size(); i++)
			{
				w.write(" " + i);
			}
			w.newLine();
			w.write(""+renderPoints.size());
			w.newLine();
			
			if(this.size() == 1)
			{
				DoublePoint p = renderPoints.get(0);
				w.write(p.getX() + " " + p.getY() + " 0.00000000");
				w.newLine();
				w.write(p.getX() + " " + p.getY() + " 0.00000000");
				w.newLine();
			}
			else
			{
				for(DoublePoint p : renderPoints)
				{
					w.write(p.getX() + " " + p.getY() + " 0.00000000");
					w.newLine();
				}
			}
			
			w.newLine();
			w.write(";");
			w.newLine();
			
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private ArrayList<DoublePoint> createRenderPoints()
	{
		ArrayList<DoublePoint> renderPoints = new ArrayList<DoublePoint>();
		
		renderPoints.add(this.points.get(0));
		renderPoints.add(this.points.get(1));
		
		for(int i = 1; i < this.size() - 1; i++)
		{
			DoublePoint p1 = this.points.get(i);
			DoublePoint p2 = this.points.get(i+1);
			Circle circle = this.pointCircles.get(i-1);
			double cx = circle.getCenterX();
			double cy = circle.getCenterY();
			double cr = circle.getRadius();
			
			if(cr == 0)
			{
				// Linear case.
				//System.out.println("Linear Between : " + p1 + " , " + p2);
				renderPoints.add(p2);
			}
			else
			{
				double p1Angle = circle.anglePosition(p1.getX(), p1.getY());
				double p2Angle = circle.anglePosition(p2.getX(), p2.getY());
				double ANGLE_SAMPLE = 0.05;
				
				if(p1Angle >= 0 && p2Angle >=0)
				{
					//  Both positive, so simple enough to sample.
					if(p1Angle > p2Angle)
					{
						double curAngle = p1Angle;
						while(curAngle > p2Angle)
						{
							curAngle -= ANGLE_SAMPLE;
							if(curAngle > p2Angle)
							{
								//System.out.println("1 : Adding Point : " + circle.pointPosition(curAngle) + " Between : " + p1 + " , " + p2);
								renderPoints.add(circle.pointPosition(curAngle));
							}
						}
					}
					else
					{
						double curAngle = p1Angle;
						while(curAngle < p2Angle)
						{
							curAngle += ANGLE_SAMPLE;
							if(curAngle < p2Angle)
							{
								//System.out.println("2 : Adding Point : " + circle.pointPosition(curAngle) + " Between : " + p1 + " , " + p2);
								renderPoints.add(circle.pointPosition(curAngle));
							}
						}
					}
				}
				else if(p1Angle <= -0 && p2Angle <= -0)
				{
					// Both negative, so simple to sample.
					double r1Angle = 360.0 + p1Angle;
					double r2Angle = 360.0 + p2Angle;
					
					if(r1Angle > r2Angle)
					{
						double curAngle = r1Angle;
						while(curAngle > r2Angle)
						{
							curAngle -= ANGLE_SAMPLE;
							if(curAngle > r2Angle)
							{
								//System.out.println("3 : Adding Point : " + circle.pointPosition(curAngle) + " Between : " + p1 + " , " + p2);
								renderPoints.add(circle.pointPosition(curAngle));
							}
						}
					}
					else
					{
						double curAngle = r1Angle;
						while(curAngle < r2Angle)
						{
							curAngle += ANGLE_SAMPLE;
							if(curAngle < r2Angle)
							{
								//System.out.println("4 : Adding Point : " + circle.pointPosition(curAngle) + " Between : " + p1 + " , " + p2);
								renderPoints.add(circle.pointPosition(curAngle));
							}
						}
					}
				}
				else
				{
					//One position is positive, the other negative, so we need to figure out which direction to go in.
					
					if(p1Angle >= 0.0)
					{
						double r2Angle = 360.0 + p2Angle;
						
						// p1 is the positive one.
						// Find the distance going from piAngle and crossing 0.
						double distanceDir1 = p1Angle + Math.abs(p2Angle);
						//  Find the distance going from piAngle and crossing PI
						double distanceDir2 = (180.0 - p1Angle) + (180.0 + p2Angle);
						
						if(distanceDir1 <= distanceDir2)
						{
							// Want to sample crossing 0.
							double r1Angle = 360.0 + p1Angle;
							
							double curAngle = r1Angle;
							while(curAngle > r2Angle)
							{
								curAngle -= ANGLE_SAMPLE;
								if(curAngle > r2Angle)
								{
									//System.out.println("5 : Adding Point : " + circle.pointPosition(curAngle) + " Between : " + p1 + " , " + p2);
									renderPoints.add(circle.pointPosition(curAngle));
								}
							}
						}
						else
						{
							// Want to sample crossing PI.
							double curAngle = p1Angle;
							while(curAngle < r2Angle)
							{
								curAngle += ANGLE_SAMPLE;
								if(curAngle < r2Angle)
								{
									//System.out.println("6 : Adding Point : " + circle.pointPosition(curAngle) + " Between : " + p1 + " , " + p2);
									renderPoints.add(circle.pointPosition(curAngle));
								}
							}
						}
						
					}
					else
					{
						double r1Angle = 360.0 + p1Angle;
						
						// p2 is the positive one.
						// Find the distance going from piAngle and crossing 0.
						double distanceDir1 = p2Angle + Math.abs(p1Angle);
						//  Find the distance going from piAngle and crossing PI
						double distanceDir2 = (180.0 - p2Angle) + (180.0 + p1Angle);
						
						if(distanceDir1 <= distanceDir2)
						{
							// Want to render crossing 0.
							double r2Angle = 360.0 + p2Angle;
							
							double curAngle = r1Angle;
							while(curAngle < r2Angle)
							{
								curAngle += ANGLE_SAMPLE;
								if(curAngle < r2Angle)
								{
									//System.out.println("7 : Adding Point : " + circle.pointPosition(curAngle) + " Between : " + p1 + " , " + p2);
									renderPoints.add(circle.pointPosition(curAngle));
								}
							}
						}
						else
						{
							// Want to render crossing PI.
							double curAngle = r1Angle;
							while(curAngle > p2Angle)
							{
								curAngle -= ANGLE_SAMPLE;
								if(curAngle > p2Angle)
								{
									//System.out.println("8 : Adding Point : " + circle.pointPosition(curAngle) + " Between : " + p1 + " , " + p2);
									renderPoints.add(circle.pointPosition(curAngle));
								}
							}
						}
					}
				}
				
				//System.out.println("9 : Adding Point : " + p2);
				renderPoints.add(p2);
			}
		}
		
		return renderPoints;
	}
	
//	private synchronized int determineAppropriateMovementDirection(DoublePoint piMinus1, DoublePoint pi, DoublePoint qiPlus1, Circle circle)
//	{
//		double piAngle = circle.anglePosition(pi.getX(), pi.getY());
//		double qiPlus1Angle = circle.anglePosition(qiPlus1.getX(), qiPlus1.getY());
//		
//		DoubleVector piV = new DoubleVector(pi.getX() - circle.getCenterX(), pi.getY() - circle.getCenterY());
//		piV.unit();
//		DoubleVector qiV = new DoubleVector(qiPlus1.getX() - circle.getCenterX(), qiPlus1.getY() - circle.getCenterY());
//		qiV.unit();
//		
//		double angleDelta = Math.acos(piV.dot(qiV));
//		double halfAngleDelta = angleDelta/2.0;
//		
//		DoubleVector piDirection = new DoubleVector(piMinus1, pi);
//		piDirection.unit();
//		DoubleVector piTangent = piV.perp();
//		piTangent.unit();
//		DoubleVector piTangent2 = piV.perp();
//		piTangent2.unit();
//		piTangent2.multiply(-1.0);
//		
//		double piTangentAngle1 = Math.acos(piDirection.dot(piTangent));
//		double piTangentAngle2 = Math.acos(piDirection.dot(piTangent2));
//		
//		
//		
//	}
	
	/**
     * Add a point to this stream using qStream as a guide curve and with the responsiveness provided.  If continueToEnd is true, if q(i+1) does not exist, then q(i+1) = qN. 
     * Returns true if a point was added.
     * @param qStream
     * @param responsiveness
     * @param continueToEnd
     * @return
     */
    public synchronized boolean addPoint(GuideStream qStream, boolean continueToEnd, boolean forceContinueToEnd, int forceContinueToEndQIndex)
    {
    	if(qStream == null || qStream.size() == 0)
    		return false;
    	
    	QuadricCirclePointStream pCircleStream = this;
    	
    	if(qStream.size() > 1 && pCircleStream.size() > 0 /*&& pCircleStream.size() > 1*/)
    	{	
    		int index = pCircleStream.size() - 1;
    		int qIndexUsed = -1;
    		boolean isConnectingPoint = false;
    		DoublePoint pi = pCircleStream.getPoint(index);
//    		DoublePoint piMinus1 = pCircleStream.getPoint(index-1);
    		
    		GuidePoint qiPlus1;
    		
    		if(pCircleStream.getNextIndex() < qStream.size() && !forceContinueToEnd)
    		{
    			qiPlus1 = qStream.getPoint(pCircleStream.getNextIndex());
    			qIndexUsed = pCircleStream.getNextIndex();
    			pCircleStream.incrementNextIndex();
    		}
    		else
    		{
    			if(continueToEnd)
    			{
    				if(forceContinueToEnd)
    				{
    					qiPlus1 = qStream.getPoint(forceContinueToEndQIndex);
    					qIndexUsed = forceContinueToEndQIndex;
    				}
    				else
    				{
    					qiPlus1 = qStream.getPoint(qStream.size()-1);
    					qIndexUsed = qStream.size()-1;
    				}
    				
					isConnectingPoint = true;
    			}
				else
					return false;
    		}
    		
    		Circle circle = null;
			try 
			{
				DoubleVector tangentVector;// = new DoubleVector(piMinus1,pi);
				
				if(this.pointCircles.size() > 0)
				{
					// Get the last circle
					Circle lastCircle = this.pointCircles.get(this.pointCircles.size() - 1);
					if(lastCircle.getRadius() == 0)
					{
						// Linear case, so the following is our tangent vector.
						tangentVector = new DoubleVector(pCircleStream.getPoint(index-1),pi);
					}
					else
					{
						// Non-linear case, so want the perp vector of the vector from the cricle center to piMinus1.
						DoubleVector perpVector = new DoubleVector(new DoublePoint(lastCircle.getCenterX(), lastCircle.getCenterY()), pi);
						tangentVector = perpVector.perp();
					}
					
					circle = new Circle(pi, qiPlus1, tangentVector);
				}
				else
				{
					// No circles yet, so the following is our circle.				
					circle = new Circle(pi.getX(), pi.getY(), qiPlus1.getX(), qiPlus1.getY(), qStream.getPoint(qIndexUsed - 1).getX(), qStream.getPoint(qIndexUsed - 1).getY());
				}
				
				
			} catch (NotACircleException e) 
			{
//				System.out.println("NOT A CIRCLE");
				
				//  Not a circle, so simply add a point linearly.
				
				// YANNICK : THERE IS A BUG I HERE SOMEWHERE WHERE LINEAR CASES AREN'T ADDING POINTS :S
				
//				System.out.println("ADDING POINT !!!");
				boolean added = super.addPoint(qStream, continueToEnd, forceContinueToEnd, forceContinueToEndQIndex);
				if(added)
				{
//					System.out.println("ADDED POINT");
					
					pCircleStream.addCircle(new Circle(0,0,0));
					
					// The previous curve was a line, so need to blend from a circle to a line.
					renderPoints.add(this.points.get(this.points.size() -1));
					return true;
				}
				else
					return false;
			}
    		
    		double piAngle = circle.anglePosition(pi.getX(), pi.getY());
    		double qiPlus1Angle = circle.anglePosition(qiPlus1.getX(), qiPlus1.getY());
    		
    		DoubleVector piV = new DoubleVector(pi.getX() - circle.getCenterX(), pi.getY() - circle.getCenterY());
    		piV.unit();
    		DoubleVector qiV = new DoubleVector(qiPlus1.getX() - circle.getCenterX(), qiPlus1.getY() - circle.getCenterY());
    		qiV.unit();
    		
    		double angleDelta = Math.acos(piV.dot(qiV));
    		// Convert to degrees.
    		angleDelta = (angleDelta*360.0)/(2.0*Math.PI);
    		
    		//  Find the next angle.
    		double factor = 1.0;
    		if(piAngle >= 0.0 && qiPlus1Angle >= 0.0)
    		{
    			//Both positive positions, so easy to figure out factor.
    			if(piAngle > qiPlus1Angle)
    				factor = -1.0;
    			else
    				factor = 1.0;
    		}
    		else if (piAngle <= -0.0 && qiPlus1Angle <= -0.0)
    		{
    			//Both negative positions, so easy to figure out factor.
    			if(piAngle > qiPlus1Angle)
    				factor = -1.0;
    			else
    				factor = 1.0;
    		}
    		else
    		{
    			//One position is positive, the other negative.
    			if(piAngle >= 0.0)
    			{
    				// piAngle is positive one, so have to reach qiPlus1Angle.
    				// Find the distance going from piAngle and crossing 0.
    				double distanceDir1 = piAngle + Math.abs(qiPlus1Angle);
    				//  Find the distance going from piAngle and crossing PI
    				double distanceDir2 = (180 - piAngle) + (180 + qiPlus1Angle);
    				
    				//System.out.println("D1 : " + distanceDir1 + " D2 : " + distanceDir2);
    				
    				if(distanceDir1 <= distanceDir2)
    					factor = -1.0f;
    				else
    					factor = 1.0f;
    					
    			}
    			else
    			{
    				// qiPlus1Angle is positive one, so have to reach piAngle.
    				
    				// Find the distance going from piAngle and crossing 0.
    				double distanceDir1 = Math.abs(piAngle) + qiPlus1Angle;
    				
    				//  Find the distance going from piAngle and crossing PI
    				double distanceDir2 = (180 + piAngle) + (180 - qiPlus1Angle);
    				
    				if(distanceDir1 <= distanceDir2)
    					factor = 1.0f;
    				else
    					factor = -1.0f;
    			}
    			
    		}
    		
    		double resultAngle;
    		double neededResponsiveness = this.getAppropriateResponsiveness(qiPlus1);
    		
    		if(piAngle >= 0.0)
    			resultAngle = piAngle + factor*neededResponsiveness*angleDelta;
    		else
    			resultAngle = (360 + piAngle) + factor*neededResponsiveness*angleDelta;
    		
    		if(resultAngle < 0)
    			resultAngle += 360;
    		
    		if(resultAngle > 360)
    			resultAngle -= 360;
    		
    		// Convert to radians.
    		resultAngle = (resultAngle*2*Math.PI)/360;
    		
    		double resultX = circle.getCenterX() + circle.getRadius()*Math.cos(resultAngle);
    		double resultY = circle.getCenterY() + circle.getRadius()*Math.sin(resultAngle); 		
    		
    		DoublePoint result = new DoublePoint(resultX,resultY);
    		
			if(result.distanceTo(pi) >= DISTANCE_THRESHOLD)
			{
				if(qIndexUsed == -1)
    				System.out.println("Bad New Bears...");
				
				lastGuidePoint = qiPlus1; 
				//lastGuidePoint = qStream.getPoint(qIndexUsed+1);
				
//				if(USE_CIRCLE_BLENDING)
//				{
//					// Must add the render points associated with this new arc.
//					
//					if(this.size() == 2 || this.pointCircles.get(this.pointCircles.size() - 1).getRadius() == 0)
//					{
//						// The previous curve was a line, so need to blend a line to a circle.
//						renderPoints.add(result);
//					}
//					else
//					{
//						System.out.println("Adding render points between two circles");
//						Circle prevCircle = this.pointCircles.get(this.pointCircles.size() - 1);
//						Circle curCircle = circle;
//						
//						double prevAnglePosition = prevCircle.anglePosition(pi.getX(), pi.getY());
//						double curAnglePosition = piAngle;
//						
//						DoublePoint prevPointPosition = pi;
//						DoublePoint curPointPosition = pi;
//						double resultPointX,resultPointY;
//						
//						double stepSize = 0.01;
//						double blendFactor, stepAnglePrev, stepAngleCur;
//						for(double i = 0.0; i <= 1.0; i+=stepSize)
//						{
//							// NEED TO TAKE INTO ACCOUNT DIRECTIONS !!!!
//							stepAnglePrev = prevAnglePosition + i*(angleDelta);
//							stepAngleCur = curAnglePosition + i*(angleDelta);
//							
//							prevPointPosition = prevCircle.pointPosition(stepAnglePrev);
//							curPointPosition = curCircle.pointPosition(stepAngleCur);
//							
//							blendFactor = (i*i - 1)*(i*i - 1);
//							
//							resultPointX = blendFactor*prevPointPosition.getX() + (1.0-blendFactor)*curPointPosition.getX();
//							resultPointY = blendFactor*prevPointPosition.getY() + (1.0-blendFactor)*curPointPosition.getY();
//							
//							renderPoints.add(new DoublePoint(resultPointX, resultPointY));
//						}
//					}
//					
//					System.out.println("RenderPoints size : " + this.renderPoints.size());
//				}
				
				pCircleStream.addPoint(new GeneratedPoint(result, this.thickness/2.0f, qIndexUsed, isConnectingPoint), circle);
				
				return true;
			}
			else
				return false;
    	}
    	else
    	{
    		if(this.size() < 1)
    		{
    			lastGuidePoint = qStream.getPoint(0); 
    			
    			this.addPoint(new GeneratedPoint(qStream.getPoint(0), this.thickness/2.0f, 0,false));
    			this.incrementNextIndex();
    			this.incrementNextIndex();
    			return true;
    		}
    		else
    			return false;
    		
//    		if(pCircleStream.size() < 2)
//    		{
//    			boolean added = super.addPoint(qStream, continueToEnd, forceContinueToEnd, forceContinueToEndQIndex);
//    			if(added)
//    			{
//    				renderPoints.add(this.points.get(this.points.size() -1));
//    				return true;
//    			}
//    			else
//    				return false;
//    		}
//    		else
//    			return false;
    	}
    }
	
    public synchronized void render(Graphics2D g2D, boolean drawPoints)
    {
//    	if(USE_CIRCLE_BLENDING)
//    		renderWithBlending(g2D, drawPoints);
//    	else
    		renderNoBlending(g2D, drawPoints);
    		
//    	render1(g2D, drawPoints);
//    	render2(g2D, drawPoints);
    }
    
//    public synchronized void renderWithBlending(Graphics2D g2D, boolean drawPoints)
//    {
//    	Color old = g2D.getColor();
//		Stroke befStroke = g2D.getStroke();
//		
//		g2D.setColor(streamColor);
//		g2D.setStroke(new BasicStroke(this.thickness, ShoeLaceFrame.STROKE_CAP_TYPE, ShoeLaceFrame.STROKE_JOIN_TYPE));
//		
//		if(this.renderPoints.size() > 1)
//		{
//			GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
//			DoublePoint p1, p2;
//			Line2D.Double line;
//			
//			//TODO: Yannick : Need to add connecting curve rendering.
//			
//			for(int i = 0; i < renderPoints.size() - 1; i++)
//			{
//				p1 = this.renderPoints.get(i);
//				p2 = this.renderPoints.get(i+1);
//				line = new Line2D.Double(p1.getX(),-p1.getY(),p2.getX(),-p2.getY()); //-y to reflect back into pixel plane.
//				
//				path.append(line, false);  //g2D.draw(line);
//			}
//			
//			g2D.draw(path);
//		}
//		else
//		{
//			if(this.renderPoints.size() > 0)
//			{
//				DoublePoint p1 = this.renderPoints.get(0);
//				Line2D.Double line = new Line2D.Double(p1.getX(),-p1.getY(),p1.getX(),-p1.getY());//-y to reflect back into pixel plane.
//				g2D.draw(line);
//			}
//		}
//		
//		if(drawPoints)
//		{
//			int i = 0;
//			for(GeneratedPoint p : points)
//			{
//				if(i < this.size())
//					p.render(g2D);
//				i++;
//			}
//		}
//		
//		g2D.setStroke(befStroke);
//		g2D.setColor(old);
//    }
    
	public synchronized void renderNoBlending(Graphics2D g2D, boolean drawPoints)
	{
		Color old = g2D.getColor();
		
		if(DRAW_RENDERING_CIRCLES)
		{
			g2D.setColor(Color.ORANGE);
			for(Circle c : pointCircles)
			{
				Ellipse2D rCircle = new Ellipse2D.Double(c.getCenterX() - c.getRadius(), -(c.getCenterY() + c.getRadius()), 2*c.getRadius(), 2*c.getRadius()); //-y to reflect back into pixel plane.
				g2D.draw(rCircle);
			}
		}
		
		Stroke befStroke = g2D.getStroke();
		
		g2D.setColor(streamColor);
		g2D.setStroke(new BasicStroke(this.thickness, ShoeLaceFrame.STROKE_CAP_TYPE, ShoeLaceFrame.STROKE_JOIN_TYPE));
		
		boolean connectToPath = false;
		
		if(points.size() > 1)
		{	
			GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
			
			DoublePoint p1 = this.points.get(0);
			DoublePoint p2 = this.points.get(1);
//			Line2D.Double line = new Line2D.Double(p1.getX(),-p1.getY(),p2.getX(),-p2.getY());//-y to reflect back into pixel plane.
			
			if(DRAW_CONNECTING_CURVE)
			{
				float dash1[] = {20.0f};
				BasicStroke dashed = new BasicStroke(this.thickness, ShoeLaceFrame.STROKE_CAP_TYPE, ShoeLaceFrame.STROKE_JOIN_TYPE, 20.0f, dash1, 0.0f);
				Color oColor = new Color(streamColor.getRed(),streamColor.getGreen(), streamColor.getBlue(), streamColor.getAlpha()/2);
				
//				DoublePoint lastPoint = this.getPoint(this.size() - 1);
//				DoublePoint befLastPoint = this.getPoint(this.size() - 2);
//				Circle c = null;
//				try {
//					c = new Circle(lastPoint.getX(), lastPoint.getY(), befLastPoint.getX(), befLastPoint.getY(), lastGuidePoint.getX(), lastGuidePoint.getY());
//				} catch (NotACircleException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
//				
//				this.drawArcBetween(g2D, c, lastPoint, lastGuidePoint,oColor, dashed);
					
//				if(points.size() == 2)
//				{
//					this.drawArcBetween(g2D, new Circle(0,0,0), p1, lastGuidePoint,oColor, dashed);
//				}
//				else
//				{		
					
					Circle circleToUse;
					
					try
					{
//						if(this.pointCircles.size() < this.size() - 2)
							circleToUse = this.pointCircles.get(this.size() - 2);
//						else
//							circleToUse = this.pointCircles.get(this.size() - 3);
						
						this.drawArcBetween(g2D, circleToUse, this.points.get(this.size() - 1), lastGuidePoint,oColor, dashed);
					}
					catch(Exception e)
					{
						System.out.println(this.pointCircles.size());
						System.out.println(this.points.size());
						System.out.println(this.size());
						e.printStackTrace();
					}
//				}
			}
			
//			path.append(line, connectToPath);
			
			for(int i = 0; i < this.size() - 1; i++)
			{
				p1 = this.points.get(i);
				p2 = this.points.get(i+1);
				Circle circle;
				
//				if(i < this.pointCircles.size())
					circle = this.pointCircles.get(i);
//				else
//					circle = this.pointCircles.get(i-1);
				
				double cx = circle.getCenterX();
				double cy = circle.getCenterY();
				double cr = circle.getRadius();
				
				if(cr == 0)
				{
					//  Linear case.
					Line2D.Double line = new Line2D.Double(p1.getX(),-p1.getY(),p2.getX(),-p2.getY()); //-y to reflect back into pixel plane.
					
					path.append(line, connectToPath);
					
					continue;
				}
				
				double p1Angle = circle.anglePosition(p1.getX(), p1.getY());
				double p2Angle = circle.anglePosition(p2.getX(), p2.getY());
				
				Shape s;
				
				if(p1Angle >= 0 && p2Angle >=0)
				{
					//  Both positive, so simple enough to render.
					if(p1Angle > p2Angle)
						s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p2Angle, p1Angle-p2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
					else
						s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p1Angle, p2Angle-p1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
					
					path.append(s, connectToPath);
				}
				else if(p1Angle <= -0 && p2Angle <= -0)
				{
					// Both negative, so simple to render.
					double r1Angle = 360.0 + p1Angle;
					double r2Angle = 360.0 + p2Angle;
					
					if(p1Angle > p2Angle)
						s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r2Angle, r1Angle-r2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
					else
						s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r1Angle, r2Angle-r1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.

					path.append(s,connectToPath);
				}
				else
				{
					//One position is positive, the other negative, so we need to figure out which direction to go in.
					
					if(p1Angle >= 0.0)
					{
						double r2Angle = 360.0 + p2Angle;
						
						// p1 is the positive one.
						// Find the distance going from piAngle and crossing 0.
	    				double distanceDir1 = p1Angle + Math.abs(p2Angle);
	    				//  Find the distance going from piAngle and crossing PI
	    				double distanceDir2 = (180.0 - p1Angle) + (180.0 + p2Angle);
	    				
	    				if(distanceDir1 <= distanceDir2)
	    				{
	    					// Want to render crossing 0.
	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r2Angle, 360.0 - r2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
	    					path.append(s, connectToPath);
	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 0.0, p1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
	    					path.append(s, connectToPath);
	    				}
	    				else
	    				{
	    					// Want to render crossing PI.
	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 180.0, r2Angle - 180.0, Arc2D.OPEN);//-y to reflect back into pixel plane.
	    					path.append(s, connectToPath);
	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p1Angle, 180.0 - p1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
	    					path.append(s, connectToPath);
	    				}
	    				
					}
					else
					{
						double r1Angle = 360.0 + p1Angle;
						
						// p2 is the positive one.
						// Find the distance going from piAngle and crossing 0.
	    				double distanceDir1 = p2Angle + Math.abs(p1Angle);
	    				//  Find the distance going from piAngle and crossing PI
	    				double distanceDir2 = (180.0 - p2Angle) + (180.0 + p1Angle);
	    				
	    				if(distanceDir1 <= distanceDir2)
	    				{
	    					// Want to render crossing 0.
	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r1Angle, 360.0 - r1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
	    					path.append(s, connectToPath);
	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 0.0, p2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
	    					path.append(s, connectToPath);
	    				}
	    				else
	    				{
	    					// Want to render crossing PI.
	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 180.0, r1Angle - 180.0, Arc2D.OPEN);//-y to reflect back into pixel plane.
	    					path.append(s, connectToPath); //path.append(s, false);
	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p2Angle, 180.0 - p2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
	    					path.append(s, connectToPath);
	    				}
					}
				}
				
				
			}
			
			g2D.draw(path);
		}
		else
		{
			if(this.points.size() > 0)
			{
				DoublePoint p1 = this.points.get(0);
				Line2D.Double line = new Line2D.Double(p1.getX(),-p1.getY(),p1.getX(),-p1.getY());//-y to reflect back into pixel plane.
				g2D.draw(line);
			}
		}
		
		if(drawPoints)
		{
			for(GeneratedPoint p : points)
			{
				p.render(g2D);
			}
		}
		
		g2D.setStroke(befStroke);
		g2D.setColor(old);
	}
	
	public synchronized void drawArcBetween(Graphics2D g2D, Circle circle, DoublePoint p1, DoublePoint p2, Color c, Stroke stroke)
	{
		GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
		Shape s;
		
		Color oldC = g2D.getColor();
		Stroke oldS = g2D.getStroke();
		
		double cx = circle.getCenterX();
		double cy = circle.getCenterY();
		double cr = circle.getRadius();
		
		if(cr == 0)
		{
			//  Linear case.
			Line2D.Double line = new Line2D.Double(p1.getX(),-p1.getY(),p2.getX(),-p2.getY()); //-y to reflect back into pixel plane.
			
			g2D.setColor(c);
			g2D.setStroke(stroke);
			g2D.draw(line);
			g2D.setColor(oldC);
			g2D.setStroke(oldS);
			
			return;
		}
		
		double p1Angle = circle.anglePosition(p1.getX(), p1.getY());
		double p2Angle = circle.anglePosition(p2.getX(), p2.getY());
		
		if(p1Angle >= 0 && p2Angle >=0)
		{
			//  Both positive, so simple enough to render.
			if(p1Angle > p2Angle)
				s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p2Angle, p1Angle-p2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
			else
				s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p1Angle, p2Angle-p1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
			
			path.append(s, false);
		}
		else if(p1Angle <= -0 && p2Angle <= -0)
		{
			// Both negative, so simple to render.
			double r1Angle = 360.0 + p1Angle;
			double r2Angle = 360.0 + p2Angle;
			
			if(p1Angle > p2Angle)
				s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r2Angle, r1Angle-r2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
			else
				s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r1Angle, r2Angle-r1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.

			path.append(s,false);
		}
		else
		{
			//One position is positive, the other negative, so we need to figure out which direction to go in.
			
			if(p1Angle >= 0.0)
			{
				double r2Angle = 360.0 + p2Angle;
				
				// p1 is the positive one.
				// Find the distance going from piAngle and crossing 0.
				double distanceDir1 = p1Angle + Math.abs(p2Angle);
				//  Find the distance going from piAngle and crossing PI
				double distanceDir2 = (180.0 - p1Angle) + (180.0 + p2Angle);
				
				if(distanceDir1 <= distanceDir2)
				{
					// Want to render crossing 0.
					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r2Angle, 360.0 - r2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
					path.append(s, false);
					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 0.0, p1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
					path.append(s, false);
				}
				else
				{
					// Want to render crossing PI.
					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 180.0, r2Angle - 180.0, Arc2D.OPEN);//-y to reflect back into pixel plane.
					path.append(s, false);
					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p1Angle, 180.0 - p1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
					path.append(s, false);
				}
				
			}
			else
			{
				double r1Angle = 360.0 + p1Angle;
				
				// p2 is the positive one.
				// Find the distance going from piAngle and crossing 0.
				double distanceDir1 = p2Angle + Math.abs(p1Angle);
				//  Find the distance going from piAngle and crossing PI
				double distanceDir2 = (180.0 - p2Angle) + (180.0 + p1Angle);
				
				if(distanceDir1 <= distanceDir2)
				{
					// Want to render crossing 0.
					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r1Angle, 360.0 - r1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
					path.append(s, false);
					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 0.0, p2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
					path.append(s, false);
				}
				else
				{
					// Want to render crossing PI.
					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 180.0, r1Angle - 180.0, Arc2D.OPEN);//-y to reflect back into pixel plane.
					path.append(s, false); //path.append(s, false);
					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p2Angle, 180.0 - p2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
					path.append(s, false);
				}
			}
		}
		
		g2D.setColor(c);
		g2D.setStroke(stroke);
		g2D.draw(path);
		g2D.setColor(oldC);
		g2D.setStroke(oldS);
	}
	
//	public synchronized void render2(Graphics2D g2D, boolean drawPoints)
//	{
//		Color old = g2D.getColor();
//
//		g2D.setColor(new Color(1,1,0,0.25f));
//		
//		boolean connectToPath = false;
//		
//		if(points.size() > 2)
//		{	
//			GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
//			
//			for(int i = 0; i < this.size() - 1; i++)
//			{
//				DoublePoint p1; 
//				DoublePoint p2; 
//				DoublePoint p3;
//				if(i == 0)
//				{
//					p1 = this.points.get(i);
//					p2 = this.points.get(i+1);
//					p3 = this.points.get(i+2);
//				}
//				else
//				{
//					p1 = this.points.get(i);
//					p2 = this.points.get(i+1);
//					p3 = this.points.get(i-1);
//				}
//					
//				Circle circle;
//				try {
//					circle = new Circle(p1.getX(), p1.getY(), p2.getX(), p2.getY(), p3.getX(), p3.getY());
//				} catch (NotACircleException e) {
//					//  Linear case.
//					Line2D.Double line = new Line2D.Double(p1.getX(),-p1.getY(),p2.getX(),-p2.getY()); //-y to reflect back into pixel plane.
//					
//					path.append(line, connectToPath);
//					
//					continue;
//				}
//				
//				double cx = circle.getCenterX();
//				double cy = circle.getCenterY();
//				double cr = circle.getRadius();
//				
//				double p1Angle = circle.anglePosition(p1.getX(), p1.getY());
//				double p2Angle = circle.anglePosition(p2.getX(), p2.getY());
//				
//				Shape s;
//				
//				if(p1Angle >= 0 && p2Angle >=0)
//				{
//					//  Both positive, so simple enough to render.
//					if(p1Angle > p2Angle)
//						s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p2Angle, p1Angle-p2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
//					else
//						s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p1Angle, p2Angle-p1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
//					
//					path.append(s, connectToPath);
//				}
//				else if(p1Angle <= -0 && p2Angle <= -0)
//				{
//					// Both negative, so simple to render.
//					double r1Angle = 360.0 + p1Angle;
//					double r2Angle = 360.0 + p2Angle;
//					
//					if(p1Angle > p2Angle)
//						s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r2Angle, r1Angle-r2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
//					else
//						s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r1Angle, r2Angle-r1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
//
//					path.append(s,connectToPath);
//				}
//				else
//				{
//					//One position is positive, the other negative, so we need to figure out which direction to go in.
//					
//					if(p1Angle >= 0.0)
//					{
//						double r2Angle = 360.0 + p2Angle;
//						
//						// p1 is the positive one.
//						// Find the distance going from piAngle and crossing 0.
//	    				double distanceDir1 = p1Angle + Math.abs(p2Angle);
//	    				//  Find the distance going from piAngle and crossing PI
//	    				double distanceDir2 = (180.0 - p1Angle) + (180.0 + p2Angle);
//	    				
//	    				if(distanceDir1 <= distanceDir2)
//	    				{
//	    					// Want to render crossing 0.
//	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r2Angle, 360.0 - r2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
//	    					path.append(s, connectToPath);
//	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 0.0, p1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
//	    					path.append(s, connectToPath);
//	    				}
//	    				else
//	    				{
//	    					// Want to render crossing PI.
//	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 180.0, r2Angle - 180.0, Arc2D.OPEN);//-y to reflect back into pixel plane.
//	    					path.append(s, connectToPath);
//	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p1Angle, 180.0 - p1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
//	    					path.append(s, connectToPath);
//	    				}
//	    				
//					}
//					else
//					{
//						double r1Angle = 360.0 + p1Angle;
//						
//						// p2 is the positive one.
//						// Find the distance going from piAngle and crossing 0.
//	    				double distanceDir1 = p2Angle + Math.abs(p1Angle);
//	    				//  Find the distance going from piAngle and crossing PI
//	    				double distanceDir2 = (180.0 - p2Angle) + (180.0 + p1Angle);
//	    				
//	    				if(distanceDir1 <= distanceDir2)
//	    				{
//	    					// Want to render crossing 0.
//	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, r1Angle, 360.0 - r1Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
//	    					path.append(s, connectToPath);
//	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 0.0, p2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
//	    					path.append(s, connectToPath);
//	    				}
//	    				else
//	    				{
//	    					// Want to render crossing PI.
//	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, 180.0, r1Angle - 180.0, Arc2D.OPEN);//-y to reflect back into pixel plane.
//	    					path.append(s, connectToPath); //path.append(s, false);
//	    					s = new Arc2D.Double(cx - cr, -(cy + cr), 2*cr, 2*cr, p2Angle, 180.0 - p2Angle, Arc2D.OPEN);//-y to reflect back into pixel plane.
//	    					path.append(s, connectToPath);
//	    				}
//					}
//				}
//				
//				
//			}
//			
//			g2D.draw(path);
//		}
//		else
//		{
//			if(this.points.size() > 0)
//			{
//				DoublePoint p1 = this.points.get(0);
//				Line2D.Double line = new Line2D.Double(p1.getX(),-p1.getY(),p1.getX(),-p1.getY());//-y to reflect back into pixel plane.
//				g2D.draw(line);
//			}
//		}
//		
//		if(drawPoints)
//		{
//			for(GeneratedPoint p : points)
//			{
//				p.render(g2D);
//			}
//		}
//		
//		g2D.setColor(old);
//	}
}
