package primitives.geomtry;
import java.awt.*;
/**
* The class <code>Geomtry</code> contains methods for preforming basic plane geomtry calculations 
* required by related objects.
*@author Dori Eldar
*/
public final class  Geomtry  {

	/**
     * Don't let anyone instantiate this class.
     */
	private Geomtry() {}
	/**
	* Determines the size in pixels of a drawn machine's anchor.
	**/
	private final static int ANCHOR_SIZE = 3;
   /**
	* Determines the size in pixels of a drawn Machine's joint.
	**/
	private final static int JOINT_SIZE = 3;

	/**
	*Returns the euclidean distance between 2 <code>coordinate</code> objects.
	*
	*@param d1 a coordinate.
	*@param d2 a coordinate.
	*@return the distance between d1 and d2.
	*/
	public static double distance(Coordinate d1,Coordinate d2){
		return Math.sqrt((d1.x-d2.x)*(d1.x-d2.x)+(d1.y-d2.y)*(d1.y-d2.y));
	}

	/**
	*Returns the 2 solutions of a quadratic equation.
	*Given the equation: ax<sup>2</sup>+bx+c=0.
	*
	*@param a a double.
	*@param b a double.
	*@param c a double.
	*@return an array of double with length =2 of the solutions to the quadratic equation.
	*@exception java.lang.ArithmeticException when there is no real solution to the quadratic equation.
	*/
	public static double[] squareFormula(double a,double b,double c) throws ArithmeticException {
		double[] results = new double[2];	
		if (a==0){
					results[0]=-c/b;results[1]=-c/b;
		}else{
			double discriminant=b*b-4*a*c;
			if (discriminant>=0){
				discriminant = Math.sqrt(discriminant);
				results[0]=(-b+discriminant)/(2*a);
				results[1]=(-b-discriminant)/(2*a);
			}else 	throw (new ArithmeticException("No solution"));
		}
		return results;
	}
	/**
	*Returns the coordinate location with the specified distance and angle from the given coordinate.
	*
	*@param c a coordinate.
	*@param modul the distance from c.
	*@param arg the angle in radians.
	*@return the location of with the specified argument and modul from c.
	*/
	public static Coordinate getPointByVector(Coordinate c,double modul,double arg){
		return (new Coordinate(c.x+(Math.cos(arg)*modul),c.y+(Math.sin(arg)*modul)));
	}
	/**
	*Returns the location of the intersection of a convex polygon and the line connecting 2 coordinates.
	*The method uses an approximation algorithm rather than a linear computation which would
	*be more efficient.
	*@param center a coordiante presumed to lie inside the polygon.
	*@param state a coordinate presumed to lie outside(or on) the polygon.
	*@param polygon the polygon.
	*@return the <code>Coordinate</code> location of the point on the polygon.
	*/
	public static Coordinate findPointOnPolygon(Coordinate center,Coordinate state,Polygon polygon){
		return findPointOnPolygon(center,getAngle(center,state),polygon);
	}
	/**
	*Returns the location of the intersection of a convex polygon and the line projecting of the
	*coordinate with the given angle.
	*The method uses an approximation algorithm rather than a linear computation which would
	*be more efficient.
	*@param center a coordiante presumed to lie inside the polygon.
	*@param angle the angle of the line from <code>center</code>to find the intersection.
	*@param polygon the polygon.
	*@return the <code>Coordinate</code> location of the point on the polygon.
	*/
	public static Coordinate findPointOnPolygon(Coordinate center,double angle,Polygon polygon){
		Coordinate inside = new Coordinate(center.x,center.y);
		Coordinate outside =getPointByVector(center,
			distance(center,new Coordinate(polygon.xpoints[0],polygon.ypoints[0]))+1
			,angle);
 		Coordinate tstate = outside;
		boolean found = false;
		while (!found){
			if (Geomtry.distance(inside,outside)<1.45) found = true;
				tstate = Geomtry.getMidPoint(inside,outside,0.5);
				if (polygon.contains(tstate.toPoint())) inside.move(tstate.x,tstate.y);
				else outside.move(tstate.x,tstate.y);
		}
		return tstate;
	}
	/**
	*Returns the convex sum of 2 locations.
	*@param c1 a location.
	*@param c2 a location.
	*@param ration the convex ratio of c1 & c2.
	*@return  ratio*c1+(1-ratio)*c2.
	*/
	public static Coordinate getMidPoint(Coordinate c1,Coordinate c2,double ratio){
		return new Coordinate(ratio*c1.x+(1-ratio)*c2.x,ratio*c1.y+(1-ratio)*c2.y);
	}
	/**
	*Returns the 2 possible locations of a point with the specified distances from 2 locations.
	*if c1 & c2 represent equal locations and beta=alpha the return is the location with distance 
	*alpha and angle 0 from c1.
	*@param c1 the first location.
	*@param alpha the required distance from c1.
	*@param c2 the second location.
	*@param beta the requierd distance from c2.
	*@return the locations with distances alpha from c1 & beta from c2.
	*@exception java.lang.ArithmeticException if there is no valid location with the specified parameters.
	*/
	public static Coordinate[] getTriPoint(Coordinate c1,double alpha,Coordinate c2,double beta) throws ArithmeticException{
		Coordinate A,B;
		boolean switched = false;
		boolean translated = false;
		if (Math.abs(c1.y-c2.y)<0.000001){
			if (c1.x-c2.x==0) {
				Coordinate[] results =new Coordinate[2];
				if(Math.abs(alpha-beta)<1.1)results[0] = getPointByVector(c1,alpha,0);
				else results[0] = new Coordinate(c1.x,c1.y);
				results[1] = results[0];
				return results;
			}
		/*	A = new Coordinate(0,0);
			B = new Coordinate(0,c2.x-c1.x);
			switched = true;
			c1 = getPointByVector(c2,distance(c1,c2),0.5);
		*/	c1.translate(0,0.00004);
			translated = true;
		}//else{
			A = new Coordinate(0,0);
			B = new Coordinate(c2.x-c1.x,c2.y-c1.y);
 	//	}
		double gamma = B.y/2+((alpha-beta)*(alpha+beta)+B.x*B.x)/(2*B.y);
		double delta = B.x/B.y;
		double a = 1+delta*delta;
		double b = -2*gamma*delta;
		double c = (gamma-alpha)*(alpha+gamma);
		double[] Xresults = squareFormula(a,b,c);
		double[] Yresults = new double[2];
		Yresults[0] = gamma-delta*Xresults[0];
		Yresults[1] = gamma-delta*Xresults[1];
		if (switched==true){
			A.move(Yresults[0]+c1.x,Xresults[0]+c1.y);
			B.move(Yresults[1]+c1.x,Xresults[1]+c1.y);
		/*	A = getPointByVector(c2,distance(A,c2),-0.5);
			B = getPointByVector(c2,distance(B,c2),-0.5);
			c1 = getPointByVector(c2,distance(c1,c2),-0.5);
		*/}else{
			A.move(Xresults[0]+c1.x,Yresults[0]+c1.y);
			B.move(Xresults[1]+c1.x,Yresults[1]+c1.y);
		}
		Coordinate[] results = new Coordinate[2];
		results[0] = A;
		results[1] = B;
		c1.translate(0,-0.00004);
		return results;
	}
	/**
	*Returns a location of a point with the specified distances from 2 locations.
	*The location returned is the closest to reference  and secondReference in this order. a nil
	*argument for these parameters will return one of the two possible solutions.
	*@param c1 the first location.
	*@param alpha the required distance from c1.
	*@param c2 the second location.
	*@param beta the requierd distance from c2.
	*@param reference specifies a location to which to return the closest location of possible 2.
	*@param secondReference used if two locations have equal distances from <code>reference</code>.
	*@return the locations with distances alpha from c1 & beta from c2.
	*@exception java.lang.ArithmeticException if there is no valid location with the specified parameters.
	*@see #getTriPoint
	*/
	public static Coordinate getTriPointEx(Coordinate c1,double alpha,Coordinate c2,double beta,
		Coordinate reference,Coordinate secondReference) throws ArithmeticException{
		double d0,d1;
		Coordinate[] results = getTriPoint(c1,alpha,c2,beta);
		if(reference!=null){
			d0 = distance(results[0],reference);
			d1 = distance(results[1],reference);
			if(d1-d0<-15) return results[1];
			else if((d0-d1<-15) ||(secondReference==null))return results[0];
			else {
					d0 = distance(results[0],secondReference);
					d1 = distance(results[1],secondReference);
					if (d1<=d0) return results[1];
					else return results[0];
			}
		}return results[0];
	}
	/**
	*Returns the angle in radians between 2 coordinates.
	*@param p1 a location.
	*@param p2 a location.
	*@return the angle in the direction from p1 to p2.
	*/
	public static double getAngle(Coordinate p1,Coordinate p2){
		if(p1.y!=p2.y){
			if (p1.x<p2.x)return Math.atan((p2.y-p1.y)/(p2.x-p1.x));
			else if(p1.x>p2.x)return Math.atan((p2.y-p1.y)/(p2.x-p1.x))+Math.PI;
			else if (p1.y<p2.y) return Math.PI/2;
			else return Math.PI*3/2;
		}else{
			if(p1.x<p2.x) return 0;
			else return Math.PI;
		}
	}
	/**
	*Converts degrees to radians.
	*@param Angle an angle in degrees representation.
	*@return a radian representaion of Angle by the formula:Math.PI*Angle/180.
	*/
	public static double toRadian(double Angle){
		return Math.PI*Angle/180;
	}
	/**
	*Converts radians to degrees.
	*@param radian an angle in radian representation.
	*@return a degrees representaion of radian by the formula:180*radian/Math.PI.
	*/
	public static double toAngle(double radian){
		return 180*radian/Math.PI;
	}
	/**
	*Returns the corresponding argument value in the range 0-Math.PI*2
	*@param angle the angle to convert.
	*@return the corresponding argument value in the range 0-Math.PI*2.
	*/
	public static double getStandartAngle(double angle){
	/*	while(angle<0) angle = angle+Math.PI*2;
		 angle = angle%(Math.PI*2);
		return angle;*/
		return modulo(angle,Math.PI*2);
	}
	/**
	*return the positive mod of an argument.
	*@param arg  the value argument.
	*@param mod the modolus argument.
	*@return the positive argument of arg%mod.
	*/
	public static double modulo(double arg,double mod){
		return((arg%mod)+mod)%mod;
	}
	/**
	*Draws a machine's joint.
	*@param g Graphic object to draw on.
	*@param center the center of the drawn joint.
	*/
 	public static void drawJoint(Graphics g,Point joint){
		  g.fillOval(joint.x-JOINT_SIZE,joint.y-JOINT_SIZE,2*JOINT_SIZE,2*JOINT_SIZE);
	}
	/**
	*Draws a machine's anchor.
	*@param g Graphic object to draw on.
	*@param anchor the center of the drawn joint.
	*/
	public static void drawAnchor(Graphics g,Point anchor){
	   g.drawLine(anchor.x-ANCHOR_SIZE,anchor.y-ANCHOR_SIZE,anchor.x+ANCHOR_SIZE,anchor.y+ANCHOR_SIZE);
	   g.drawLine(anchor.x+ANCHOR_SIZE,anchor.y-ANCHOR_SIZE,anchor.x-ANCHOR_SIZE,anchor.y+ANCHOR_SIZE);
	}
	/**
	*Converts polar coordinate to cartesian coordinate.
	*@param polar a polar representation of the loction.
	*@return the cartesian representation of polar.
	*/
	public static Coordinate polarToCartes(Coordinate polar){
		return new Coordinate(polar.x*Math.cos(polar.y),polar.x*Math.sin(polar.y));
	}
}

