package primitives.machines;
import java.awt.*;
import java.awt.event.*;
import primitives.geomtry.*;
/**
*A class encapsulating a Pantograph linkage.
*@see FunctionalLinkage
*@see Adder
*@see TranslatorApplet
*@author Dori Eldar
*/
public class ScalarMultiplier1 extends Machine implements FunctionalLinkage{
	/**
	*The parameter used by the machine for computing its state.
	*scalar is allways >1
	*@see #lamda
	*/
	double scalar;
	/** 
	*The scalar multiplication parameter
	*/
	double lamda;
	 double tBar,sBar;
	 int output;
	 int input1,input2;
	 /**
	 *Determines if this object should have 2 input joints or one anchor pinned at relative(0,0)
	 */
	 boolean fixed = false;
	 Rectangle d;
	 /**
	 *The relative origin 
	 */
	 Point origin;
	 /**
	 *@param d the relative rectangle the Pantograh should be drawn in. The origin is set to 
	 *the middle of this rectangle.
	 *@param scalar the parameter of multiplication.
	 *@param fixed Determines if this object should have 2 input joints or one anchor pinned at relative(0,0).
	 */
	 public ScalarMultiplier1(Rectangle d,double scalar,boolean fixed){
		  super(6,0,null);
		  
		  this.d = d; 
		  for(int i=0;i<6;i++) dJoints[i] = new Coordinate();
		  setScalar(scalar);
		   outputs[0] = new Coordinate();
	  	   this.fixed = fixed;
		   inputs[0] = new Coordinate(d.x+(int)(d.width/2),d.y+(int)(d.height/2));
		   origin = new Point((int)Math.rint(inputs[0].x),(int)Math.rint(inputs[0].y));
		   if(lamda>0)
				tBar = Math.min(d.width,d.height)/(4*this.scalar);
		   else tBar = Math.min(d.width,d.height)/(2.5*this.scalar);
		   sBar = tBar;
		   if (lamda>0) inputs[1] = new Coordinate(inputs[0].x+sBar,inputs[0].y);
		   else inputs[1] = new Coordinate(inputs[0].x+sBar*(this.scalar-1),inputs[0].y+tBar);
		   try{
		   setInputJoints(inputs);
		   }catch(Exception e){}
	  }
	  /**
	  *Used to translate between the parameter used by the object (allways >1)
	  *and the scalar used to compute the output joint location.
	  * the translation is done by setting different input joints.
	  *@param scalar the new scalar to set to. 
	  *@see #scalar
	  */
	  void setScalar(double scalar){
		   lamda = scalar;
		   if (scalar>1){
			   this.scalar = scalar;
			   output = 2;
			   input1=0;
			   input2=1;
		   }else if(scalar>0){
			   this.scalar = 1/scalar;         
			   output = 1;
			   input1 = 0;
			   input2 = 2;
		   }else if(scalar<0){
			   this.scalar = 1-scalar;
			   output = 2;
			   input1 = 1;
			   input2 = 0;
		   }
	   }
	   Coordinate[] outputs = new Coordinate[1];
	   /**
	   *@param inputs an array of 2 Coordinate objects. If the Pantograph is fixed 
	   *the first location is overlooked.
	   *@return a one size array containing the location of the output joint.
	   *@see FunctionalLinkage
	   */
	   public Coordinate[] forceInputJoints(Coordinate[] inputs){
			  double[] annulus = getAnnolus();
			  double d = Geomtry.distance(inputs[0],inputs[1]);
			  if (d<annulus[0]+20){
				  sBar = Math.max(tBar,sBar);
			  }else if (d>annulus[1]-20){
				  if(lamda>1||lamda<0){
					  sBar = Math.max(sBar,d/2+30);
				  }else{
					  sBar = Math.max(sBar,d/2+30)*scalar;
				  }
			  }
			  tBar = sBar;
			  try{
				  return setInputJoints(inputs);
			  }catch(MachineException e){}
			  return getOutputJoints();
	   }
	   /**
	   *@param inputJoints an array of 2 Coordinate objects. If the Pantograph is fixed 
	   *the first location is overlooked.
	   *@retrun return a one size array containing the location of the output joint.
	   *@exception MachineException if the new input location is not valid. 
	   *@see FunctionalLinkage
	   */
	   public Coordinate[] setInputJoints(Coordinate[] inputJoints)throws MachineException{
		   	savePoints();
			if(!fixed||dJoints[input1].x==0)
				dJoints[input1].move(inputJoints[0].x,inputJoints[0].y);
			dJoints[input2].move(inputJoints[1].x,inputJoints[1].y);
			dJoints[output].move(dJoints[input1].x+(dJoints[input2].x-dJoints[input1].x)*lamda,
					dJoints[input1].y+(dJoints[input2].y-dJoints[input1].y)*lamda);
			try{
		  		dJoints[5] = Geomtry.getTriPointEx(dJoints[0],scalar*sBar,dJoints[2],scalar*tBar,
					dJoints[5],null);
				dJoints[3] = Geomtry.getMidPoint(dJoints[0],dJoints[5],1-1/scalar);
				dJoints[4] = Geomtry.getMidPoint(dJoints[2],dJoints[5],1/scalar);

		   }catch(ArithmeticException e){
				restorePoints();
			   throw new MachineException("inputs out of Domain");
		   }
		   outputs[0].move(dJoints[output].x,dJoints[output].y);
		   return outputs;
		}
		Coordinate[] inputs = new Coordinate[2];
		/**
		*@retrun a 2 size array containing the locations of the input joints.
		*@see FunctionalLinkage
		*/
	   public Coordinate[] getInputJoints(){
			  inputs[0].move(dJoints[input1].x,dJoints[input1].y);
			  inputs[1].move(dJoints[input2].x,dJoints[input2].y);
			  return inputs;
	   }
	   /**
	   *@retrun a 1 size array containing the location of the output joint
	   *@see FunctionalLinkage
	   */
	   public Coordinate[] getOutputJoints(){
		   outputs[0].move(dJoints[output].x,dJoints[output].y);
		   return outputs ;
	   }
	   /**
	   *Drawing the pantoghraph
	   *@param g the graphic context to draw to.
	   */
	   public void redraw(Graphics g){
		   Color c = g.getColor();
		   updatePoints(dJoints,joints);
		   drawLine(g,joints[0],joints[5]);
		   drawLine(g,joints[5],joints[2]);
		   drawLine(g,joints[1],joints[3]);
		   drawLine(g,joints[1],joints[4]);
	   	   g.setColor(Color.yellow);
		   for(int i=3;i<6;i++) Geomtry.drawJoint(g,joints[i]);
		   g.setColor(Color.green);
		   Geomtry.drawJoint(g,joints[input2]);
		   if(!fixed)
			   Geomtry.drawJoint(g,joints[input1]);
		   else{
			   g.setColor(Color.red); 
			   Geomtry.drawAnchor(g,joints[input1]);
		   }
		   g.setColor(Color.red);
		   Geomtry.drawJoint(g,joints[output]);
		   if(activeJoint>-1&&activeJoint!=4&&(!fixed||activeJoint!=input1)){
			   g.setColor(Color.blue);
			   Geomtry.drawJoint(g,joints[activeJoint]);
		   }
		   g.setColor(c);
	   }
	   double[] parameters = new double[2];
	   /**
	   *@retrun an array containing scalar and lamda fiels (in this order)
	   *@see #lamda
	   *@see #scalar
	   */
	   public double[] getParameters(){
		   parameters[0] = scalar;
		   parameters[1] = lamda;
		   return parameters;
	   }
	   /**
	   *@param parameters the first member of this array should specify the new scalar
	   *@exception MachineException if the new current location of the input joints
	   * is not valid with the new paraeters.
	   *@see FunctionalLinkage
	   */
	   public void setParameters(double[] parameters)throws MachineException{
		   setScalar(parameters[0]);
		   setInputJoints(getInputJoints());
	   }
	   int activeJoint = -1;
	   Coordinate temp = new Coordinate();
	   /**
	   *@see FunctionalLinkage
	   */
	   public int mouseMoved(MouseEvent m){
		   Point p = m.getPoint();
		   activeJoint = -1;
		   int i=1;
		   temp.move(p.x,p.y);
		   if(Geomtry.distance(dJoints[input2],temp)<8) activeJoint = input2;
		   else if(Geomtry.distance(dJoints[input1],temp)<8) activeJoint = input1;
		   else if(Geomtry.distance(dJoints[output],temp)<8) activeJoint = output;
		   else  while((activeJoint<0)&&(++i<dJoints.length))
			   if(Geomtry.distance(dJoints[i],temp)<8) activeJoint = i;
		   return activeJoint;   
	   }
	   /**
	   * Process a a change according to the activeJoint (a joint the mouse pointer is over).
	   * @exception MachineException .
	   * @see FunctionalLinkage
	   */
	   public void mouseDragged(MouseEvent m) throws MachineException{
		   Point p = m.getPoint();
		   if(activeJoint==input1&&!fixed){
			   inputs[0].move(p.x,p.y);
			   inputs[1].move(Math.rint(dJoints[input2].x),Math.rint(dJoints[input2].y));
			   setInputJoints(inputs);
		   }else
		   if(activeJoint==input2){
			   inputs[0].move(Math.rint(dJoints[input1].x),Math.rint(dJoints[input1].y));
			   inputs[1].move(p.x,p.y);
			   setInputJoints(inputs);
		   }else if (activeJoint==3){
			   temp.move(p.x,p.y);
			   double tlamda = lamda;
			   double tscalar = scalar;
			   double d = Geomtry.distance(dJoints[0],temp);
			   double total = scalar*sBar;
			   double total2 = scalar*tBar;
			   if ((d>total-15)||(d<15)) throw new MachineException("scalar out of range");
			   sBar = d;
			   scalar = total/sBar;
			   scalar = Math.rint(scalar*10)/10;
			   sBar = total/scalar;
			   tBar = total2/scalar;
			   dJoints[3] = Geomtry.getMidPoint(dJoints[0],dJoints[5],1-1/scalar);
			   Coordinate t = Geomtry.getPointByVector(dJoints[3],tBar,
						Geomtry.getAngle(dJoints[3],dJoints[1]));
			   
			   if (lamda>1){
				   lamda = scalar;
					inputs[0].move(Math.rint(dJoints[0].x),Math.rint(dJoints[0].y));
				   inputs[1].move(Math.rint(t.x),Math.rint(t.y));
			   }else if(lamda>0){
				   lamda = 1/scalar;
				   inputs[0].move(dJoints[0].x,dJoints[0].y);
				   inputs[1].move(Math.rint(dJoints[0].x+(dJoints[1].x-dJoints[0].x)*scalar),
					Math.rint(dJoints[0].y+(dJoints[1].y-dJoints[0].y)*scalar));
			   }else{
				   lamda = 1-scalar;
				   inputs[0].move(dJoints[1].x,dJoints[1].y);
			   	   inputs[1].move(Math.rint(dJoints[0].x+dJoints[1].x-t.x),
					   Math.rint(dJoints[0].y+dJoints[1].y-t.y));
			   }try{
					setInputJoints(inputs);
			   }catch(MachineException e){
					lamda = tlamda;
					scalar = tscalar;
					throw e;
			   }

		   } else if (activeJoint==5){
			   if(!fixed){
			   double dx =  p.x-dJoints[5].x;
			   double dy = p.y-dJoints[5].y;
			   for(int i=0;i<6;i++)
				 dJoints[i].translate(dx,dy);
			   }else{
				   temp.move(p.x,p.y);
				   double sd = Geomtry.distance(temp,dJoints[0]);
				   double td = Geomtry.distance(temp,dJoints[2]);
				   if(td<50||sd<50)throw new MachineException(0);
				   double tsBar = sBar;
				   double ttBar = tBar;
				   sBar = sd/scalar;
				   tBar = td/scalar;
				   inputs[0].move(dJoints[input1]);
				   inputs[1].move(dJoints[input2]);
				   try{
					   setInputJoints(inputs);
				   }catch(MachineException e){
					   tBar = ttBar;
					   sBar = tsBar;
					   throw e;
				   }
			   }

		   }
		}
	/**
	*@return the annolus of input domain
	*/
	double[] getAnnolus(){
		double[] result = new double[2];
		if(lamda>1||lamda<0){
			result[0] = Math.abs(sBar-tBar);
			result[1] = Math.abs(sBar+tBar);
		}else{
			result[0] = Math.abs(sBar-tBar)*scalar;
			result[1] = Math.abs(sBar+tBar)*scalar;
		}
		return result;
	}
	final static String outputStr = "Output Joint (";
	final static String inputStr = "Move input joint:(";
	final static String	setStr = "Set scalar multiplier: ";
	final static String joint5Str1 = "Move 2 input joints";
	final static String joint5Str2 = "Set Input Domain:";
	final static String def = "ScalarMultiplier: ";
	/**
	*@see FunctionalLinkage
	*/
	public String getActiveStr(int activeJoint){
		String str = lamda+"";
		str = str.substring(0,str.indexOf(dot)+2);
		if (activeJoint>-1&&activeJoint<3){
			int   x = joints[activeJoint].x-origin.x;
			int   y = origin.y-joints[activeJoint].y;
			if (activeJoint==output)return outputStr+x+comma+y+bracket;
			else if(!fixed||activeJoint!=input1)return inputStr+x+comma+y+bracket;
		}
		if (activeJoint==3) return setStr+str;
		if (activeJoint==5){
			if(!fixed)return joint5Str1;
			else{				
				return joint5Str2+(int)getAnnolus()[0]+annulus+
				(int)getAnnolus()[1];
			}
		}
		return def+str;
			
	}
	/**
	*@see FunctionalLinkage
	*/
	public int getActiveJoint(){
		   return activeJoint;
	}
	/**
	*@see FunctionalLinkage
	*/
	public void setActiveJoint(int activeJoint){
		this.activeJoint = activeJoint;
	}



}
