#include "ofxVectorGraphics.h"

//we need a point class that can actually be used
//scroll down for the vectorGraphics code
class ofPoint3 : public ofPoint{

	public:
		ofPoint3(float _x = 0, float _y = 0, float _z = 0){
			x = _x;
			y = _y;
			z = _z;
		}

		void set(ofPoint3 &p){
			x = p.x;
			y = p.y;
			z = p.z;
		}
		
		void set(float _x, float _y){
			x = _x;
			y = _y;
		}
		
		void set(float _x, float _y, float _z){
			x = _x;
			y = _y;
			z = _z;
		}		
				
		bool operator==(const ofPoint3 &p) { 
			return (x == p.x) && (y == p.y) && (z == p.z);
		}

		bool operator!=(const ofPoint3 &p) { 
			return (x != p.x) || (y != p.y) || (z != p.z);
		}
    
		ofPoint3 operator+(const ofPoint3 &p) const { 
			return ofPoint3( x+p.x, y+p.y, z+p.z); 
		} 
		
		ofPoint3 operator+(const float f) const { 
			return ofPoint3( x+f, y+f, z+f );
		} 
		
		ofPoint3& operator+=(const ofPoint3& p) { 
			x += p.x;
			y += p.y;
			z += p.z;
			return *this; 
		} 
    
		ofPoint3& operator+=(const float f) { 
			x += f;
			y += f;
			z += f;
			return *this; 
		} 	
	
		ofPoint3 operator-(const ofPoint3 &p) const { 
			return ofPoint3( x-p.x, y-p.y, z-p.z ); 
		} 
		
		ofPoint3 operator-(const float f) const { 
			return ofPoint3( x-f, y-f, z-f );
		} 
		
		ofPoint3& operator-=(const ofPoint3 &p) { 
			x -= p.x;
			y -= p.y;
			z -= p.z;
			return *this; 
		} 

		ofPoint3& operator-=(const float f) { 
			x -= f;
			y -= f;
			z -= f;
			return *this; 
		} 		

		ofPoint3 operator-() const { 
			return ofPoint3( -x, -y, -z ); 
		}
		
		ofPoint3 operator*(const ofPoint3 &p) const { 
			return ofPoint3( x*p.x, y*p.y, z*p.z ); 
		} 
			
		ofPoint3& operator*=(const ofPoint3 &p) { 
			x *= p.x;
			y *= p.y;
			z *= p.z;
			return *this; 
		} 

		ofPoint3 operator*(const float f) const { 
			return ofPoint3( x*f, y*f, z*f );
		} 
		
		ofPoint3& operator*=(const float f) { 
			x *= f;
			y *= f;
			z *= f;
			return *this; 
		} 
		
		ofPoint3 operator/(const ofPoint3 &p) const { 
			return ofPoint3( x/p.x, y/p.y, z/p.z );
		} 
		
		ofPoint3& operator/=(const ofPoint3 &p) { 
			x /= p.x;
			y /= p.y;
			z /= p.z;
			return *this; 
		} 

		ofPoint3 operator/(const float f) const { 
			return ofPoint3( x/f, y/f, z/f );
		} 

		ofPoint3& operator/=(const float f) { 
			x /= f;
			y /= f;
			z /= f;
			return *this; 
		}

	union{
		struct{
		float x; 
		float y;
		float z;};
		float v[3];
	};
	
};

//----------------------------------------------------------			
ofxVectorGraphics::ofxVectorGraphics(){
	bDraw		= true;
	bFill		= true;
	bCenter		= false;
	bRecord		= false;	
	bFirstPoint = false;	
	bShouldClose= false;
	whichShapeMode = 0;		
}

//----------------------------------------------------------			
void ofxVectorGraphics::beginEPS(string fileName, int x, int y, int w, int h){
	creeps.newFile(ofToDataPath(fileName).c_str(), x, y, x+w, y+h);
	bRecord = true;
	
	//CreEPS uses lower left as (0,0)
	//so we have to flip our coordinates
	//this works fine as long
	//as the window doesn't change size 
	//while we are capturing!!!				
	creeps.applyTranslation(0, ofGetHeight());
	creeps.applyScaling(1, -1);
}

//----------------------------------------------------------						
void ofxVectorGraphics::endEPS(){
	creeps.resetTransformations();
	creeps.finalize();
	bRecord = false;
}

//----------------------------------------------------------			
void ofxVectorGraphics::enableDraw(){ 
	bDraw = true; 
}

//----------------------------------------------------------			
void ofxVectorGraphics::disableDraw(){ 
	bDraw = false; 
}

//----------------------------------------------------------			
void ofxVectorGraphics::enableCenterRect(){ 
	bCenter = true; 
}

//----------------------------------------------------------			
void ofxVectorGraphics::disableCenterRect(){ 
	bCenter = false; 
}

//----------------------------------------------------------			
void ofxVectorGraphics::fill(){ 
	bFill = true; ofFill(); 
}

//----------------------------------------------------------			
void ofxVectorGraphics::noFill(){ 
	bFill = false; ofNoFill();
}

//----------------------------------------------------------			
void ofxVectorGraphics::setLineWidth(float lineWidth){
	glLineWidth((int)lineWidth);
	if(bRecord) creeps.setAttributes( CAtLineThickness(lineWidth) );
}

//----------------------------------------------------------
void ofxVectorGraphics::setColor(int _r, int _g, int _b){
	float r = (float)_r / 255.0f; r = MAX(0,MIN(r,1.0f));
	float g = (float)_g / 255.0f; g = MAX(0,MIN(g,1.0f));
	float b = (float)_b / 255.0f; b = MAX(0,MIN(b,1.0f));
	changeColor(r, g, b);
}

//----------------------------------------------------------
void ofxVectorGraphics::setColor(int hexColor){
	int r = (hexColor >> 16) & 0xff;
	int g = (hexColor >> 8) & 0xff;
	int b = (hexColor >> 0) & 0xff;
	setColor(r,g,b);
}

//----------------------------------------------------------
void ofxVectorGraphics::changeColor(float r, float g, float b){
	glColor3f(r,g,b);
	if(bRecord)creeps.setAttributes( CAtColor(r, g, b) );
}

//----------------------------------------------------------		
void ofxVectorGraphics::rect(float x1,float y1, float w, float h){
	if(bDraw){
		if(bCenter)ofSetRectMode(OF_RECTMODE_CENTER);
		else ofSetRectMode(OF_RECTMODE_CORNER);
		ofRect(x1, y1, w, h);
	}
	if(bRecord){
		if(bCenter){
			x1 -= w * 0.5;
			y1 -= h * 0.5;
		}
		
		if(bFill) creeps.rectFill(x1,  y1,  w,  h); 
		else creeps.rectStroke(x1, y1, w, h);
	}
}

//----------------------------------------------------------
void ofxVectorGraphics::triangle(float x1,float y1, float x2, float y2, float x3, float y3){
	if(bDraw){
		ofTriangle(x1, y1,  x2,  y2,  x3,  y3);
	}
	if(bRecord){
		creeps.startPath(x1, y1);
		creeps.addLine(x2,  y2);
		creeps.addLine(x3,  y3);						
		creeps.closeSubpath();
		if(bFill)creeps.endPath(CreEPS::FILL);
		else creeps.endPath(CreEPS::STROKE);
	}
}	

//----------------------------------------------------------
void ofxVectorGraphics::circle(float x1,float y1, float radius){
	if(bDraw){
		ofCircle(x1, y1, radius);
	}
	if(bRecord){
		if(bFill) creeps.disk(x1, y1, radius); 
		else creeps.circle(x1, y1, radius);
	}
}	

//----------------------------------------------------------
void ofxVectorGraphics::ellipse(float x1,float y1, float w, float h){
	if(bDraw){
		ofEllipse(x1, y1, w, h);
	}
	if(bRecord){
		if(bFill){
			//there is no filled ellipse in non-path mode
			creeps.startPath();
			creeps.addEllipse(x1, y1, w, h);
			creeps.endPath(CreEPS::FILL);
		} 
		else creeps.ellipse(x1, y1, w, h); 
	}
}	

//----------------------------------------------------------
void ofxVectorGraphics::line(float x1,float y1, float x2, float y2){
	if(bDraw){
		ofLine(x1, y1, x2, y2);
	}
	if(bRecord){
		creeps.line(x1, y1, x2, y2);
	}
}	

//-----------------------------------------------------------
void ofxVectorGraphics::arc(float x, float y, float radius, float offsetAngleDegrees, float internalAngleDegrees){
	if(bDraw){
		//no openFrameworks arc command so we make a ghetto gl one
		int		arcResolution = 20;
		float	step  = ( DEG_TO_RAD * internalAngleDegrees) / (arcResolution - 1);
		float	angle = DEG_TO_RAD * offsetAngleDegrees;
			
		float	xpos, ypos;

		if(!bFill){
	
			glBegin(GL_LINE_STRIP);
				for(int i = 0; i < arcResolution; i++){
					xpos	= cos(angle) * radius;
					ypos	= sin(angle) * radius; 
					glVertex2f(x + xpos, y + ypos);						
					angle += step;
				}
			glEnd();
		
		}else{
			glBegin(GL_TRIANGLE_STRIP);
				for(int i = 0; i < arcResolution; i++){
					xpos	= cos(angle) * radius;
					ypos	= sin(angle) * radius; 
					glVertex2f(x + xpos, y + ypos);						
					glVertex2f(x, y);														
					angle += step;
				}
			glEnd();
		}
	}
	if(bRecord){
		
		//creps works differently it likes to know absolute starting and ending angles
		float startAngle = offsetAngleDegrees;
		float endAngle   = startAngle + internalAngleDegrees;
		
		//it also only like to go from smaller to bigger in terms of rotation
		if(startAngle > endAngle){
			float tmp = endAngle;
			endAngle = startAngle;
			startAngle = tmp;
		}
		
		if(bFill){
			creeps.startPath(x, y);
			creeps.addArc(x , y, radius, startAngle,  endAngle);
			creeps.endPath(CreEPS::FILL);
		}else{
			creeps.arc(x , y, radius, startAngle,  endAngle);
		}
		
	}
}

//----------------------------------------------------------
void ofxVectorGraphics::bezier(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4){
	if(bDraw){
		ofBezier(x1, y1, x2, y2, x3, y3, x4, y4);
	}
	if(bRecord){
		if(bFill){
			creeps.startPath(x1, y1);
			creeps.addCurve(x2, y2, x3, y3, x4, y4);
			creeps.addLine(x1, y1);	
			creeps.endPath(CreEPS::FILL);
		}
		else creeps.curve(x1, y1, x2, y2, x3, y3, x4, y4);
	}
}				

//----------------------------------------------------------
void ofxVectorGraphics::curve(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4){
	if(bDraw){
		ofCurve(x1, y1, x2, y2, x3, y3, x4, y4);						
	}
	if(bRecord){
	
		//we treat the catmull rom curve as a bezier 
		//by finding the approrpriate control points 
		//to make the same curve
													
		//x1 y1 = p1
		//x2 y2 = p2
		//x3 y3 = p3
		//x4 y4 = p4
		
		//control point at p2 is parallel 
		//to the line defined by p3 - p1
		
		//control point at p3 is parallel
		//to the line defined by p4 - p2
	
		ofPoint3 p1(x1, y1);
		ofPoint3 p2(x2, y2);
		ofPoint3 p3(x3, y3);
		ofPoint3 p4(x4, y4);
		
		//SUPER WEIRD MAGIC CONSTANT = 1/6 (this works 100% can someone explain it?)
		ofPoint3 cp1 = p2 + ( p3 - p1 ) * (1.0/6);
		ofPoint3 cp2 = p3 + ( p2 - p4 ) * (1.0/6);						
				
		creeps.startPath(x2, y2);
		creeps.addCurve( cp1.x, cp1.y, cp2.x, cp2.y, x3, y3);
		
		if(bFill){
			creeps.addLine(x2, y2);	
			creeps.endPath(CreEPS::FILL);
		}else{
			creeps.endPath(CreEPS::STROKE);
		}
	}
}	

//----------------------------------------------------------						
void ofxVectorGraphics::beginShape(){
	if(bDraw){
		ofBeginShape();
	}
	if(bRecord){
		bFirstPoint		= true;
		bShouldClose	= false;
	}
}

//----------------------------------------------------------						
void ofxVectorGraphics::polyVertex(float x, float y){
	if(bDraw){
		ofVertex(x, y);
	}
	
	if(bRecord){
	
		//clear curve vertices
		if(whichShapeMode == 1){
			clearAllVertices();	
		}

		whichShapeMode = 0;
		
		if(bFirstPoint){
			creeps.startPath(x, y);
			bFirstPoint = false;
			bShouldClose = true;
		}else{
			creeps.addLine(x,y);
		}
	}
}

//----------------------------------------------------------	
//this takes three arguments as it is based off the idea that at least one 
//inital polyVertex has already been set. 					
void ofxVectorGraphics::bezierVertex(float x1, float y1, float x2, float y2, float x3, float y3){
	if(bDraw){
		ofBezierVertex(x1, y1, x2, y2, x3, y3);
	}
	if(bRecord){
		
		//clear curve vertices
		if(whichShapeMode == 1){
			clearAllVertices();	
		}
		
		whichShapeMode = 2;					
		
		//we can only add a bezier curve if the curve
		//has started with a polyVertex
		//so as long as this is not the first point we add it
		if(!bFirstPoint){
			creeps.addCurve(x1, y1, x2, y2, x3, y3);
		}
		
	}
}			

//----------------------------------------------------------						
void ofxVectorGraphics::curveVertex(float x, float y){
	if(bDraw){
		ofCurveVertex(x, y);
	}
	if(bRecord){
	
		//if some points have already been created
		//this could crash the app 
		//so we close the current path to be safe
		if(bShouldClose){
			if(bFill) creeps.endPath(CreEPS::FILL);
			else creeps.endPath(CreEPS::STROKE);
		}
	
		whichShapeMode = 1;
	
		double* point = new double[2];
		point[0] = x;
		point[1] = y;
		curvePts.push_back(point);
	}
}


//----------------------------------------------------------						
void ofxVectorGraphics::endShape(bool bClose){
	if(bDraw){
		ofEndShape(bClose);
	}
	if(bRecord){
											
		//catmull roms - we need at least 4 points to draw
		if( whichShapeMode == 1 && curvePts.size() > 3){												
			
			//we go through and we calculate the bezier of each 
			//catmull rom curve - smart right? :)
			for (int i = 1; i< curvePts.size()-2; i++) {
				
				ofPoint3 prevPt(	curvePts[i-1][0],	curvePts[i-1][1]);
				ofPoint3 startPt(curvePts[i][0],		curvePts[i][1]);							
				ofPoint3 endPt(	curvePts[i+1][0],	curvePts[i+1][1]);
				ofPoint3 nextPt(	curvePts[i+2][0],	curvePts[i+2][1]);
				
				//SUPER WEIRD MAGIC CONSTANT = 1/6
				//Someone please explain this!!! 
				//It works and is 100% accurate but wtf!
				ofPoint3 cp1 = startPt + ( endPt - prevPt ) * (1.0/6);
				ofPoint3 cp2 = endPt + ( startPt - nextPt ) * (1.0/6);						
				
				//if this is the first line we are drawing 
				//we have to start the path at a location
				if( i == 1 ){
					creeps.startPath(startPt.x, startPt.y);
					bShouldClose = true;
				}
				
				creeps.addCurve( cp1.x, cp1.y, cp2.x, cp2.y, endPt.x, endPt.y);
			}						
		}
		
		if(bShouldClose){														
			//we close the path if requested
			if(bClose)creeps.closeSubpath();
		
			//render the stroke as either a fill or a stroke.
			if(bFill){
				creeps.endPath(CreEPS::FILL);
			}else{
				creeps.endPath(CreEPS::STROKE);
			}
			bShouldClose = false;
		}
		
		//we want to clear all the vertices
		//otherwise we keep adding points from
		//the previous file - cool but not what we want!
		clearAllVertices();
		
	}
}			


//----------------------------------------------------------
void ofxVectorGraphics::clearAllVertices(){
	for(vector<double*>::iterator itr=curvePts.begin();
		itr!=curvePts.end();
		++itr){
		delete [] (*itr);
	}
	curvePts.clear();
}