import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

/**
 * Encapsulates access to the (Hack VM language) input code. Reads a
 * VM language command, parses it, and provides convenient access to the
 * command's components. In addition, removes all white space and comments.
 * 
 * @author David Owen
 */
public class Parser {

	private Scanner input;
	private boolean endOfFile;
	private String currentCommand, nextCommand;
	private CommandType commandType;
	private String arg1;
	private int arg2;

	/**
	 * Create a Parser to run on Hack VM language input.
	 * 
	 * @param filename Hack VM language input file name.
	 * @throws FileNotFoundException If input file missing.
	 */
	public Parser(String filename) throws FileNotFoundException {
		input = new Scanner(new File(filename));
		endOfFile = false;

		// Read first command from input into nextCommand, so that first real
		// call to advance will assign it to currentCommand, where it needs to
		// be when commandType is called.
		advance();
	}

	/**
	 * Are there more commands in the input?
	 * 
	 * @return true if there are; false if we've reached the end of the file.
	 */
	public boolean hasMoreCommands() {
		return !endOfFile;
	}

	/**
	 * Read the next command from the input and make it the current command.
	 */
	public void advance() {
		currentCommand = nextCommand;

		// (currentCommand will be null when advance is called from the
		// constructor.)
		if (currentCommand != null)
			updateFields();

		// Check for end of file. (currentCommand is last line of the file.)
		if (!input.hasNextLine()) {
			endOfFile = true;

		} else {

			// If not at the end of the file, read ahead to prepare for the
			// next call to advance.
			while (input.hasNextLine()) {
				nextCommand = input.nextLine();

				// Remove comments and white space, except single space between
				// parts of a command.
				nextCommand = nextCommand.replaceFirst("//.*", "");
				nextCommand = nextCommand.replaceAll("\\s", " ");
				nextCommand = nextCommand.trim();

				// If nextCommand contains a command, break out of the loop.
				// (Otherwise the line was just white space or a comment.)
				if (!nextCommand.equals(""))
					break;
			}

			// Check for the possibility that we've reached the end of the
			// file without finding another command.
			if (nextCommand.equals("") && !input.hasNextLine())
				endOfFile = true;
		}
	}

	private void updateFields() {

		if (currentCommand.startsWith("p") ||
				currentCommand.startsWith("function") ||
				currentCommand.startsWith("call")) {

			if (currentCommand.startsWith("push"))
				commandType = CommandType.C_PUSH;

			else if (currentCommand.startsWith("pop"))
				commandType = CommandType.C_POP;

			else if (currentCommand.startsWith("call"))
				commandType = CommandType.C_CALL;
			
			else /* if (currentCommand.startsWith("function")) */
				commandType = CommandType.C_FUNCTION;

			int i = currentCommand.indexOf(" ");
			int j = currentCommand.lastIndexOf(" ");
			arg1 = currentCommand.substring(i + 1, j);
			arg2 = Integer.parseInt(currentCommand.substring(j + 1));

		} else if (currentCommand.startsWith("label")) {
			commandType = CommandType.C_LABEL;
			arg1 = currentCommand.substring(currentCommand.indexOf(" ") + 1);

		} else if (currentCommand.startsWith("goto")) {
			commandType = CommandType.C_GOTO;
			arg1 = currentCommand.substring(currentCommand.indexOf(" ") + 1);

		} else if (currentCommand.startsWith("if-goto")) {
			commandType = CommandType.C_IF;
			arg1 = currentCommand.substring(currentCommand.indexOf(" ") + 1);

		} else if (currentCommand.startsWith("return")) {
			commandType = CommandType.C_RETURN;

		} else {
			commandType = CommandType.C_ARITHMETIC;
			arg1 = currentCommand;
		}
	}

	/**
	 * Returns the type of the current command.
	 * 
	 * @return C_ARITHMETIC, C_PUSH, etc.
	 */
	public CommandType commandType() {
		return commandType;
	}

	/**
	 * For arithmetic commands, returns the command as a String; for other
	 * commands, returns the first argument (as a String).
	 * 
	 * @return command (for arithmetic commands) or first argument
	 */
	public String arg1() {
		return arg1;
	}

	/**
	 * Returns second argument.  (This method should only be called if the type
	 * of the current command is C_PUSH or C_POP).
	 * 
	 * @return second argument (index value in a memory access command)
	 */
	public int arg2() {
		return arg2;
	}

	/**
	 * Close the input file.
	 */
	public void close() {
		input.close();
	}
}