package de.dhbw.compiler.xparser;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

public class XParser {

    private final TokenBuffer in;
    private long comparecount = 0;

    private final Set<Integer> PlusMinus;
    private final Set<Integer> MultDiv;

    public XParser(TokenBuffer in) {
        this.in = in;
        PlusMinus = new HashSet<>();
        PlusMinus.add(Token.PLUS);
        PlusMinus.add(Token.MINUS);
        MultDiv = new HashSet<>();
        MultDiv.add(Token.MULT);
        MultDiv.add(Token.DIV);
    }

    public long getComparecount() {
        return comparecount;
    }

    private Tree addLeftAssoziativ(Tree left, Tree op, Tree right, Set<Integer> samePrio) {
        if (right.getChildren()
                 .isEmpty() || !samePrio.contains(right.getToken()
                                                       .getType())) {
            op.addFirstChild(left);
            op.addLastChild(right);
            return op;
        } else {
            Tree leftop = right;
            while (samePrio.contains(leftop.getChild(0)
                                           .getToken()
                                           .getType())) {
                leftop = leftop.getChild(0);
            }
            Tree leftright = leftop.getChild(0);
            leftop.removeChild(0);
            op.addLastChild(leftright);
            op.addFirstChild(left);
            leftop.addFirstChild(op);
            return right;
        }
    }

    public Tree parseToken(int type) {
        comparecount++;

	    try {
		    Token token = in.nextToken();

		    if (token.getType() == type) {
			    return new Tree(token);
		    } else {
			    return null;
		    }
	    } catch (IOException e) {
		    e.printStackTrace();
		    return null;
	    }
    }

    /* ---------------------------------------- *
     *
	 * modifier ::= read modifier2 | print | eps.
	 * modifier2 ::= print | eps.
	 */

	/* modifier ::= read modifier2 | print | eps. */
	public Tree parseModifier() {
	    switch (in.lookaheadToken().getType()) {
	        case Token.READ:
	        	Tree read, modifier2;

	        	if ((read = parseToken(Token.READ)) != null
			        && (modifier2 = parseModifier2()) != null) {
	        		Tree tree = new Tree(new Token(Token.MODIFIER));
	        		tree.addLastChild(read);
	        		tree.addLastChild(modifier2);
	        		return tree;
		        } else {
	        		return null;
		        }

	        case Token.PRINT:
	    	    Tree print;

	    	    if ((print = parseToken(Token.PRINT)) != null) {
	    	    	Tree tree = new Tree(new Token(Token.MODIFIER));
	    	    	tree.addLastChild(print);
	    	    	return tree;
		        } else {
	    	    	return null;
		        }

		    default:
		    	return null;
	    }
    }

    /* modifier2 ::= print | eps. */
    public Tree parseModifier2() {
	    if (in.lookaheadToken()
	          .getType() == Token.PRINT) {
		    Tree print;

		    if ((print = parseToken(Token.PRINT)) != null) {
			    return print;
		    }
	    }
	    return null;
    }

    public Tree parseType() {
        Tree type;

        // type ::= int.
        if (((type = parseToken(Token.INT)) != null)) {
            return type;
        }

        // type ::= float.
        if (((type = parseToken(Token.FLOAT)) != null)) {
            return type;
        }

        // type ::= string.
        if (((type = parseToken(Token.STRING)) != null)) {
            return type;
        }

        // fail
        return null;
    }

    public Tree parseDecl() {

        // decl ::= modifier id ":" type ";".
        Tree modifier, id, type;
        if (((modifier = parseModifier()) != null) && ((id = parseToken(Token.ID)) != null) &&
            ((parseToken(Token.COLON)) != null) && ((type = parseType()) != null) && ((parseToken(Token.SEMICOLON)) != null)) {
            Tree tree = new Tree(new Token(Token.DECL));
            // tree.addLastChild(modifier);
            tree.addLastChild(id);
            tree.addLastChild(type);
            for (Tree mod : modifier.getChildren()) {
                tree.addLastChild(mod);
            }
            return tree;
        }

        // fail
        return null;
    }

    public Tree parseExpr3() {

        Tree minus, number, id, expr;
        Token uminus;

        // expr3 ::= "-" int.
        if (((minus = parseToken(Token.MINUS)) != null) && ((number = parseToken(Token.INTCONST)) != null)) {
            uminus = new Token(Token.UMINUS,
                               "-",
                               minus.getToken()
                                    .getLine(),
                               minus.getToken()
                                    .getColumn());
            minus = new Tree(uminus);
            minus.addLastChild(number);
            return minus;
        }

        // expr3 ::= int.
        if (((number = parseToken(Token.INTCONST)) != null)) {
            return number;
        }

        // expr3 ::= "-" float.
        if (((minus = parseToken(Token.MINUS)) != null) && ((number = parseToken(Token.FLOATCONST)) != null)) {
            uminus = new Token(Token.UMINUS,
                               "-",
                               minus.getToken()
                                    .getLine(),
                               minus.getToken()
                                    .getColumn());
            minus = new Tree(uminus);
            minus.addLastChild(number);
            return minus;
        }

        // expr3 ::= float.
        if (((number = parseToken(Token.FLOATCONST)) != null)) {
            return number;
        }

        // expr3 ::= string.
        if (((number = parseToken(Token.STRINGCONST)) != null)) {
            return number;
        }

        // expr3 ::= id.
        if (((id = parseToken(Token.ID)) != null)) {
            return id;
        }

        // expr3 ::= "(" expr ")".
        if (((parseToken(Token.LBR)) != null) && ((expr = parseExpr()) != null) && ((parseToken(Token.RBR)) != null)) {
            return expr;
        }

        // fail
        return null;
    }

    public Tree parseExpr2() {
        Tree exprl, op, exprr;

        // expr2 ::= expr3 "*" expr2.
        if (((exprl = parseExpr3()) != null) && ((op = parseToken(Token.MULT)) != null) && ((exprr = parseExpr2()) != null)) {
            // left-assoziativ!!
            return addLeftAssoziativ(exprl, op, exprr, MultDiv);
        }

        // expr2 ::= expr3 "/" expr2.
        if (((exprl = parseExpr3()) != null) && ((op = parseToken(Token.DIV)) != null) && ((exprr = parseExpr2()) != null)) {
            return addLeftAssoziativ(exprl, op, exprr, MultDiv);
        }

        // expr2 ::= expr3.
        if (((exprl = parseExpr3()) != null)) {
            return exprl;
        }

        // fail
        return null;
    }

    public Tree parseExpr() {

        Tree exprl, op, exprr;

        // expr ::= expr2 "+" expr.
        if (((exprl = parseExpr2()) != null) && ((op = parseToken(Token.PLUS)) != null) && ((exprr = parseExpr()) != null)) {
            return addLeftAssoziativ(exprl, op, exprr, PlusMinus);
        }

        // expr ::= expr2 "-" expr.
        if (((exprl = parseExpr2()) != null) && ((op = parseToken(Token.MINUS)) != null) && ((exprr = parseExpr()) != null)) {
            return addLeftAssoziativ(exprl, op, exprr, PlusMinus);
        }

        // expr ::= expr2.
        if (((exprl = parseExpr2()) != null)) {
            return exprl;
        }

        // fail
        return null;
    }

    public Tree parseAssignStat() {

        Tree id, assign, expr;

        // assignStat ::= id ":=" expr ";".
        if (((id = parseToken(Token.ID)) != null) && ((assign = parseToken(Token.ASSIGN)) != null) &&
            ((expr = parseExpr()) != null)) {
            assign.addLastChild(id);
            assign.addLastChild(expr);
            return assign;
        }

        // fail
        return null;
    }

    public Tree parseForStat() {

        Tree tfor, init, cond, cont, stat;

        // forstat ::= for "(" assignstat cond ";" assignstat ")" stat.
        if (((tfor = parseToken(Token.FOR)) != null) && ((parseToken(Token.LBR)) != null) &&
            ((init = parseAssignStat()) != null) && ((parseToken(Token.SEMICOLON)) != null) && ((cond = parseCond()) != null) &&
            ((parseToken(Token.SEMICOLON)) != null) && ((cont = parseAssignStat()) != null) &&
            ((parseToken(Token.RBR)) != null) && ((stat = parseStat()) != null)) {
            tfor.addLastChild(init);
            tfor.addLastChild(cond);
            tfor.addLastChild(cont);
            tfor.addLastChild(stat);
            return tfor;
        }

        // fail
        return null;
    }

    public Tree parseWhileStat() {

        Tree twhile, cond, stat;

        // whilestat ::= while "(" cond ")" stat.
        if (((twhile = parseToken(Token.WHILE)) != null) && ((parseToken(Token.LBR)) != null) && ((cond = parseCond()) != null) &&
            ((parseToken(Token.RBR)) != null) && ((stat = parseStat()) != null)) {
            twhile.addLastChild(cond);
            twhile.addLastChild(stat);
            return twhile;
        }

        // fail
        return null;
    }

    public Tree parseCondStat() {

        Tree tif, cond, thenstat, elsestat;

        // condstat ::= if cond then stat else stat.
        if (((tif = parseToken(Token.IF)) != null) && ((cond = parseCond()) != null) && ((parseToken(Token.THEN)) != null) &&
            ((thenstat = parseStat()) != null) && ((parseToken(Token.ELSE)) != null) && ((elsestat = parseStat()) != null)) {
            tif.addLastChild(cond);
            tif.addLastChild(thenstat);
            tif.addLastChild(elsestat);
            return tif;
        }

        // condstat ::= if cond then stat.
        if (((tif = parseToken(Token.IF)) != null) && ((cond = parseCond()) != null) && ((parseToken(Token.THEN)) != null) &&
            ((thenstat = parseStat()) != null)) {
            tif.addLastChild(cond);
            tif.addLastChild(thenstat);
            return tif;
        }

        // fail
        return null;
    }

    public Tree parseCond() {

        Tree left, comp, right;

        // cond ::= expr "<" expr.
        if (((left = parseExpr()) != null) && ((comp = parseToken(Token.LESS)) != null) && ((right = parseExpr()) != null)) {
            comp.addLastChild(left);
            comp.addLastChild(right);
            return comp;
        }

        // cond ::= expr ">" expr.
        if (((left = parseExpr()) != null) && ((comp = parseToken(Token.MORE)) != null) && ((right = parseExpr()) != null)) {
            comp.addLastChild(left);
            comp.addLastChild(right);
            return comp;
        }

        // cond ::= expr "=" expr.
        if (((left = parseExpr()) != null) && ((comp = parseToken(Token.EQUALS)) != null) && ((right = parseExpr()) != null)) {
            comp.addLastChild(left);
            comp.addLastChild(right);
            return comp;
        }

        // fail
        return null;
    }

    public Tree parseStat() {

        Tree stat;

        // stat ::= assignstat.
        if (((stat = parseAssignStat()) != null)) {
            return stat;
        }

        // stat ::= condstat.
        if (((stat = parseCondStat()) != null)) {
            return stat;
        }

        // stat ::= whilestat.
        if (((stat = parseWhileStat()) != null)) {
            return stat;
        }

        // stat ::= forstat.
        if (((stat = parseForStat()) != null)) {
            return stat;
        }

        // stat ::= block.
        if (((stat = parseBlock()) != null)) {
            return stat;
        }

        // fail
        return null;
    }

    public Tree parseBlock() {

        Tree statlist;

        // block ::= begin statlist end.
        if (((parseToken(Token.BEGIN)) != null) && ((statlist = parseStatlist()) != null) && ((parseToken(Token.END)) != null)) {
            return statlist;
        }

        // fail

        return null;
    }

    public Tree parseStatlist() {
        Tree stat;

        Tree tree = new Tree(new Token(Token.STATLIST));
        // statlist ::= { statwithsemi }.
        while (((stat = parseStatwithsemi())) != null) {
            tree.addLastChild(stat);
        }
        return tree;
    }

    public Tree parseStatwithsemi() {

        Tree stat;

        // statwithsemi ::= stat ";"
        if ((((stat = parseStat())) != null) && ((parseToken(Token.SEMICOLON)) != null)) {
            return stat;
        }

        // fail
        return null;
    }

    /*
     * decllist ::= decl decllist.
     * decllist ::= eps.
     */
    public Tree parseDecllist() {
        Tree decl;

        Tree tree = new Tree(new Token(Token.DECLLIST));
        // decllist ::= { decl }.
        while (((decl = parseDecl())) != null) {
            tree.addLastChild(decl);
        }
        return tree;
    }

    public Tree parseProgram() {

        Tree program, id, decllist, block;

        // program ::= program id ";" decllist block "." EOF.
        if (((program = parseToken(Token.PROGRAM)) != null) && (((id = parseToken(Token.ID))) != null) &&
            ((parseToken(Token.SEMICOLON)) != null) && ((decllist = parseDecllist()) != null) &&
            ((block = parseBlock()) != null) && ((parseToken(Token.DOT)) != null) && ((parseToken(Token.EOF)) != null)) {
            program.addLastChild(id);
            program.addLastChild(decllist);
            program.addLastChild(block);
            return program;
        }

        // fail
        return null;
    }
}
