import java.io.BufferedReader;
import java.io.IOException;

public class Tokenizer {
	private static final int EOF = -1; /* FIXME */
	private BufferedReader reader;
	private TokenData inputData = new TokenData();
	private int rowIndex = 0;
	private int columnIndex = -1;
	private String name;
	
	//private Stack<ParserInputSource> sources = new Stack<ParserInputSource>();
	public void startParsing(BufferedReader reader, String name) {
		//pushState();
		this.reader = reader;		
		this.name = name;
		this.rowIndex = 0;
		this.columnIndex = -1;
		consumeChar();
		consumeQ();
	}
	/*protected void pushState() {
		this.sources.push(new ParserInputSource(reader, name, input, rowIndex, columnIndex));
	}*/
	/*protected void popState() {
		ParserInputSource source = this.sources.pop();
		reader = source.getReader();
		name = source.getName();
		input = source.getInput();
		rowIndex = source.getRowIndex();
		columnIndex = source.getColumnIndex();
	}*/
	/*public Object parse(BufferedReader reader, String name) {
		startParsing(reader, name);
		consume();
		Object result = parseExpression();
		popState();
		return(result);
	}*/
	private int input = 0;
	protected static Symbol S_private = Symbol.intern("private");
	protected static Symbol S_public = Symbol.intern("public");
	protected static Symbol S_protected = Symbol.intern("protected");
	protected static Symbol S_ID = Symbol.intern("ID");
	protected static Symbol S_class = Symbol.intern("class");
	protected static Symbol S_plus = Symbol.intern("+");
	protected static Symbol S_plusplus = Symbol.intern("++");
	protected static Symbol S_dash = Symbol.intern("-");
	protected static Symbol S_dashdash = Symbol.intern("--");
	protected static Symbol S_less = Symbol.intern("<");
	protected static Symbol S_lessless = Symbol.intern("<<");
	protected static Symbol S_greater = Symbol.intern(">");
	protected static Symbol S_greatergreater = Symbol.intern(">>");
	protected static Symbol S_exclamation = Symbol.intern("!");
	protected static Symbol S_tilde = Symbol.intern("~");
	protected static Symbol S_star = Symbol.intern("*");
	protected static Symbol S_ampersandampersand = Symbol.intern("&&");
	protected static Symbol S_ampersand = Symbol.intern("&");
	protected static Symbol S_ampersandequal = Symbol.intern("&=");
	protected static Symbol S_pipe = Symbol.intern("|");
	protected static Symbol S_pipepipe = Symbol.intern("||");
	protected static Symbol S_slashslash = Symbol.intern("//");
	protected static Symbol S_slashstarstarslash = Symbol.intern("/**/");
	protected static Symbol S_slash = Symbol.intern("/");
	protected static Symbol S_import = Symbol.intern("import");
	protected static Symbol S_openparenthesis = Symbol.intern("(");
	protected static Symbol S_closeparenthesis = Symbol.intern(")");
	protected static Symbol S_circumflex = Symbol.intern("^");
	protected static Symbol S_return = Symbol.intern("return");
	//protected static Symbol S_doublequote = Symbol.intern("\"");
	protected static Symbol S_openbracket = Symbol.intern("[");
	protected static Symbol S_closebracket = Symbol.intern("]");
	protected static Symbol S_comma = Symbol.intern(",");
	protected static Symbol S_dot = Symbol.intern(".");
	protected static Symbol S_equal = Symbol.intern("=");
	protected static Symbol S_lesslessequal = Symbol.intern("<<=");
	protected static Symbol S_greatergreaterequal = Symbol.intern(">>=");
	protected static Symbol S_percent = Symbol.intern("%");
	protected static Symbol S_equalequal = Symbol.intern("==");
	protected static Symbol S_exclamationequal = Symbol.intern("!=");
	protected static Symbol S_plusequal = Symbol.intern("+=");
	protected static Symbol S_dashequal = Symbol.intern("-=");
	protected static Symbol S_starequal = Symbol.intern("*=");
	protected static Symbol S_slashequal = Symbol.intern("/=");
	protected static Symbol S_circumflexequal = Symbol.intern("^=");
	protected static Symbol S_const = Symbol.intern("const");
	protected static Symbol S_greaterequal = Symbol.intern(">=");
	protected static Symbol S_lessequal = Symbol.intern("<=");
	protected static Symbol S_percentequal = Symbol.intern("%=");
	protected static Symbol S_pipeequal = Symbol.intern("|=");
	protected static Symbol S_questionmark = Symbol.intern("?");
	protected static Symbol S_colon = Symbol.intern(":");
	protected static Symbol S_semicolon = Symbol.intern(";");
	protected static Symbol S_stringliteral = Symbol.intern("string-literal");
	protected static Symbol S_final = Symbol.intern("final");
	protected static Symbol S_opencurlybrace = Symbol.intern("{");
	protected static Symbol S_closecurlybrace = Symbol.intern("}");
	protected static Symbol S_static = Symbol.intern("static");
	protected static Symbol S_new = Symbol.intern("new");
	protected static Symbol S_at = Symbol.intern("@");
	protected static Symbol S_instanceof = Symbol.intern("instanceof");
	protected static Symbol S_greatergreatergreater = Symbol.intern(">>>");
	protected static Symbol S_greatergreatergreaterequal = Symbol.intern(">>>=");
	protected static Symbol S_package = Symbol.intern("package");
	protected static Symbol S_if = Symbol.intern("if");
	protected static Symbol S_starEOF = Symbol.intern("*EOF");
	//private static Symbol S_void = Symbol.intern("void");
	protected static Symbol precedenceTable[][];  
	static {
		precedenceTable = new Symbol[][] {
				new Symbol[] {S_openbracket, S_dot, S_openparenthesis, S_plusplus /* FIXME post*/, S_dashdash/* FIXME post*/},
				new Symbol[] {S_plusplus, S_dashdash, S_plus/*FIXME unary*/, S_dash/*FIXME unary*/, S_exclamation, S_tilde},
				new Symbol[] {/*FIXME cast*/ S_new },
				new Symbol[] {S_star, S_slash, S_percent },
				new Symbol[] {S_plus, S_dash },
				new Symbol[] {S_lessless, S_greatergreater },
				new Symbol[] {S_less, S_lessequal, S_greater, S_greaterequal, S_instanceof },
				new Symbol[] {S_equalequal, S_exclamationequal },
				new Symbol[] {S_ampersand },
				new Symbol[] {S_circumflex },
				new Symbol[] {S_pipe },
				new Symbol[] {S_ampersandampersand },
				new Symbol[] {S_pipepipe },
				new Symbol[] {S_questionmark },
				new Symbol[] {S_equal, S_plusequal, S_dashequal, S_starequal, S_slashequal, S_percentequal, S_ampersandequal, S_circumflexequal, S_pipeequal, S_lesslessequal, S_greatergreaterequal, S_greatergreatergreaterequal },
		};
		
	};
	
	public char consumeChar() {
		char oldInput = (char) input;
		++columnIndex;
		if(oldInput == '\n') {
			columnIndex = 0;
			++rowIndex;
		}
		
		try {
			input = reader.read();
		} catch (IOException e) {
			input = -1;
		}
		return oldInput;
	}
	public Symbol consumeQ(Symbol expectedToken) {
		if(expectedToken != inputData.getToken())
			return (Symbol) raiseError(expectedToken.toString(), inputData.getToken().toString());
		else
			return(consumeQ());
	}
	public Symbol consumeQ() {
		Symbol oldInput = inputData.getToken();
		inputData.setMatchText("");
		if(isSymbol1Character(input)) {
			Symbol ID = parseSymbol();
			if(ID == S_protected || ID == S_private || ID == S_public || ID == S_class || ID == S_import || ID == S_return || ID == S_const || ID == S_final || ID == S_static || ID == S_new || ID == S_instanceof || ID == S_package || ID == S_if)
				inputData.setToken(ID);
			else
				inputData.setToken(S_ID);
		} else if(input == '+') {
			consumeChar();
			if(input == '+') {
				consumeChar();
				inputData.setToken(S_plusplus);
			} else if(input == '=') {
				consumeChar();
				inputData.setToken(S_plusequal);
			} else
				inputData.setToken(S_plus);
		} else if(input == '-') {
			consumeChar();
			if(input == '-') {
				consumeChar();
				inputData.setToken(S_dashdash);
			} else if(input == '=') {
				consumeChar();
				inputData.setToken(S_dashequal);
			} else
				inputData.setToken(S_dash);
		} else if(input == '*') {
			consumeChar();
			if(input == '=') {
				consumeChar();
				inputData.setToken(S_starequal);
			} else
				inputData.setToken(S_star);
		} else if(input == '<') {
			consumeChar();
			if(input == '<') {
				consumeChar();
				inputData.setToken(S_lessless);
			} else if(input == '=') {
				consumeChar();
				inputData.setToken(S_lessequal);
			} else
				inputData.setToken(S_less);
		} else if(input == '>') {
			consumeChar();
			if(input == '>') {
				consumeChar();
				if(input == '=') {
					consumeChar();
					inputData.setToken(S_greatergreaterequal);
				} else if (input == '>') {
					consumeChar();
					if(input == '=') {
						consumeChar();
						inputData.setToken(S_greatergreatergreaterequal);
					} else
						inputData.setToken(S_greatergreatergreater);
				} else
					inputData.setToken(S_greatergreater);
			} else if(input == '=') {
				consumeChar();
				inputData.setToken(S_greaterequal);
			} else
				inputData.setToken(S_greater);
		} else if(input == '!') {
			consumeChar();
			inputData.setToken(S_exclamation);
		} else if(input == '~') {
			consumeChar();
			inputData.setToken(S_tilde);
		} else if(input == '&') {
			consumeChar();
			if(input == '&') {
				consumeChar();
				inputData.setToken(S_ampersandampersand);
			} else if(input == '=') {
				consumeChar();
				inputData.setToken(S_ampersandequal);
			} else
				inputData.setToken(S_ampersand);
		} else if(input == '|') {
			consumeChar();
			if(input == '|') {
				consumeChar();
				inputData.setToken(S_pipepipe);
			} else if(input == '=') {
				consumeChar();
				inputData.setToken(S_pipeequal);
			} else
				inputData.setToken(S_pipe);
		} else if(input == '/') {
			consumeChar();
			if(input == '/') {
				consumeChar();
				inputData.setToken(S_slashslash);
				inputData.setMatchText(parseLineCommentBody());
			} else if(input == '*') { /* block comment */
				consumeChar();
				inputData.setToken(S_slashstarstarslash);
				inputData.setMatchText(parseBlockCommentBody());
			} else if(input == '=') {
				consumeChar();
				inputData.setToken(S_slashequal);
			} else
				inputData.setToken(S_slash);
		} else if(input == '^') {
			consumeChar();
			if(input == '=') {
				consumeChar();
				inputData.setToken(S_circumflexequal);
			} else
				inputData.setToken(S_circumflex);
		} else if(input == '(') {
			consumeChar();
			inputData.setToken(S_openparenthesis);
		} else if(input == ')') {
			consumeChar();
			inputData.setToken(S_closeparenthesis);
		} else if(input == '[') {
			consumeChar();
			inputData.setToken(S_openbracket);
		} else if(input == ']') {
			consumeChar();
			inputData.setToken(S_closebracket);
		} else if(input == ',') {
			consumeChar();
			inputData.setToken(S_comma);
		} else if(input == '.') {
			consumeChar();
			inputData.setToken(S_dot);
		} else if(input == '=') {
			consumeChar();
			if(input == '=') {
				consumeChar();
				inputData.setToken(S_equalequal);
			} else 
				inputData.setToken(S_equal);
		} else if(input == '<') {
			consumeChar();
			if(input == '<') {
				consumeChar();
				if(input == '=') {
					consumeChar();
					inputData.setToken(S_lesslessequal);
				} else
					inputData.setToken(S_lessless);
			} else
				inputData.setToken(S_less);
		} else if(input == '>') {
			consumeChar();
			if(input == '>') {
				consumeChar();
				if(input == '=') {
					consumeChar();
					inputData.setToken(S_greatergreaterequal);
				} else
					inputData.setToken(S_greatergreater);
			} else
				inputData.setToken(S_greater);
		} else if(input == '%') {
			consumeChar();
			if(input == '=') {
				consumeChar();
				inputData.setToken(S_percentequal);
			} else
				inputData.setToken(S_percent);
		} else if(input == '!') {
			consumeChar();
			if(input == '=') {
				consumeChar();
				inputData.setToken(S_exclamationequal);
			} else
				inputData.setToken(S_exclamation);
		} else if(input == '?') {
			consumeChar();
			inputData.setToken(S_questionmark);
		} else if(input == ':') {
			consumeChar();
			inputData.setToken(S_colon);
		} else if(input == ';') {
			consumeChar();
			inputData.setToken(S_semicolon);
		} else if(input == '"') {
			parseStringLiteral();
		} else if(input == '{') {
			consumeChar();
			inputData.setToken(S_opencurlybrace);
		} else if(input == '}') {
			consumeChar();
			inputData.setToken(S_closecurlybrace);
		} else if(input == '@') {
			consumeChar();
			inputData.setToken(S_at);
			//parseSymbol(); // FIXME store, args
		} else if(input == -1) {
			inputData.setToken(S_starEOF);
		} else {
			raiseError("<symbol>");
		}
		parseOptionalWhitespace();
		//System.out.println(String.format("upcoming: %s (%s)", inputData.getToken().toString(), inputData.getMatchText()));
		return oldInput;
	}
	private void parseStringLiteral() {
		consumeChar();
		inputData.setToken(S_stringliteral);
		StringBuffer buffer = new StringBuffer();
		while(input != EOF && input != '"') { // FIXME escape, quote
			buffer.append((char) consumeChar());
		}
		inputData.setMatchText(buffer.toString());
	}
	private String parseBlockCommentBody() { /* TODO ignore stuff after line comments */
		StringBuffer body = new StringBuffer();
		boolean BMaybeEnd = false;
		boolean BMaybeBeginning = false;
		int nestingLevel = 1;
		body.append('/');
		body.append('*');
		for(; input != EOF; body.append(consumeChar())) {
			if(input == '*') {
				BMaybeEnd = true;
			} else if(BMaybeEnd && input == '/') {
				--nestingLevel;
				if(nestingLevel == 0) {
					body.append(consumeChar());
					break;
				}
			} else if(BMaybeBeginning && input == '*') {
				++nestingLevel;
			} else if(input == '/' && !BMaybeEnd) {
				BMaybeBeginning = true;
			} else {
				BMaybeEnd = false;
				BMaybeBeginning = false;
			}
		}
		return body.toString();
	}
	private String parseLineCommentBody() {
		StringBuffer body = new StringBuffer();
		body.append('/');
		body.append('/');
		while(input != '\n' && input != '\r' && input != EOF)
			body.append(consumeChar());
		return body.toString();
	}
	public void setInputData(TokenData inputData) {
		this.inputData = inputData;
	}
	public TokenData getInputData() {
		inputData.setFileName(name);
		inputData.setRowIndex(rowIndex);
		inputData.setColumnIndex(columnIndex);
		return inputData;
	}
	private boolean isWhitespace(int input) {
		return input == ' ' || input == '\t' || input == '\n' || input == 13 || input == 10;
	}
	protected void parseOptionalWhitespace() {
		while(isWhitespace(input))
			consumeChar();
	}
	protected void parseWhitespace() {
		if(!isWhitespace(input))
			raiseError("<whitespace>");
		parseOptionalWhitespace();
	}
	protected Object raiseError(String expectedString) {
		String x = "";
		x += (char) input;
		return raiseError(expectedString, "<" + inputData.getToken().toString() + ">"); // (input >= 32 && input < 128) ? x : Integer.toString(input));
	}
	protected Object raiseError(String expectedString, String gotString) {
		System.err.println(String.format("\"%s\": expected %s but got %s near line %d, column %d", name, expectedString, gotString, rowIndex + 1, columnIndex + 1));
		System.exit(1);
		return null;
	}
	protected Symbol parseSymbol() {
		StringBuffer buffer = new StringBuffer();
		if(!isSymbol1Character(input))
			return (Symbol) raiseError("<symbol>");
		while(isSymbolCharacter(input))
			buffer.append(consumeChar());
		parseOptionalWhitespace();
		String s = buffer.toString();
		inputData.setMatchText(s);
		return Symbol.intern(s);
	}
	protected static boolean isSymbolCharacter(int input) {
		return isSymbol1Character(input) || (input == '_') || isDecimalDigit(input) || (input == '?') || (input == '!') || (input == '"'/*FIXME extra string parser? */);
	}
	protected static boolean isSymbol1Character(int input) {
		return (input >= 'A' && input <= 'Z') || (input >= 'a' && input <= 'z') || (input == '_');
	}
	protected static boolean isDecimalDigit(int input) {
		return (input >= '0') && (input <= '9');
	}
	public Symbol getToken() {
		return getInputData().getToken();
	}
}
