/*
 * Copyright 2013 Yuichiro Moriguchi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.morilib.c.pre.parser;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import net.morilib.c.pre.CpreSyntaxException;
import net.morilib.c.pre.expr.CpreAdder;
import net.morilib.c.pre.expr.CpreBitAndOp;
import net.morilib.c.pre.expr.CpreBitNotOp;
import net.morilib.c.pre.expr.CpreBitOrOp;
import net.morilib.c.pre.expr.CpreBitXorOp;
import net.morilib.c.pre.expr.CpreCommaOp;
import net.morilib.c.pre.expr.CpreConditionOp;
import net.morilib.c.pre.expr.CpreDefinedOp;
import net.morilib.c.pre.expr.CpreDivider;
import net.morilib.c.pre.expr.CpreLeftShifter;
import net.morilib.c.pre.expr.CpreLiteralFloat;
import net.morilib.c.pre.expr.CpreLiteralInteger;
import net.morilib.c.pre.expr.CpreExpression;
import net.morilib.c.pre.expr.CpreLogicalAndOp;
import net.morilib.c.pre.expr.CpreLogicalNotOp;
import net.morilib.c.pre.expr.CpreLogicalOrOp;
import net.morilib.c.pre.expr.CpreMultiplier;
import net.morilib.c.pre.expr.CpreNegater;
import net.morilib.c.pre.expr.CpreRelationOp;
import net.morilib.c.pre.expr.CpreRemainderOp;
import net.morilib.c.pre.expr.CpreRightShifter;
import net.morilib.c.pre.expr.CpreSubtracter;

public final class CpreParser {

	private CpreParser() {}

	static CpreExpression identifier(CpreLexer rd) throws IOException {
		CpreExpression e;
		CpreToken t = rd.getToken();

		if(t instanceof CpreIntegerToken) {
			rd.nextToken();
			return new CpreLiteralInteger(t.getInteger());
		} else if(t instanceof CpreFloatToken) {
			rd.nextToken();
			return new CpreLiteralFloat(t.getFloat());
		} else if(t.equals(CpreOperator.DEFINED)) {
			rd.nextToken();
			rd.eatToken(CpreOperator.LPAREN);
			if(!((t = rd.getToken()) instanceof CpreSymbol)) {
				throw new CpreSyntaxException(
						rd.getLineNumber(), "symbol", t);
			}
			rd.nextToken();
			rd.eatToken(CpreOperator.RPAREN);
			return new CpreDefinedOp(t.getString());
		} else if(t instanceof CpreSymbol) {
			return new CpreLiteralInteger(0);
		} else if(t.equals(CpreOperator.LPAREN)) {
			rd.nextToken();
			e = parseExpression(rd);
			rd.eatToken(CpreOperator.RPAREN);
			return e;
		} else {
			throw new CpreSyntaxException(
					rd.getLineNumber(), "identifier", t);
		}
	}

	static CpreExpression unaries(CpreLexer rd) throws IOException {
		CpreToken t = rd.getToken();

		if(t.equals(CpreOperator.ADD)) {
			t = rd.nextToken();
			return identifier(rd);
		} else if(t.equals(CpreOperator.SUB)) {
			t = rd.nextToken();
			return new CpreNegater(identifier(rd));
		} else if(t.equals(CpreOperator.L_NOT)) {
			t = rd.nextToken();
			return new CpreLogicalNotOp(identifier(rd));
		} else if(t.equals(CpreOperator.B_NOT)) {
			t = rd.nextToken();
			return new CpreBitNotOp(identifier(rd));
		} else {
			return identifier(rd);
		}
	}

	static CpreExpression factor(CpreLexer rd) throws IOException {
		List<CpreExpression> l = new ArrayList<CpreExpression>();
		List<CpreToken> m = new ArrayList<CpreToken>();
		CpreExpression e;
		CpreToken t;

		l.add(unaries(rd));
		m.add(null);
		while((t = rd.getToken()).equals(CpreOperator.MUL) ||
				t.equals(CpreOperator.DIV) ||
				t.equals(CpreOperator.MOD)) {
			m.add(t);  rd.nextToken();
			l.add(unaries(rd));
		}
		e = l.get(0);
		for(int i = 1; i < l.size(); i++) {
			if(m.get(i).equals(CpreOperator.MUL)) {
				e = new CpreMultiplier(e, l.get(i));
			} else if(m.get(i).equals(CpreOperator.DIV)) {
				e = new CpreDivider(e, l.get(i));
			} else if(m.get(i).equals(CpreOperator.MOD)) {
				e = new CpreRemainderOp(e, l.get(i));
			}
		}
		return e;
	}

	static CpreExpression term(CpreLexer rd) throws IOException {
		List<CpreExpression> l = new ArrayList<CpreExpression>();
		List<CpreToken> m = new ArrayList<CpreToken>();
		CpreExpression e;
		CpreToken t;

		l.add(factor(rd));
		m.add(null);
		while((t = rd.getToken()).equals(CpreOperator.ADD) ||
				t.equals(CpreOperator.SUB)) {
			m.add(t);  rd.nextToken();
			l.add(factor(rd));
		}
		e = l.get(0);
		for(int i = 1; i < l.size(); i++) {
			if(m.get(i).equals(CpreOperator.ADD)) {
				e = new CpreAdder(e, l.get(i));
			} else if(m.get(i).equals(CpreOperator.SUB)) {
				e = new CpreSubtracter(e, l.get(i));
			}
		}
		return e;
	}

	static CpreExpression sft(CpreLexer rd) throws IOException {
		List<CpreExpression> l = new ArrayList<CpreExpression>();
		List<CpreToken> m = new ArrayList<CpreToken>();
		CpreExpression e;
		CpreToken t;

		l.add(term(rd));
		m.add(null);
		while((t = rd.getToken()).equals(CpreOperator.SHIFTL) ||
				t.equals(CpreOperator.SHIFTR)) {
			m.add(t);  rd.nextToken();
			l.add(term(rd));
		}
		e = l.get(0);
		for(int i = 1; i < l.size(); i++) {
			if(m.get(i).equals(CpreOperator.SHIFTL)) {
				e = new CpreLeftShifter(e, l.get(i));
			} else if(m.get(i).equals(CpreOperator.SHIFTR)) {
				e = new CpreRightShifter(e, l.get(i));
			}
		}
		return e;
	}

	static CpreExpression relop1(CpreLexer rd) throws IOException {
		List<CpreExpression> l = new ArrayList<CpreExpression>();
		List<CpreOperatorType> m = new ArrayList<CpreOperatorType>();
		CpreExpression e;
		CpreToken t;

		l.add(sft(rd));
		m.add(null);
		while((t = rd.getToken()) instanceof CpreRelop1) {
			m.add(t.getRelation());  rd.nextToken();
			l.add(sft(rd));
		}
		e = l.get(0);
		for(int i = 1; i < l.size(); i++) {
			e = new CpreRelationOp(e, l.get(i), m.get(i));
		}
		return e;
	}

	static CpreExpression relop2(CpreLexer rd) throws IOException {
		List<CpreExpression> l = new ArrayList<CpreExpression>();
		List<CpreOperatorType> m = new ArrayList<CpreOperatorType>();
		CpreExpression e;
		CpreToken t;

		l.add(relop1(rd));
		m.add(null);
		while((t = rd.getToken()) instanceof CpreRelop2) {
			m.add(t.getRelation());  rd.nextToken();
			l.add(relop1(rd));
		}
		e = l.get(0);
		for(int i = 1; i < l.size(); i++) {
			e = new CpreRelationOp(e, l.get(i), m.get(i));
		}
		return e;
	}

	static CpreExpression band(CpreLexer rd) throws IOException {
		List<CpreExpression> l = new ArrayList<CpreExpression>();
		CpreExpression e;

		l.add(relop2(rd));
		while(rd.getToken().equals(CpreOperator.B_AND)) {
			rd.nextToken();  l.add(relop2(rd));
		}
		e = l.get(0);
		for(int i = 1; i < l.size(); i++) {
			e = new CpreBitAndOp(e, l.get(i));
		}
		return e;
	}

	static CpreExpression bxor(CpreLexer rd) throws IOException {
		List<CpreExpression> l = new ArrayList<CpreExpression>();
		CpreExpression e;

		l.add(band(rd));
		while(rd.getToken().equals(CpreOperator.B_XOR)) {
			rd.nextToken();  l.add(band(rd));
		}
		e = l.get(0);
		for(int i = 1; i < l.size(); i++) {
			e = new CpreBitXorOp(e, l.get(i));
		}
		return e;
	}

	static CpreExpression bor(CpreLexer rd) throws IOException {
		List<CpreExpression> l = new ArrayList<CpreExpression>();
		CpreExpression e;

		l.add(bxor(rd));
		while(rd.getToken().equals(CpreOperator.B_OR)) {
			rd.nextToken();  l.add(bxor(rd));
		}
		e = l.get(0);
		for(int i = 1; i < l.size(); i++) {
			e = new CpreBitOrOp(e, l.get(i));
		}
		return e;
	}

	static CpreExpression logand(CpreLexer rd) throws IOException {
		List<CpreExpression> l = new ArrayList<CpreExpression>();
		CpreExpression e;

		l.add(bor(rd));
		while(rd.getToken().equals(CpreOperator.L_AND)) {
			rd.nextToken();  l.add(bor(rd));
		}
		e = l.get(0);
		for(int i = 1; i < l.size(); i++) {
			e = new CpreLogicalAndOp(e, l.get(i));
		}
		return e;
	}

	static CpreExpression logor(CpreLexer rd) throws IOException {
		List<CpreExpression> l = new ArrayList<CpreExpression>();
		CpreExpression e;

		l.add(logand(rd));
		while(rd.getToken().equals(CpreOperator.L_OR)) {
			rd.nextToken();  l.add(logand(rd));
		}
		e = l.get(0);
		for(int i = 1; i < l.size(); i++) {
			e = new CpreLogicalOrOp(e, l.get(i));
		}
		return e;
	}

	static CpreExpression cond(CpreLexer rd) throws IOException {
		CpreExpression e, f, g;

		e = logor(rd);
		if(rd.getToken().equals(CpreOperator.TRI1)) {
			rd.nextToken();
			f = parseExpression(rd);
			rd.eatToken(CpreOperator.TRI2);
			g = parseExpression(rd);
			return new CpreConditionOp(e, f, g);
		} else {
			return e;
		}
	}

	static CpreExpression commaop(CpreLexer rd) throws IOException {
		CpreExpression e;

		e = cond(rd);
		if(rd.getToken().equals(CpreOperator.COMMA)) {
			rd.nextToken();
			return new CpreCommaOp(e, commaop(rd));
		} else {
			return e;
		}
	}

	/**
	 * 
	 * @param rd
	 * @return
	 * @throws IOException
	 */
	public static CpreExpression parseExpression(
			CpreLexer rd) throws IOException {
		return commaop(rd);
	}

}
