import java.io.*;
import josx.rcxcomm.*;
import josx.platform.rcx.*;
import josx.robotics.*;

/**
  * 
  * 
  * 
  * Author : Swapnil Pathare
  *
 */
public class CarDriveMini extends Thread
{
	/* Power adjustment for motors to go straight */
	static int powerLeft=3;
	static int powerRight=7;

	/* The choices in useMotors	*/
	public static final int stopCar=10;
	public static final int driveStraight=11;
	public static final int verifyObstacle=12;
	public static final int floatCar=13;
	public static final int rotateCar=14;
	public static final int swerveLeft=15;
	public static final int turnBack=16;
	public static final int alignWall=17;

	TimingNavigator navigator =null;
	public boolean overtaking=true;
	public TouchSense ts=null;
	public LightSense ls=null;
	public InfraredSense is=null;
	public volatile int rightBumperHit=0;
	public volatile int leftBumperHit=0;
	public byte carId=20;				// MY CAR IS NUMBER 20
	


	public static void main(String args[]) throws Exception{
		CarDriveMini car=new CarDriveMini();
		car.start();
	}

	public void run()
	{
		Motor.A.setPower(powerLeft);
		Motor.C.setPower(powerRight);
		
		/* Motor.B is actually the TailLight  */
		Motor.B.setPower(powerRight);
		Motor.B.forward();
		

		navigator = new TimingNavigator(Motor.C, Motor.A, 8.12f, 3.835f);
		navigator.forward();

//		ts=new TouchSense(this);
		ls=new LightSense(this);
//		is=new InfraredSense(this);
//		ts.start(); 
		ls.start();
//		is.start();


	}
	
	/***
	* Method avoid_obstacle : accepts variable left,dist ; returns 0 if no collision
	* left=true : avoid by turning from left side
	* left=false: avoid by turning from right side
	* dist		: How far to go
	*
	* Function: moves car around the obstacle and places it ahead of obstacle
	***/
	public int avoidObstacle(boolean left, int dist)
	{	int num=60;	//first rotation
		if (!left)
			num=-60;
		//		|            |
		//		 \          /
		//		  \        /
		//		   |  OR  |     DEPENDING UPON VARIABLE left
		//		   |      |
		//		  /        \
		//		 /          \
		//		|            |

		navigator.rotate(num);	
		navigator.travel(dist/2);	
		if (Sensor.S1.readBooleanValue())
			return(99);
		if (Sensor.S3.readBooleanValue())
			return(88);
		navigator.travel(dist/2);	
		if (Sensor.S1.readBooleanValue())
			return(99);
		if (Sensor.S3.readBooleanValue())
			return(88);
		navigator.rotate(-num);	
		navigator.travel(dist/2);	
		if (Sensor.S1.readBooleanValue())
			return(99);
		if (Sensor.S3.readBooleanValue())
			return(88);
		navigator.travel(dist/2);	
		if (Sensor.S1.readBooleanValue())
			return(99);
		if (Sensor.S3.readBooleanValue())
			return(88);
		navigator.rotate(-num);	
		navigator.travel(dist/2);	
		if (Sensor.S1.readBooleanValue())
			return(99);
		if (Sensor.S3.readBooleanValue())
			return(88);
		navigator.travel(dist/2);	
		navigator.rotate(num);	
		navigator.forward();
		if (Sensor.S1.readBooleanValue())
			return(99);
		if (Sensor.S3.readBooleanValue())
			return(88);

		return 0;
	}

	public synchronized int useMotors(int ch1,int ch2)
	{
		if (ch1==CarDriveMini.driveStraight)
		{
			navigator.forward();
			return 0;
		}
		if (ch1==CarDriveMini.stopCar)
		{
			navigator.stop();
			return 0;
		}
		if (ch1==CarDriveMini.floatCar)
		{
			navigator.stop();
			return 0;
		}
		if (ch1==CarDriveMini.rotateCar)
		{
			navigator.rotate(ch2);
			return 0;
		}
		if (ch1==CarDriveMini.swerveLeft)
		{	//need to check Touch Sensor!
			boolean wasRunning=Motor.A.isMoving() || Motor.C.isMoving();
			navigator.rotate(60);
			for (int i=0;i<4;i++ )
			{
				navigator.travel(8);
				if ((Sensor.S1.readBooleanValue()) || (Sensor.S3.readBooleanValue()))
				{
					return(99);
				}
			}
			navigator.rotate(-60);
			if ((Sensor.S1.readBooleanValue()) || (Sensor.S3.readBooleanValue()))
			{
				return(99);
			}
			if (wasRunning)
			{
				navigator.forward();
			}
			return 0;
		}
		if (ch1==CarDriveMini.verifyObstacle)
		{

			//	perform all operations and give back
			//	aligned car in proper direction
			// return value 0 indicates it is ready to drive
			// return value 99 indicates that there was a collision.


			// move back a little in any case
			navigator.travel(-10);
			int result=1;
			if (ch2<10)
			{	result=avoidObstacle(false,25);	//avoid from right
			}
			else
			{
				result=avoidObstacle(true,25);	//avoid from left
			}
			return result;
		}
		if (ch1==CarDriveMini.turnBack)
		{
			navigator.travel(-10);
			if (ch2==99)
			{
				// left bumper hit most recently...turn left
				navigator.rotate(160);
			}
			else if (ch2==88)
			{
				// right bumper hit most recently...turn right
				navigator.rotate(-160);
			}
			navigator.forward();
			return 0;
		}
		if (ch1==CarDriveMini.alignWall)
		{
			navigator.travel(-10);
			if (ch2==1)
			{	//wall on the right...try rotating left
				navigator.rotate(65);
			}
			else if (ch2==2)
			{	//wall on the left...try rotating right
				navigator.rotate(-65);
			}
			navigator.forward();
			return 0;
		}

		return 0;
	}
}


class TouchSense extends Thread
{
	CarDriveMini car=null;
	public TouchSense(CarDriveMini parentObj)
	{
		// Set the 2 touch sensors in boolean mode
		Sensor.S1.setTypeAndMode(SensorConstants.SENSOR_TYPE_TOUCH, SensorConstants.SENSOR_MODE_BOOL);
		Sensor.S3.setTypeAndMode(SensorConstants.SENSOR_TYPE_TOUCH, SensorConstants.SENSOR_MODE_BOOL);
		car=parentObj;
	}

	public void run()
	{
		Sensor.S3.activate();
		Sensor.S1.activate();
		boolean s1=false,s3=false;
		try{Thread.sleep(100);}catch (Exception e)	{}

		while(true)
		{
			s1=Sensor.S1.readBooleanValue();
			s3=Sensor.S3.readBooleanValue();
			if(s1 || s3)	//touched...handle the collision
			{
//				try{Thread.sleep(1000);}catch (Exception e)	{} 
				//	signal other threads to stop, take exclusive control of the car 
				//	but what if other threads are inside exclusively used methods useIR / useMotors?
				//	Other threads should check for stopFlag as soon as they exit useIR/useMotors
				//
//				car.ls.stopFlag=true;
				car.is.stopFlag=true;

//				while( ! car.ls.stopped);	// wait till other sensors stop

				car.useMotors(CarDriveMini.stopCar,0);


				int bumpers=0;	
				if (s1)
				{
					bumpers++;
				}
				if (s3)
				{
					bumpers+=10;
				}
				int motorValue=car.useMotors(CarDriveMini.verifyObstacle,bumpers);
				if (motorValue==0)
				{
					// obstacle handled. proceed
					car.leftBumperHit=0;
					car.rightBumperHit=0;
					car.useMotors(CarDriveMini.driveStraight,0);
				}
				else
				{
					LCD.showNumber(motorValue);
					if (motorValue==88)
						car.rightBumperHit++;
					else if (motorValue==99)
						car.leftBumperHit++;
					
					if ((car.rightBumperHit>=3) && (car.leftBumperHit >=3))
					{
						// squeezed in the corner or someplace similar. turn back & come out
						car.useMotors(CarDriveMini.turnBack,motorValue);
						car.leftBumperHit=0;
						car.rightBumperHit=0;
					}
					else if (car.rightBumperHit>=3)
					{
						// cud be a wall on the right... angle myself a little left
						car.useMotors(CarDriveMini.alignWall,1);
					}
					else if (car.leftBumperHit>=3)
					{
						// cud be a wall on the left... angle myself a little right
						car.useMotors(CarDriveMini.alignWall,2);
					}
					else
						continue;
				}
				// AFTER COMPLETING all activities & setting car back to driving line, 
				// restart both light & IR
//				car.ls.stopFlag=false;
				car.is.stopFlag=false;

			}
		}
	}
};

class LightSense extends Thread
{
	public boolean stopFlag=false;
	public boolean stopped=false;
	CarDriveMini car=null;
	public LightSense(CarDriveMini parentObj)
	{
		// Set the light sensor in RAW mode
		Sensor.S2.setTypeAndMode(SensorConstants.SENSOR_TYPE_LIGHT, SensorConstants.SENSOR_MODE_RAW);
		car=parentObj;
	}

	public void run()
	{
		Sensor.S2.activate();
		int lightValue=0;
		try{Thread.sleep(100);}catch (Exception e)	{}
		while (true)
		{
			stopped=false;
			while(! stopFlag)
			{	
				// problem to use the useMotors method is that if collision occurs while tailing car
				//	then collision is to be given priority, and tailing has to be ABANDONED...
				//	so a 'break; ' will be required below, if proper tailing/overtaking used.
				
				lightValue=Sensor.S2.readRawValue();
				while((lightValue<700) && (lightValue>600))	
				{
					LCD.showNumber(lightValue);
					car.useMotors(CarDriveMini.floatCar,0);

					if (stopFlag)	//	check if stop request
						break;

					car.useMotors(CarDriveMini.driveStraight,0);
					lightValue=Sensor.S2.readRawValue();
				}
				if (lightValue<=600)
				{
					// Taillight of car in front gets brighter, even though i'm going slow.
					// The car may be stopping just like that, or communicating with IR
					// Overtaking the car directly may miss IR (landmark). Wait and see for sometime
					//  if the car moves. If it doesn't move, overtake it. 
					car.useMotors(CarDriveMini.stopCar,0);
					//wait for 5 seconds to see if the car in front moves
					try{Thread.sleep(5000);}catch (Exception e)	{} 
					lightValue=Sensor.S2.readRawValue();
					if (lightValue>650)
					{
						car.useMotors(CarDriveMini.driveStraight,0);

						if (stopFlag)	//	check if stop request
							break;

						continue;
					}
					//car hasn't moved or has moved very little. Overtake it !
					
					// what if obstacle comes during overtaking? keep goal co-ord. in memory
					car.overtaking=true;
					int result=car.avoidObstacle(false,30);	// overtake from right, cover distance of 60
					car.overtaking=false;

				}
				if (stopFlag)	//	check if stop request
					break;
			}
			stopped=true;
			try
			{
				while (stopFlag)
				{
					Thread.sleep(500);
				}
			}
			catch (Exception e)	{}
		}
	}
};

class InfraredSense extends Thread
{
	CarDriveMini car=null;
	public InfraReceive iRecv ;
	public InfraSend iSend;
	public volatile boolean read=false;
	public volatile boolean written=false;
	public boolean landmark=false;
	public boolean stopFlag=false;
	public boolean stopped=false;
	public int readValue=0;
	public int writeValue=0;
	
	public RCXPort rcxport=null;
	public DataInputStream  ris;
	public DataOutputStream ros;

	public InfraredSense(CarDriveMini parentObj)
	{
		// Set the infrared light port
		car=parentObj;
		writeValue=car.carId;
		try
		{
			rcxport=new RCXPort();
			ris=new DataInputStream(rcxport.getInputStream());
			ros=new DataOutputStream(rcxport.getOutputStream());
		}
		catch (Exception e)
		{
		}
	}
	public void run()
	{
		boolean respawnRead=true;
		boolean respawnWrite=true;

		boolean touched;
		iRecv=new InfraReceive(this);
		iRecv.start();
		iSend=new InfraSend(this);
		iSend.start();
		while (true)
		{
			Sound.beep();
			touched=false;
			stopped=false;
			read=false;
			written=false;
			if (respawnRead)
			{
				iRecv.resume=true;
			}
			if (respawnWrite)
			{
				iSend.resume=true;
			}
			while (true)
			{
				landmark=false;
				if (stopFlag)
				{
					break;
				}
				if (read && written)
				{
					// a value read at this point of time, 
					//	> 40 is communication between other IRs
					if ((readValue>=30) && (readValue <=40))
					{
						// Landmark in front. quit
						landmark=true;
						break;
					}
					else if (readValue<30)
					{
						// car in front. swerve left
						// also reset the booleans to avoid false alarms while car swerves left
						read=false;
						written=false;
						touched=(car.useMotors(CarDriveMini.swerveLeft,0)==99)?true:false;
						if (touched)
						{
							break;
						}
						iRecv.resume=true;
						iSend.resume=true;
					}
					else 
					{
						// landmark is communicating with another car.
						//	wait for sometime and retry.
						car.useMotors(CarDriveMini.stopCar,0);
						LCD.showNumber(3100);
						try{Thread.sleep(4000);}catch (Exception e)	{}
						iRecv.resume=true;
						iSend.resume=true;
					}
				}
				else if (read && (!written))
				{
					// value is read, but communication broke before value was written
					// or maybe i'm checking just before value is about to be written
					// give it some time...
					try{Thread.sleep(500);}catch (Exception e)	{}
					if (!written)
					{
						// surely communication error. now the read thread has ended!
						// restart the read thread, so that next time there is no communication
						//problem bcos of 'only write'

						iRecv.resume=true;
						iSend.resume=false;
					}	
					else 
					{
						iRecv.resume=false;
						iSend.resume=false;
					}

				}
				else if ((!read) && written)
				{
					// value is written, but communication broke before value was read
					// or maybe i'm checking just before value is about to be read
					// give it some time...
					try{Thread.sleep(500);}catch (Exception e)	{}
					if (!read)
					{
						// surely communication error. now the write thread has ended!
						// restart the write thread, so that next time there is no communication
						//problem bcos of 'only read'
						iRecv.resume=false;
						iSend.resume=true;
					}
					else 
					{
						iRecv.resume=false;
						iSend.resume=false;
					}
				}
			}
			if (touched)
			{
				stopFlag=true;
				stopped=true;
			}
			//landmark is present. stop the car, communicate with it, get directions.
			// first stop light sensor. stopping touch sensor is unnecesary, since it is not 
			// going to feel anything on bumpers
			// what if IR is found WHILE touch sensor module was circumventing an obstacle?
//swapz:do
			if ((!stopFlag) && landmark)
			{
				// TouchSense has not asked me to stop... hence it is not performing any task
				// After stopping car, there is rare chance that TouchSense may perform any op.
				//  now once again verify that Landmark is in front, else blocking calls will
				//  put car in trouble ..
				car.useMotors(CarDriveMini.stopCar,0);

				read=false;
				written=false;
				iRecv.resume=true;
				iSend.resume=true;
				try{Thread.sleep(1500);}catch (Exception ex)	{}

				if(read && written && (readValue>=30) && (readValue<=40))
				{	//yes, Landmark surely is in front. proceed with data exchange
					int tower=readValue;
					int angle=0,direction=0;
					try
					{
						ros.write(199);		//value to suggest i'm ready for pt.to pt. communication
						if(ris.read()==199)
						{
							// the angle received is pre-incremented by 50, to avoid another
							// car assuming it as TowerId or CarId
							angle=ris.read();		//values: 0 - 180 sent as (angle+50).
							direction=ris.read();	//values: 190 (anticlock), 200 (clock)
							if (direction==190)
							{
								direction=angle-50;
							}
							else if (direction==200)
							{
								direction=-(angle-50);
							}
							car.useMotors(CarDriveMini.rotateCar,direction);
							car.useMotors(CarDriveMini.driveStraight,0);

							try{Thread.sleep(2000);}catch (Exception ex)	{}
							
						}
					}
					catch (Exception e)
					{
						LCD.showNumber(666);
					}
				}
				else if (read && (!written))
				{
					// some problem, so go to next iteration, but spawn read thread only,
					// write thread is already on, and blocked
					respawnRead=true;
					respawnWrite=false;
					continue;
				}
				else if (written && (!read))
				{
					// some problem, so go to next iteration, but spawn read thread only,
					// write thread is already on, and blocked
					respawnRead=false;
					respawnWrite=true;
					continue;
				}
				else if ((!read) && (!written))
				{
					//	!read && !written
					respawnRead=false;
					respawnWrite=false;
					continue;
				}
			}
			
			try
			{
				while (stopFlag)
				{
					stopped=true;
					Thread.sleep(1000);
				}
			}
			catch (Exception e)	{}
			
			respawnRead=true;
			respawnWrite=true;
			
		}
	}

};

class InfraReceive extends Thread
{
	public DataInputStream ris;
	public InfraredSense is;
	public boolean resume;
	public InfraReceive(InfraredSense infra)
	{
		is=infra;
		ris=infra.ris;
	}
	public void run()
	{
		while (true)
		{
			while(!resume)
			{
				try{Thread.sleep(200);}catch (Exception ex)	{}
			}
			resume=false;
			is.read=false;
			try
			{
				is.readValue=ris.read();
			}
			catch (Exception e)
			{			LCD.showNumber(777);
					try{Thread.sleep(800);}catch (Exception ex)	{}
			}
			is.read=true;
			
		}
	}
};

class InfraSend extends Thread
{
	public DataOutputStream ros;
	public InfraredSense is;
	public boolean resume;
	public InfraSend(InfraredSense infra)
	{
		is=infra;
		ros=infra.ros;
	}
	public void run()
	{
		while (true)
		{
			while(!resume)
			{
				try{Thread.sleep(200);}catch (Exception ex)	{}
			}
			resume=false;
			is.written=false;
			try
			{
				ros.write(is.writeValue);
			}
			catch (Exception e)
			{
				LCD.showNumber(888);
					try{Thread.sleep(300);}catch (Exception ex)	{}
			}
			
			is.written=true;
		}
	}
};