////////predefined 
var knotvec_circ = [0,0,0,0.25,0.25,0.5,0.5,0.75,0.75,1,1,1]; //knotvec_circ and ptsweights_circ hold true for ellipses
var ptsweights_circ = [1, 0.70710678118, 1, 0.70710678118, 1, 0.70710678118,1,0.70710678118,1];
////////end predefined

//all body shapes are represented with NURBS -> http://en.wikipedia.org/wiki/Non-uniform_rational_B-spline

//memory variables
var isGravityOn = false;

var Material = function(e,mue){
	this.e = e;
	this.mue = mue;
}


var Gravity =function(){
	this.acc = 0;
	this.acc_def = "space";
	this.isGravityOn = false;
	this.on = function(phys_obj_array,planet){
		if(planet=="earth"){
			this.acc = -9.81;
			this.acc_def = "earth";
			isGravityOn
		}else if(planet=="moon"){
			this.acc = -1.622;
			this.acc_def = "moon";
		}else if(!isNaN(planet)){
			this.acc = planet;
			this.acc_def = "custom";
		}

		if(!isGravityOn || isGravityOn === undefined){
			for (var i = 0; i < phys_obj_array.length; i++) {
				//console.log(phys_obj_array[i].acc.x2, " + ", this.acc);
				if(!(phys_obj_array[i] instanceof Environment)){
					(phys_obj_array[i]).acc.x2 =(phys_obj_array[i]).acc.x2 +this.acc;
					this.isGravityOn = true;
				}
			}
		}
	}
	this.off = function(phys_obj_array){
		if(isGravityOn){
			for (var i = 0; i < phys_obj_array.length; i++) {
				(phys_obj_array[i]).acc.x2 =(phys_obj_array[i]).acc.x2 -this.acc;
				this.isGravityOn = false;
				this.acc_def = "space";

			}
		}
	}
	this.modify_gravity = function(obj,val){
		obj.acc.x2 =obj.acc.x2 + val;
	}
}

var move_phys_objs =function(phys_obj_array){
	for (var i = 0; i < phys_obj_array.length; i++) {
		if(!(phys_obj_array[i] instanceof Environment)){
			phys_obj_array[i].move();
			console.log("Position: ", phys_obj_array[i].pos, i);
			console.log("Speed: ", phys_obj_array[i].speed, i);

			phys_obj_array[i].update();
		}
	};
}

var CollisionHandler = function(){
	this.ignore_memory = [[],[],[]]; //ignore collision env vs. env
	this.preP_memory   = []; //only execute this.preProcessorCollision once -> prepP[i][j]= true;
 	this.coll_flags = [];
	this.coll_flags["Environment"] = [false,false]; //flags for collision behavior of the objects
	this.coll_flags["Circle"] = [false,false];
	this.coll_flags["Ellipse"] = [false,false];

	this.preProcessorCollision = function(obj_0,obj_1,i,j){ //collision flags
		

		if (obj_0 instanceof Environment){
			this.coll_flags["Environment"][0] = true;
			if(obj_1 instanceof Environment){
				this.coll_flags["Environment"][1] = true;
				this.ignore_memory[i][j]=true;
				this.preP_memory[i][j]=true;
			}

		}else if(obj_0 instanceof Circle){
				this.coll_flags["Circle"][0] = true;
				this.ignore_memory[i][j]=false;
				this.preP_memory[i][j]=true;

		}else if(obj_0 instanceof Ellipse){
				this.coll_flags["Ellipse"][0] = true;
				this.ignore_memory[i][j]=false;
				this.preP_memory[i][j]=true;
		}

		if (obj_1 instanceof Environment){
			this.coll_flags["Environment"][1] = true;
			this.preP_memory[i][j]=true;

		}else if(obj_1 instanceof Circle){
				this.coll_flags["Circle"][1] = true;
				this.ignore_memory[i][j]=false;
				this.preP_memory[i][j]=true;

		}else if(obj_1 instanceof Ellipse){
				this.coll_flags["Ellipse"][1] = true;
				this.ignore_memory[i][j]=false;
				this.preP_memory[i][j]=true;
		}
	}
	this.calcConsequences = function(obj_0,obj_1,intersect){
		var obj0 = obj_0.shape;
		var obj1 = obj_1.shape;
		var param0 = obj0.closestParam(intersect);
		var param1 = obj1.closestParam(intersect);
        var coll_tangent0 = obj0.tangent(param0);
        var coll_tangent1 = obj1.tangent(param1);
        //calc arithmetically averaged collision normal
        //convert array to vector
        var collt0 = convertArray2Vector(coll_tangent0);
        var collt1 = convertArray2Vector(coll_tangent1);

        var colltFinal = biSectorVector(collt0,collt1);
        //normal vecto of colltFinal :
        var normal_vec = colltFinal.normal_vec();
        var angle_coll = Math.atan(colltFinal[1]/colltFinal[0]);


		if(this.coll_flags["Environment"][0]){
			if(this.coll_flags["Circle"][1]){
				//calc environment vs. circle
				var origin = new Vector2d(0,0,0);
        		var trans_speed1 = obj_1.speed.rotate(angle_coll,origin);

        		var trans_speed_new= new Vector2d(0,0,0);

                trans_speed_new.x1 = -trans_speed1.x1*obj_1.material.e;
                trans_speed_new.x2 = trans_speed1.x2; //stays without friction
                //trans_speed_new[2] = 0; //stays zero, rotation doesnt matter without friction
                
                //convert speeds back to global directions
               	obj_1.speed = trans_speed_new.inv_rotate(angle_coll,origin);
               	//correction of penetration of the physical objects due to the finite small time tick
               	//
               	var correction = 0.01; //maybe calculate correction value from relative speeds of objects
                obj_1.pos = obj_1.pos.add(normal_vec.multi_scalar(correction))

			}else if(this.coll_flags["Ellipse"][1]){
				//calc environment vs. ellipse
			}

		}else if(this.coll_flags["Circle"][0]){
			if(this.coll_flags["Environment"][1]){
				//calc circle vs. enviroment
			}else if(this.coll_flags["Ellipse"][1]){
				//calc circle vs. ellipse
			}else if(this.coll_flags["Circle"][1]){
				//calc circle vs. circle
			}

		}else if(this.coll_flags["Ellipse"][0]){
			if(this.coll_flags["Environment"][1]){
				//calc ellipse vs. enviroment
			}else if(this.coll_flags["Circle"][1]){
				//calc ellipse vs. circle
			}else if(this.coll_flags["Ellipse"][1]){
				//calc ellipse vs. ellipse
			}
		}

	}
	this.ignoreCollision = function(i,j){
		this.ignore_memory[i][j]=true;
	}
	this.init = function (phys_obj_array){
		for (var i = 0; i < phys_obj_array.length; i++) {

		inner: for (var j = i+1; j < phys_obj_array.length; j++) {
			if(this.ignore_memory[i][j]=true){
				break inner;
			}
			var intersect = verb.geom.Intersect.curves( phys_obj_array[i].nurbsData, phys_obj_array[j].nurbsData, 1e-10 );
			if(!this.preP_memory[i][j]){
					this.preProcessorCollision(phys_obj_array[i],phys_obj_array[j],i,j);
			}
			if(intersect==0) {
                break inner;
       	 	}else if(intersect.length>=2){
       	 		
				var intersect_average = [(intersect[0].point0[0]+intersect[1].point0[0])/2, (intersect[0].point0[1]+intersect[1].point0[1])/2,0]
				this.calcConsequences(phys_obj_array[i],phys_obj_array[j],intersect_average)
           		//pass objs and intersect to this.calcConsequences
           	}else{
          		var intersect_single = [intersect[0].point0[0], intersect[0].point0[1],0];
          		this.calcConsequences(phys_obj_array[i],phys_obj_array[j],intersect_single)
       	 	}
		}
	}
	}
}

var Phys_obj = function(pos,mass,speed,acc,center){
	this.pos = pos;  //pos.x1 =x position, pos.x2 =y position, pos.x3 =orientation_angle,
	this.mass = mass;
	this.speed = speed || new Vector2d(0,0,0); //speed.x1= speed x-direction... speed.x3 = rotation x3 axis
	this.acc = acc || new Vector2d(0,0,0); // acc = [0,0,0]
	this.center = center;
	this.shapeData = {
		degree: 0,
		knotvec: 0,
		controlpoints: 0,
		weights: 0
	}; //every shape is generated with NURBS basic functions //shapeData: [knotvec, controlpoints,weights]
	this.material = new Material(0,0);
	
	this.generateNurbsData = function() { //convert shapeData to verb nurbsData
		this.nurbsData = new verb.core.NurbsCurveData(this.shapeData.degree,this.shapeData.knotvec.slice(),verb.core.Eval.homogenize1d(this.shapeData.controlpoints,this.shapeData.weights));
	}
	this.generateShape= function() { //generate shape from verbData
		this.shape = new verb.geom.NurbsCurve(this.nurbsData);
	}
	this.move = function() { //update pos, speed //x3 values are rotationvalues
		this.pos = this.pos.add((this.speed.multi_scalar(tick)).add(this.acc.multi_scalar(0.5*Math.pow(tick,2))));//this.shape = new verb.geom.NurbsCurve(this.nurbsData);
		this.speed = this.speed.add(this.acc.multi_scalar(tick));

	}
}


var Circle = function(radius,pos,mass,material){
	this.radius = checkandYield(radius,"radius");
	this.pos 	= pos;
	this.center = new Vector2d(this.pos.x1+this.radius,this.pos.x2+this.radius,0);
	this.mass   = checkandYield(mass,"mass");
	this.shapeData.degree = 2;
	this.shapeData.knotvec = knotvec_circ;
	this.shapeData.weights = ptsweights_circ;
	this.material = checkandYield(material,"Material");
	
	this.generateCpts = function(){
		var p1b= [this.pos.x1,                this.pos.x2+this.radius,     0];
        var p2b= [this.pos.x1,                this.pos.x2,                 0]; 
        var p3b= [this.pos.x1+this.radius,    this.pos.x2,                 0]; 
        var p4b= [this.pos.x1+2*this.radius,  this.pos.x2,                 0]; 
        var p5b= [this.pos.x1+2*this.radius,  this.pos.x2+this.radius,     0]; 
        var p6b= [this.pos.x1+2*this.radius,  this.pos.x2+2*this.radius,   0]; 
       	var p7b= [this.pos.x1+this.radius,    this.pos.x2+2*this.radius,   0];
        var p8b= [this.pos.x1,                this.pos.x2+2*this.radius,   0];

        var ptsb= [p1b,p2b, p3b,p4b, p5b,p6b,p7b,p8b,p1b];

        return ptsb;
	}
	this.shapeData.controlpoints = this.generateCpts();
	this.generateNurbsData();
	this.generateShape();
	this.update = function(){
		this.shapeData.controlpoints = this.generateCpts();
		this.generateNurbsData();
		this.generateShape();
	}
}

Circle.prototype = new Phys_obj;
Circle.prototype.constructor = Circle;

var Environment = function(degree,cpt,knotvec,weights){
		this.shapeData = {
			degree: degree,
			knotvec: knotvec,
			cpt: cpt,
			weights: weights
		};
	
	this.generateShapeWith4Args = function(){ // generates classic nurbs curve with controlpoints etc.
		if (!(weights === undefined)) {
			this.shape = verb.geom.NurbsCurve.byKnotsControlPointsWeights(degree, knotvec,cpt ,weights );
		}else if(weights === undefined) {
			this.shape = verb.geom.NurbsCurve.byKnotsControlPointsWeights(degree, knotvec,cpt);
		}
	}
	this.generateShapeWith2Args = function(){ //generates curve by points on the curve
		this.shape = verb.geom.NurbsCurve.byPoints( cpt, degree );
	}
	this.generateNurbsData = function() {
		this.nurbsData = this.shape.asNurbs();
	}

	if(arguments.length==4 || arguments.length==3){
		this.generateShapeWith4Args();
	}else if (arguments.length == 2){
		this.generateShapeWith2Args();
	}
	this.generateNurbsData();
}

var Ellipse = function(x1axis,x2axis,pos,mass,material){
	this.x1axis = checkandYield(x1axis,"x1axis");
	this.x2axis = checkandYield(x2axis,"x2axis");
	this.pos 	= checkandYield(pos,"pos");
	this.mass   = checkandYield(mass,"mass");
	this.center = new Vector2d(this.pos.x1+this.x1axis,this.pos.x2+this.x2axis,0);
	this.massIntertia = 2;//this.mass*()
	this.shapeData.degree = 2;
	this.shapeData.knotvec = knotvec_circ;
	this.shapeData.weights = ptsweights_circ;
	this.material = checkandYield(material,"Material");

	this.generateCpts = function(){
		var p1b= new Vector2d(this.pos.x1,                this.pos.x2+this.x2axis,     0);
        var p2b= new Vector2d(this.pos.x1,                this.pos.x2,                 0); 
        var p3b= new Vector2d(this.pos.x1+this.x1axis,    this.pos.x2,                 0); 
        var p4b= new Vector2d(this.pos.x1+2*this.x1axis,  this.pos.x2,                 0); 
        var p5b= new Vector2d(this.pos.x1+2*this.x1axis,  this.pos.x2+this.x2axis,     0); 
        var p6b= new Vector2d(this.pos.x1+2*this.x1axis,  this.pos.x2+2*this.x2axis,   0); 
       	var p7b= new Vector2d(this.pos.x1+this.x1axis,    this.pos.x2+2*this.x2axis,   0);
        var p8b= new Vector2d(this.pos.x1,                this.pos.x2+2*this.x2axis,   0);

        var ptsb= [p1b,p2b, p3b,p4b, p5b,p6b,p7b,p8b,p1b];

        for (var i = 0; i < ptsb.length; i++) {
        	ptsb[i] = (ptsb[i]).rotate(this.pos.x3,this.center);

        };
        // convert ptsb[i] from Vector2d to Array
        return ptsb;
	}

	this.shapeData.controlpoints = this.generateCpts();

	this.generateNurbsData();
	this.generateShape();
	this.update = function(){
		this.shapeData.controlpoints = this.generateCpts();
		this.generateNurbsData();
		this.generateShape();
	}
}



Ellipse.prototype = new Phys_obj;
Ellipse.prototype.constructor = Ellipse;



//Ellipse = function... proto = phys_obj

//obstacle = function no proto, no speed, mass = inifity, i.e. wall