 package primitives.machines;
import primitives.geomtry.*;
import java.awt.*;
/**
*A spider machnine with varying number of arms.
*@author Dori Eldar.
*/ 
public class NarmsMachine extends Machine {
	/**
	*The central joint.
	*/
	protected Coordinate dCenter;
	/**
	* The integral representation of the central joint.
	*/
	public ExtPoint center;
	/**
	*A backup for the central joint.
	*/
	public Coordinate tempCenter;
	/**
	*Number of arms of the machine.
	*/
	public int arms;
	/**
	* The location of equal distances from all anchors.
	*/
	public  Coordinate origin;
	/**
	*@param the size of the rectangle the machine should be drawn in.
	*@param arms number of arms of the machine.
	*@param ration the ratio between the distance between the anchors and the origin and the length
	*of the bars. should me greater then 1.
	*/
	public NarmsMachine(Dimension d,int arms,double ratio){	  //ratio>1
		super(arms,arms,d);
		this.arms = arms;
		dCenter = new Coordinate(d.width/2,d.height/2);
		tempCenter = new Coordinate(d.width/2,d.height/2);
		int length = Math.min(d.height,d.width)/4-4;
		barLength = (int)(length*ratio);
		for(int i=0;i<arms;i++){
			dAnchors[i] = Geomtry.getPointByVector(dCenter,length*2,Math.PI*2*i/arms);
			integrizeAnchors();
			if(i>0) dJoints[i] = Geomtry.getTriPointEx(dCenter,barLength,dAnchors[i],barLength,dAnchors[(i-1)],null);
			bendStates[i]=-1;
		}
			dJoints[0] = Geomtry.getTriPointEx(dCenter,barLength,dAnchors[0],barLength,dAnchors[arms-1],null);
			updatePoints(dAnchors,anchors);
			origin = new Coordinate(dCenter.x,dCenter.y);
			center = dCenter.toPoint();
	}
	
	public double getCurrentAngle(){
		 return Geomtry.getAngle(origin,dCenter);
     
	}
	/**
	*@return the ratio between the distance between the <code>origin</code> and
	*the location of the current location of the central joint and the maximum distance
	*between these locations.
	*param angle the angle with which to calculate the ratio.
	*@see getMaxDistance
	*/
	public double getRatioDistance(double angle){
		return	Geomtry.distance(dCenter,origin)/getMaxDistance(angle);
	}
	/**
	*@return the maximum possible distance between the <code>origin</code>
	*and the location of the central joint for a given angle.
	*@param angle the angle to calculate for which to calculate the distance.
	*/
	public double getMaxDistance(double angle){

		return Geomtry.distance(getMaxPoint(angle),origin);
	}

	private Coordinate in = new Coordinate();
	/**
	*Returns the extremum location of the central joint from the origin in direction
	*specified by the angle.
	*An approximation algorithm is used.
	*@param angle the angle to use.
	*@return the extremum location
	*/
	public Coordinate getMaxPoint(double angle){
	  	savePoints();
		Coordinate out = Geomtry.getPointByVector(origin,barLength,angle);
		in.move(origin.x,origin.y);
		Coordinate result = Geomtry.getMidPoint(in,out,0.5);
		while (Geomtry.distance(out,in)>0.5){
				dCenter.move(result.x,result.y);
				if(moveCenterEx(0,0)<0){
					in.move(result.x,result.y);
				}else{
					out.move(result.x,result.y);
				}
				result = Geomtry.getMidPoint(in,out,0.5);
		}
		restorePoints();
		return result;
	}
	/**
	*Moves the central joint to an extermum location in the direction of a given location.
	*@param newP the given location.
	*@return 0
	*/
	public int moveToMaxCoordinate(Coordinate newP){
		double angle = Geomtry.getAngle(origin,newP);
		dCenter = getMaxPoint(angle);
		return moveCenterEx(0,0);
	}
	/**
    *Return a Polyogn objects which locations are the locations of the central joint
	*in which 2 adjacent arms are streched out.
	*/
	public Polygon findConstraints(){
		Polygon polygon = new Polygon();
		for(int j=0;j<arms;j++){
			Coordinate t = Geomtry.getTriPointEx(dAnchors[j],2*barLength,dAnchors[(j+1)%arms],
				2*barLength,origin,null);
			polygon.addPoint(t.toPoint().x,t.toPoint().y);
		}
		return polygon;
	}
	/**
	*Returns the locations of the central joint in which adjacent arms are streched.
	*@return an array of the locations.
	*@see #findConstraints
	*/
	public Coordinate[] findConstraintsEx(){
			Coordinate[] polygon = new Coordinate[arms];
		for(int j=0;j<arms;j++)
			 polygon[j] = Geomtry.getTriPointEx(dAnchors[j],2*barLength,dAnchors[(j+1)%arms],
				2*barLength,origin,null);
		return polygon;
	}
	/**
	*@deprecate use findConstraintsEx
	*@see #findConstraintsEx
	*/
	public Coordinate[] findConstraintAngles(){
		return findConstraintsEx();
	}
	/**
	*Draws this machine.
	*@param g the graphic context to draw on.
	*/
	public void redraw(Graphics g){
			g.setColor(Color.black);
			center = dCenter.toPoint();
			updatePoints(dJoints,joints);
		 	for(int i=0;i<arms;i++){
				drawLine(g,center,joints[i]);
				drawLine(g,anchors[i],joints[i]);
			}
			super.redraw(g);
			g.setColor(Color.green);
			Geomtry.drawJoint(g,center);
	}
	/**
	*@see Machine#savePoints
	*/
	protected void savePoints(){
		super.savePoints();
		tempCenter.move(dCenter.x,dCenter.y);
	}
	/**
	*@see Machine#restorePoints
	*/
	protected void restorePoints(){
		super.restorePoints();
		dCenter.move(tempCenter.x,tempCenter.y);
	}
	/**
	*calculates the location of the periferial joints by the current location
	*of the central joint.
	*@return an array of integers if arm i  is streched the i location in the array is 1 else
	*is 0.
	*/
	private int[] moveJointsA(){
		int[] result = new int[arms];
		for(int i=0;i<arms;i++)
			try{
				dJoints[i] = Geomtry.getTriPointEx(dCenter,barLength,dAnchors[i],barLength,dJoints[i],
					dAnchors[(i+bendStates[i]+arms)%arms]);
					result[i] = 0;
			}catch(ArithmeticException e){
				dJoints[i] = Geomtry.getMidPoint(dCenter,dAnchors[i],0.5);
				result[i]=1;
			}
		return result;
	}
	/**
	*Returns the integral location of an indexed joint.
    *@param i the index of the joint to return
	*@return the integral representation of the joint
	*/
	public Point getJoint(int i){
		if (i<0) return dCenter.toPoint();
		else return dJoints[i].toPoint();
	}
	/**
	*Returns the  location of an indexed joint.
    *@param i the index of the joint to return
	*@return the location of the joint.
	*/
	public Coordinate getJointEx(int i){
			if (i<0) return dCenter;
		else return dJoints[i];
	}
	/**
	*rotates the central joint with respect to the i,i>th</i>
	*joint.
	*@param i the index of the joint.
	*@param dalpha the rotation angle.
	*@return an array of 0 or 1, for each arm 1 if the rotation caused the arm to strech,
	*0 if not.
	*/
	public int[] rotateCenterA(int i,double dalpha){
		rotateJoint(dAnchors[i],dCenter,dalpha);
		return moveJointsA();		
	}
    /**
	*calculates the locations of the periferial joint 
	*has the same effect as moveJointsA.
	*@return the index of the last arm which had been streched due its new location.
	*/
	private int moveJointsEx(){
		int result = -1;
		for(int i=0;i<arms;i++)
			try{
				dJoints[i] = Geomtry.getTriPointEx(dCenter,barLength,dAnchors[i],barLength,dJoints[i],
					dAnchors[(i+bendStates[i]+arms)%arms]);
			}catch(ArithmeticException e){
				dJoints[i] = Geomtry.getMidPoint(dCenter,dAnchors[i],0.5);
				result=i;
			}
		return result;
	}
	/**
	*moves the machine by setting a new location for the central joint
	*The method will always change the position of the machine. use <code>moveCenter</code>
	*to conditionaly move the machine.
	*@param dx the amount to move the center in the horizontal direction.
	*@param dy the amount to move the center in the vertical direction.
	*@return the index of the last joint which was streched by activating this method.
	*@see #moveCenter
	*/
	public int moveCenterEx(double dx,double dy){
		dCenter.translate(dx,dy);
		return moveJointsEx();
	}
	/**
	*Conditionaly moves the machine by setting a new location for the central joint.
	*@param dx the amount to move the center in the horizontal direction.
	*@param dy the amount to move the center in the vertical direction.
	*@exception MachineException if the new state is not physicaly allowed.
	*see #moveCenterEx
	*/
	  public void moveCenter(double dx,double dy) throws MachineException{
		savePoints();
		int result = moveCenterEx(dx,dy);
		if (result>-1){
			restorePoints();
			throw new MachineException (result);
		}
	}
    /**
	*rotates the central joint with respect to the i,i>th</i>
	*joint.
	*@param i the index of the joint.
	*@param dalpha the rotation angle.
	*@return the index of the last joint to strech.
	*/
	public int rotateCenterEx(int i,double dalpha){
		rotateJoint(dAnchors[i],dCenter,dalpha);
		return moveJointsEx();		
	}
	/**
	*conditionaly rotates the central joint.
	*@see #rotateCenterEx
	*@exception MachineException if the rotation is not physicaly possible.
	*/
	public void rotateCenter(int i,double dalpha) throws MachineException{
		savePoints();
		int result = rotateCenterEx(i,dalpha);
		if (result>-1){
	
			throw new MachineException (result);
		}
	}
	/**
	*Switch the bending of a given periferial joint with respect to the central joint
	*and its anchor.
	*@param i the index of the joint to strech.
	*/
	public void switchBend(int i){
		try{
				dJoints[i] = super.switchBend(dCenter,dAnchors[i],dJoints[i]);
		}catch(ArithmeticException e){
			dJoints[i] = Geomtry.getMidPoint(dCenter,dAnchors[i],0.5);
		}finally{
			bendStates[i] = -1*bendStates[i];
		}
	}
    /**
	*strech the given arm
	*/
	public void strechArm(int arm){
		dJoints[arm] = Geomtry.getMidPoint(dCenter,dAnchors[arm],0.5);
	}
	/**
	*@param i the index of the arm.
	*@return true if arm <code>i</code> is streched.
	*/
	public boolean armStreched(int i){
		return(2*barLength<=Geomtry.distance(dAnchors[i],dCenter));
	}
	/**
	*Sets object fields to null.
	*@exception java.lang.Throwable	.
	*/
	public void finalize() throws Throwable{
		dCenter = null;
		center = null;
		tempCenter = null;
		origin = null;
		super.finalize();
	}
}
