package Compiler;

import java.util.ArrayList;
import java.util.List;

public class Parser {
    private final List<Token> tokens;
    private int currentToken = 0;

    Parser(List<Token> tokens){
        this.tokens=tokens;
    }

    List<Statement> parse(){
        List<Statement> statements =  new ArrayList<>();
        try{
            while (!checkEOF()){
                statements.add(declaration());
            }
        }catch (Error e){
            return null;
        }
        return statements;
        
    }


    //Clean up and reduce code mess
    private Statement declaration(){
        if (matchAndAdvance(TokenType.INT)){
            if (matchOrError(TokenType.DEFINE, ":: Required for variable definition")){
                if (matchOrError(TokenType.IDENTIFIER,"Expected variable name.")){
                    Token varName = getPreviousToken();
                    return new Statement.VariableDeclaration(varName,"int");
                } 
            }
        } else if (matchAndAdvance(TokenType.REAL)){
            if (matchOrError(TokenType.DEFINE, ":: Required for variable definition")){
                if (matchOrError(TokenType.IDENTIFIER,"Expected variable name.")){
                    Token varName = getPreviousToken();
                    return new Statement.VariableDeclaration(varName,"real");
                } 
            }
        }
        
        return statement();
    }

    private Statement statement(){
        if (matchAndAdvance(TokenType.PRINT)){
            Expression expression = expression();
            return new Statement.PrintStatement(expression);
        }else if (matchAndAdvance(TokenType.IF)){
            Statement statement = ifStatement();
            return statement;
        }
        return expressionStatement();
    }

    //Could be cleaned up to handle else statements better
    private Statement ifStatement(){
        Expression condition = expression();
        if(matchOrError(TokenType.THEN, "then expected after if statement")){
            List<Statement> statements = new ArrayList<>();
            List<Statement> elseStatements = new ArrayList<>();
            while(!matchAndAdvance(TokenType.ENDIF)&&!checkToken(TokenType.ELSE)){
                if(checkEOF()){
                    throw error("endif missing");
                }
                statements.add(declaration());
            }
            if(matchAndAdvance(TokenType.ELSE)){
                while(!matchAndAdvance(TokenType.ENDIF)){
                    if(checkEOF()){
                        throw error("endif missing");
                    }
                    elseStatements.add(declaration());
                }
            }
            Statement ifstatement = new Statement.IfStatement(condition, statements,elseStatements);
            return ifstatement;
        }
        return null;

    }

    private Statement expressionStatement(){
        Expression expression = assignment();
        return new Statement.ExpressionStatement(expression);
    }

    private Expression assignment(){
        Expression variable = expression();
        if (matchAndAdvance(TokenType.EQUALS)){
            Expression assignedvalue = expression();

            if (variable instanceof Expression.Variable){
                return new Expression.AssignmentExpression(((Expression.Variable)variable).name,assignedvalue);
            }
            throw error("Incorrect assignment operation");
        }
        return variable;
    }

    private Expression expression(){
        Expression createdExpression = equality();
        return createdExpression;
    }

    private Expression equality(){
        Expression createdExpression = comparison();
        while (matchAndAdvance(TokenType.EQUALITY)){
            Token op = getPreviousToken();
            Expression right = comparison();
            createdExpression = new Expression.Binary(createdExpression, op, right);
        }
        return createdExpression;
    }

    private Expression comparison(){
        Expression createdExpression = term();
        while (matchAndAdvance(TokenType.GREATER)||matchAndAdvance(TokenType.LESS)){
            Token op = getPreviousToken();
            Expression right = term();
            createdExpression = new Expression.Binary(createdExpression, op, right);
        }
        return createdExpression;
    }

    private Expression term(){
        Expression createdExpression = factor();
        while (matchAndAdvance(TokenType.PLUS)||matchAndAdvance(TokenType.MINUS)){
            Token op = getPreviousToken();
            Expression right = factor();
            createdExpression = new Expression.Binary(createdExpression, op, right);
        }
        return createdExpression;
    }

    private Expression factor(){
        Expression createdExpression = primary();
        while (matchAndAdvance(TokenType.STAR)||matchAndAdvance(TokenType.SLASH)){
            Token op = getPreviousToken();
            Expression right = primary();
            createdExpression = new Expression.Binary(createdExpression, op, right);
        }
        return createdExpression;
    }

    private Expression primary(){
        if (matchAndAdvance(TokenType.NUMBER)){
            if(getPreviousToken().value instanceof Integer){
                return new Expression.Literal(getPreviousToken(),"int");
            } else if(getPreviousToken().value instanceof Double){
                return new Expression.Literal(getPreviousToken(),"double");
            }
        }

        if (matchAndAdvance(TokenType.IDENTIFIER)) {

            return new Expression.Variable(getPreviousToken());
          }

        if (matchAndAdvance(TokenType.LEFT_PAREN)){
            Expression expr = expression();
            if (matchAndAdvance(TokenType.RIGHT_PAREN)){
                return new Expression.BracketedExpression(expr);
            }
            else{
                throw error("Expected ')");
            }
        }
        throw error("Expected Expression");
    }

    private void advanceToken(){
        if (!checkEOF()) {
            currentToken++;
        };
    }

    private boolean matchAndAdvance(TokenType type){
        if (checkToken(type)) {
            advanceToken();
            return true;
        }
        return false;
    }

    private boolean matchOrError(TokenType type,String errorMessage){
        if (matchAndAdvance(type)){
            return true;
        }
        throw error(errorMessage);
    }

    private boolean checkToken(TokenType type){
        if (checkEOF()) return false;
        return getCurrentToken().type == type; 
    }

    private boolean checkEOF(){
        return tokens.get(currentToken).type==TokenType.EOF;
    }

    private Token getCurrentToken(){
        return tokens.get(currentToken);
    }

    private Token getPreviousToken(){
        return tokens.get(currentToken - 1);
    }

    private Error error(String message){
        Language.displayError(message);
        return new Error();
    }


}