// Archibald Haddock (123456789)
// COMP 202-A, Section 0 (Fall 2007)
// Instructor: Cuthbert Calculus
// Assignment 0, Question 0

import java.util.Random;
import java.util.Scanner;

/* The code implementing the user interface MUST be separated from the
   application code. This enables you to easily change the user interface
   without breaking the application code. It also enables graders to hook user
   interfaces which are tailor-made for automated testing. */
/**
 * An instance of the <code>BikeRaceUI</code> allows the user to set race
 * details and displays the progress and results of a <code>BikeRace</code>.
 */
public class BikeRaceUI {
	// Determines whether the level of detail about the race which should be
	// displayed.
	private boolean detailsOn;

	// Constants.
	private static final String MANUAL = "m";
	private static final String AUTOMATIC = "a";
	private static final String FLAT = "f";
	private static final String HILLY = "h";
	private static final String YES = "Y";
	private static final String NO = "N";

	// Avoid passing the keyboard variable around.
	private static Scanner keyboard;

	/**
	 * Creates an instance of the <code>BikeRaceUI</code> class.
	 * @param detailsOn indicates the level of detail about the race the user
	 *        wants to have displayed
	 */
	public BikeRaceUI(boolean detailsOn) {
		this.detailsOn = detailsOn;
	}

	/**
	 * Notifies the user interface that a <code>Cyclist</code> has died.
	 * @param cyclist the <code>Cyclist</code> who died
	 */
	public void cyclistDies(Cyclist cyclist) {
		System.out.println("OOoohh, " + cyclist.getName()
				+ " blows a tire and flies off the road.");
	}

	/**
	 * Notifies the user interface that a <code>Cyclist</code> is dead.
	 * @param cyclist the dead <code>Cyclist</code>
	 */
	public void cyclistIsDead(Cyclist cyclist) {
		System.out.println(cyclist.getName() + " is dead.");
	}

	/**
	 * Notifies the user interface that a <code>Cyclist</code> is resting.
	 * @param cyclist the resting <code>Cyclist</code>
	 * @param track the <code>RaceTrack</code> on which the race is taking place 
	 * @param time the time elapsed since the <code>Cyclist</code> started the
	 *        race
	 */
	public void cyclistRests(Cyclist cyclist, RaceTrack track, double time) {
		System.out.println(cyclist.getName()
				+ " can't take it anymore and takes a " + Math.round(time)
				+ " minute break.");
		if (this.detailsOn) {
			System.out.println("  Current speed: " + cyclist.getSpeed()
					+ " km/h, total time so far: "
					+ Math.round(cyclist.getTimeSoFar()) + " minutes");
		}
	}

	/**
	 * Notifies the user interface that a <code>Cyclist</code> has completed a
	 * lap.
	 * @param cyclist the <code>Cyclist</code> which completed the lap.
	 * @param track the <code>RaceTrack</code> on which the race is taking place
	 * @param time the time elapse since the <code>Cyclist</code> started the
	 *        race
	 */
	public void cyclistCompletesLap(Cyclist cyclist, RaceTrack track,
			double time) {
		System.out.println(cyclist.getName() + " completes lap "
				+ cyclist.getLapsCompleted() + " in " + Math.round(time)
				+ " minutes.");
		if (this.detailsOn) {
			System.out.println("  Current speed: " + cyclist.getSpeed()
					+ " km/h, total time so far: "
					+ Math.round(cyclist.getTimeSoFar()) + " minutes");
		}
	}

	/* You MUST avoid copy and pasting code fragments if you can. In this case,
	   the displayResult() method was defined to avoid copying the exact code it
	   contains in three different methods: displayWinner(), displayDead(), and
	   displayLoser(). */
	// Helper method useful to display results without having to copy and paste
	// code.
	private static void displayResult(Cyclist cyclist, RaceTrack track,
			String sentence) {
		double totalTime; 
		int lapsCompleted; 
		int distance; 
		double averageSpeed;

		// Compute the total distance by multiplying the number of laps
		// completed by the lap length.
		totalTime = cyclist.getTimeSoFar();
		lapsCompleted = cyclist.getLapsCompleted();
		distance = lapsCompleted * track.getLapLength();
		
		// Compute the average speed and avoid division by 0 if the
		// cyclist died before starting.
		if (totalTime == 0) {
			averageSpeed = 0;
		} else {
			averageSpeed = 60 * distance / totalTime;
		}

		System.out.println(cyclist.getName() + sentence + lapsCompleted
				+ " laps in " + Math.round(totalTime)
				+ " minutes at an average speed of " + averageSpeed + ".");
	}

	/**
	 * Notifies the user interface of the winner of the race.
	 * @param cyclist the <code>Cyclist</code> who won the race
	 * @param track the <code>RaceTrack</code> on which the race took place
	 */
	public void displayWinner(Cyclist cyclist, RaceTrack track) {
		displayResult(cyclist, track, " wins the race! by having completed ");
	}

	/**
	 * Notifies the user interface that a <code>Cyclist</code> lost the race.
	 * @param cyclist a <code>Cyclist</code> who lost the race 
	 * @param track the <code>RaceTrack</code> on which the race took place
	 */
	public void displayLoser(Cyclist cyclist, RaceTrack track) {
		displayResult(cyclist, track, " is a loser, but completed ");
	}

	/**
	 * Notifies the user interface that a <code>Cyclist</code> died during the
	 * race.
	 * @param cyclist a <code>Cyclist</code> who died during the race 
	 * @param track the <code>RaceTrack</code> on which the race took place
	 */
	public void displayDead(Cyclist cyclist, RaceTrack track) {
		displayResult(cyclist, track, " is no longer with us, but completed ");
	}

	// Helper method which prompts the user for an integer and keeps prompting
	// him/her until he/she types a number that is within the range given
	// by lowBound and highBound.
	private static int getIntFromUser(String message, int lowBound,
			int highBound) {
		int value;
		boolean validValue;

		// Prompt the user.
		System.out.print(message + " [" + lowBound + "-" + highBound + "]: ");
		validValue = false;
		value = -1;
		
		// While the value is not valid, get a value and check it for validity.
		// Prompt the user if the value is invalid.
		while (!validValue) {
			value = Integer.parseInt(keyboard.nextLine());
			if ((value < lowBound) || (value > highBound)) {
				System.out.print("Invalid value, please enter again ["
						+ lowBound + "-" + highBound + "]: ");
			} else {
				validValue = true;
			}

		}
		return value;
	}

	// Helper method which prompts the user for an String and keeps prompting
	// until the user types a String which is one of the two values specified
	// by parameters first and second.
	private static String getStringFromUser(String message, String first,
			String second) {
		String line;
		boolean validString;

		// Propmt the user.
		System.out.print(message);
		validString = false;
		line = "";
		
		// While the value is not valid, get a value and check it for validity.
		// Prompt the user if the value is invalid.
		while (!validString) {
			line = keyboard.nextLine();
			if (!line.equalsIgnoreCase(first) && !line.equalsIgnoreCase(second)) {
				System.out.print("Invalid value, please enter again [" + first
						+ ", " + second + "]: ");
			} else {
				validString = true;
			}
		}
		return line;
	}

	/**
	 * Starts the application.
	 * @param args Command-line parameters, which are ignored by this program
	 */
	public static void main(String[] args) {
		final int NUMBER_CYCLISTS = 3;
		Cyclist[] cyclists;
		RaceTrack track;
		BikeRace race;
		String choice;
		Random randomSource;
		int endurance;
		int speed;
		int number;
		int lapLength;
		int numberLaps;
		boolean terrainType;
		boolean trackDetails;

		// Initialize the keyboard.
		keyboard = new Scanner(System.in);

		// Ask the user whether he/she wants an automatic or manual race.
		choice = getStringFromUser("Simulate an (" + AUTOMATIC
				+ ")utomatic, or a (" + MANUAL + ")anual race? ", AUTOMATIC,
				MANUAL);
		System.out.println();

		// Initialize the Cyclist array
		cyclists = new Cyclist[NUMBER_CYCLISTS];
		track = null;
		randomSource = new Random();

		if (choice.equalsIgnoreCase(AUTOMATIC)) {
			// User wants an automatic race. Generate the Cyclists and the
			// RaceTrack using random values.
			for (int i = 0; i < 3; i++) {
				cyclists[i] = new Cyclist("Cyclist" + (i + 1), randomSource
						.nextInt(11), randomSource.nextInt(60 + 1));
			}
			track = new RaceTrack(randomSource.nextInt(100) + 1, randomSource
					.nextInt(10) + 1, randomSource.nextBoolean());
		} else {
			// User wants a manual race. Prompt the user for values used to
			// create each Cyclist as well as the RaceTrack.
			for (int i = 0; i < cyclists.length; i++) {
				number = i + 1;
				endurance = getIntFromUser("Enter Cyclist" + number
						+ "'s endurance", 0, 10);
				speed = getIntFromUser("Enter Cyclist" + number
						+ "'s initial speed", 1, 60);

				cyclists[i] = new Cyclist("Cyclist" + number, endurance, speed);
			}
			System.out.println();

			lapLength = getIntFromUser("Enter the lap length", 1, 100);
			numberLaps = getIntFromUser("Enter the number of laps", 1, 10);
			choice = getStringFromUser("Is the terrain (" + FLAT + ")lat or ("
					+ HILLY + ")illy? ", FLAT, HILLY);
			System.out.println();

			if (choice.equalsIgnoreCase("f")) {
				terrainType = RaceTrack.FLAT;
			} else {
				terrainType = RaceTrack.HILLY;
			}
			track = new RaceTrack(numberLaps, lapLength, terrainType);

		}

		// Display the details of each Cyclist.
		for (int i = 0; i < cyclists.length; i++) {
			System.out.println(cyclists[i].getName() + " has an endurance of "
					+ cyclists[i].getEndurance()
					+ " and a current maximum speed of "
					+ cyclists[i].getSpeed() + " km/h.");
		}

		// Display RaceTrack details.
		System.out.print("They will be racing on ");
		if (track.getTerrainType() == RaceTrack.FLAT) {
			System.out.print("flat");
		} else {
			System.out.print("hilly");
		}
		System.out.println(" terrain for " + track.getNumberLaps()
				+ " laps of " + track.getLapLength() + " km each.");
		System.out.println();

		// Ask the user whether details should be on or off.
		choice = getStringFromUser(
				"Would you like to track the race in detail (" + YES + "/" + NO
						+ ")? ", YES, NO);
		if (choice.equalsIgnoreCase(YES)) {
			trackDetails = true;
		} else {
			trackDetails = false;
		}

		// Run the race.
		race = new BikeRace(new BikeRaceUI(trackDetails));
		race.runRace(cyclists, track);

		// Display exit message.
		System.out.println("Thanks for using the VIRTUAL BIKE RACE - 202nd " +
			"edition!!!");
	}
}
