/* In this reference program, comments within multi-line comment indicators such
   as this one indicate a point on the general instructions and regulations.
   Comments within single-line comment indicators pertain to the actual program.
   You do NOT have to use this convention for your assignment submissions. */
/* You MUST identify yourself in comments at the beginning of each source code
   file you submit. */
// Archibald Haddock (123456789)
// COMP 202-A, Section 0 (Fall 2007)
// Instructor: Cuthbert Calculus
// Assignment 0, Question 0

import java.util.Random;

/* You MAY add Javadoc comments to your submissions, but it's NOT necessary.
   Javadoc comments start with /** and end with * followed immediately by /.
   They are used to generate HTML documentation automatically with a tool called
   javadoc; for example, the documentation about the Java API was generated
   using this tool. */
/**
 * A <code>Cyclist</code> object represents an athlete which participates in a
 * <code>BikeRace</code>. The <code>Cyclist</code> class manages the athlete's
 * values such as his/her current and maximum speed, his/her endurance, his/her
 * status (dead, resting, racing), and his/her race status (the number of laps
 * completed and the time elapsed since the start of the race).
 */
/* The '{' which indicates the beginning of the body of a class MUST be on the
   same line as the class declaration. */
/* Class names start with an upper-case letter, with the rest of the class name
   in lower case. However, new words within a class name start with an
   upper-case letter. Words within a class name are all concatenated together,
   without any characters between them (for example, 'MyOtherClass'). */
public class Cyclist {
	/* Increase the indentation level by 1 each time you encounter a '{',
	   regardless of whether '{' marks the beginning of a class, method, or
	   loop body, or the branch of a conditional statement. Likewise, decrease
	   the indentation level by 1 each time you encounter a '}', regardless of
	   whether '}' marks the end of a class, method, or loop body, or the
	   branch of a loop body. */
	
	/* Variable names start with a lower-case letter, with the rest of the
	   variable name also in lower case. However, any words within a variable
	   name which are not the first word (the second word, the third word, etc.)
	   should start with an upper-case letter. Words within a variable name are
	   all concatenated together, without any characters between them (for
	   example, 'myMaximumSpeed'). */
	/* Variable names MUST be meaningful and give a good indication of what
	   the variable will be used for. Variable names like 'a', 'b', 'c',
	   etc. are usually NOT acceptable. Exceptions include loop indeces
	   (which SHOULD be called i, j, and k) or variables representing
	   numbers. */
	/* Instance variables MUST be private. */
	private String name;
	private int endurance;
	private final int maximumSpeed;
	private int speed;
	private boolean resting;
	private CyclistTracker tracker;
	
	// Only one random number source for all cyclists.
	private static final Random randomSource = new Random();
	
	/* Constants names are all upper-case, with words separated by an
	   underscore (for example, 'TIME_TO_COMPLETE'). */
	private static final double REST_TIME = 3.0;

	/**
	 * Creates a new <code>Cyclist</code>.
	 * @param name The <code>Cyclist</code>'s name
	 * @param endurance The <code>Cyclist</code>'s endurance
	 * @param maximumSpeed The maximum speed at which the <code>Cyclist</code>
	 *        can go. 
	 */
	/* The '{' which indicates the beginning of the body of a method or 
	   constructor SHOULD be on the same line as the method header. */
	/* All methods required by the assignment specification MUST be public. */
	public Cyclist(String name, int endurance, int maximumSpeed) {
		this.name = name;
		this.endurance = endurance;
		this.maximumSpeed = maximumSpeed;
		this.speed = maximumSpeed;
		this.tracker = new CyclistTracker();
		this.resting = false;
	/* Method or constructor body ends. Indentation level decreases by 1 so that
	   the '}' which indicates the end of the method or constructor has the same
	   indentation level as the method or constructor header. */
	}

	/**
	 * Returns the <code>Cyclist</code>'s name.
	 * @return the <code>Cyclist</code>'s name
	 */
	/* Identifiers for methods obey the same rules as identifiers for local and
	   instance variables: all lower-case, except the first letter of a new word
	   within a method name, which is upper-case (for example,
	   'computeThisResult()'. */
	public String getName() {
		return name;
	}

	/**
	 * Resets the <code>Cyclist</code> to the same state as he/she would be in
	 * if he/she had just been created.
	 */
	public void reset() {
		this.speed = this.maximumSpeed;
		this.resting = false;
		this.tracker.reset();
	}

	/**
	 * Returns the number of laps the <code>Cyclist</code> has completed so
	 * far.
	 * @return the number of laps completed so far by the <code>Cyclist</code>
	 */
	public int getLapsCompleted() {
		return this.tracker.getLapsCompleted();
	}

	/**
	 * Returns the <code>Cyclist</code>'s endurance.
	 * @return the <code>Cyclist</code>'s endurance
	 */
	public int getEndurance() {
		return endurance;
	}

	/**
	 * Returns the maximum speed at which the<code>Cyclist</code> can ride
	 * his/her bike.
	 * @return the maximum speed at which the <code>Cyclist</code> can ride
	 *         his/her bike
	 */
	public int getMaximumSpeed() {
		return maximumSpeed;
	}

	/**
	 * Returns the speed at which the <code>Cyclist</code> is currently riding
	 * his/her bike.  
	 * @return the speed at which the <code>Cyclist</code> is currently
	 *         riding his/her bike.
	 */
	public int getSpeed() {
		return speed;
	}

	/**
	 * Returns the time that has elapsed since the <code>Cyclist</code> started
	 * the race.
	 * @return the time that has elpased since the <code>Cyclist</code> started
	 *         the race
	 */
	public double getTimeSoFar() {
		return this.tracker.getTotalTime();
	}

	/**
	 * Returns whether the <code>Cyclist</code> is alive or dead.
	 * @return <code>true</code> if the <code>Cyclist</code> is alive,
	 *         <code>false</code> if he/she is dead.
	 */
	public boolean isAlive() {
		return (this.endurance != 0);
	}

	/**
	 * Returns whether the <code>Cyclist</code> is currently racing or resting.
	 * @return <code>true</code> if the <code>Cyclist</code> is resting,
	 *         <code>false</code> if he/she is racing.
	 */
	public boolean isResting() {
		return this.resting;
	}

	/**
	 * Kills the <code>Cyclist</code>.
	 */
	public void kill() {
		this.endurance = 0;
	}

	/**
	 * Returns whether the <code>Cyclist</code> is done. A <code>Cyclist</code>
	 * is done if he/she is dead or if he/she has completed the race.
	 * @param track The <code>RaceTrack</code> on which the race takes place
	 * @return <code>true</code> if the <code>Cyclist</code> is either dead or
	 *         has finished the race, <code>false</code> if he/she is still
	 *         racing. 
	 */
	public boolean isDone(RaceTrack track) {
		return !this.isAlive() || this.hasFinishedRace(track);
	}

	/**
	 * Returns whether the <code>Cyclist</code> has finished the race.
	 * @param track The <code>RaceTrack</code> on which the race takes place
	 * @return <code>true</code> if the <code>Cyclist</code> has finished the
	 *         race, <code>false</code> otherwise
	 */
	public boolean hasFinishedRace(RaceTrack track) {
		return (this.tracker.getLapsCompleted() == track.getNumberLaps());
	}

	/**
	 * Makes the <code>Cyclist</code> attempt to race another lap, according to
	 * pre-defined, hardcoded rules.
	 * @param track the <code>RaceTrack</code> on which the race takes place
	 * @return the time it took the <code>Cyclist</code> to complete the lap
	 */
	public double raceLap(RaceTrack track) {
		/* It is not necessary for the variables called 'time', 'lapLength',
		   'baseTime' and 'timeModifier' to retain their respective values after
		   the method has finished executing. Therefore, they MUST be declared
		   as local variables. */
		/* Local variables are declared at the beginning of a method. */
		double time;
		double lapLength;
		double baseTime;
		double timeModifier;

		// Prevent the compiler from whining about unitialized variables
		time = 0;

		/* Comments should explain your program and be meaningful. The comment
		   below is meaningful. A comment which says "increment x" before the
		   statement
		   	x = x + 1;
		   is NOT meaningful. */ 
		// Do not process a dead cyclist or one that has finished the race.
		/* The '{' which indicates the beginning of a branch in a conditional
		   statement SHOULD be on the same line as the 'if' statement, or the
		   'else' clause. */
		if (this.isAlive() && !this.hasFinishedRace(track)) {
			if ((this.speed == 0) && !resting) {
				// If the cyclist is resting, add 3 minutes to his/her chrono
				// without incrementing the number of completed laps, and reset
				// his current speed to his maximumSpeed for the next lap.
				time = REST_TIME;
				this.resting = true;
				this.speed = this.maximumSpeed;
			} else {
				if (this.resting) {
					this.resting = false;
				}

				// The cyclist is racing.
				// Check if he should be killed...
				if (randomSource.nextInt(15) == 0) {
					this.kill();
				} else {
					// Compute the time it took
					// to complete the current lap.
					lapLength = track.getLapLength();

					if (track.getTerrainType() == RaceTrack.FLAT) {
						baseTime = lapLength / this.speed;
					} else {
						baseTime = lapLength / (this.speed * endurance / 10.0);
					}

					// Compute the time modifier: +/- the length of a lap
					// as minutes divided by 2.
					timeModifier = (lapLength / 2) * randomSource.nextDouble();
					if (randomSource.nextInt(2) == 0) {
						timeModifier = -timeModifier;
					}

					// Compute the time for the current lap as the sum
					// of the base time and the modifier
					time = baseTime * 60 + timeModifier;
					if (time <= 0.0) {
						time = 1.0;
					}

					// Adjust the cyclist's current speed. If the result is
					// negative adjust back to 0 (signalling that the
					// cyclist needs to rest for the next lap
					this.speed = this.speed - ((10 - this.endurance) * 2);
					if (this.speed < 0) {
						this.speed = 0;
					}

					// Increment the number of completed laps
					this.tracker.completeLap();
				}
			}
		/* Branch ends. Indentation level decreases by 1 so that the '}' has the
		   same indentation level as the 'if' statement or the 'else' clause. */
		}

		// Update the cyclist's chrono and return the time
		// it took to complete the current lap.
		this.tracker.addTime(time);
		return time;
	}

/* Class body ends. Indentation level decreases by 1 so that the '}' which
   indicates the end of the class has the same indentation level as the class
   declaration. */
}
