package Compiler;

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

import Compiler.Expression.*;
import Compiler.Statement.*;


public class Translator{

    List<String> CCode = new ArrayList<>();
    private Environment environment = new Environment();


    public List<String> compileToC(List<Statement> statements, boolean printC){
        CCode.add("#include <stdio.h>");
        CCode.add("#include <string.h>");
        CCode.add("int main(){");
        try{
            for (Statement statement: statements){
                evaluateStatement(statement);
            }
        } catch (Error e){

        }
        CCode.add("}");

        if (printC) {
            for(String t:CCode){
                System.out.println(t);
            }
            System.out.println("");
        }
        return CCode; 
    }

    private void evaluateStatement(Statement statement){
        switch(statement.getStatmentType()){
            case "exprStmt":
                evalExpressionStatement((ExpressionStatement)statement);
                break;
            case "varDec":
                evalVariableDeclaration((VariableDeclaration)statement);
                break;
            case "stringDec":
                evalStringDeclaration((StringDeclaration)statement);
                break;
            case "arrayDec":
                evalArrayDeclaration((ArrayDeclaration)statement);
                break;
            case "block":
                evalBlockStatement((BlockStatement)statement);
                break;
            case "print":
                evalPrintStatement((PrintStatement)statement);
                break;
            case "ifStmt":
                evalIfStatement((IfStatement)statement);
                break;
            case "doStmt":
                evalDoStatement((DoStatement)statement);
                break;
            case "dowhileStmt":
                evalDoWhileStatement((DoWhileStatement)statement);
                break;
        }
    } 
    private void evalExpressionStatement(ExpressionStatement stmt){
        evaluateExpression(stmt.expr);
    }

    private void evalStringDeclaration(StringDeclaration stringdec){
        environment.defineVariable(stringdec.name.text, "string");
        int size = (int)((Expression.Literal)stringdec.length).value.value;
        size++;
        CCode.add("char "+stringdec.name.text+"["+size+"];");
    }

    private void evalVariableDeclaration(VariableDeclaration vardec){
        environment.defineVariable(vardec.name.text, vardec.type);
        if(vardec.type.equals("int")){
            CCode.add("int "+vardec.name.text+";");
        } else if(vardec.type.equals("real")){
            CCode.add("float "+vardec.name.text+";");
        } 
    }

    private void evalArrayDeclaration(ArrayDeclaration arraydec){
        environment.defineVariable(arraydec.name.text, arraydec.type);
        String arrayString = "";
        if(arraydec.type.equals("int")){
            arrayString+="int ";
        } else if(arraydec.type.equals("real")){
            arrayString+="real ";
        } 
        arrayString+=arraydec.name.text;
        for(Expression expr:arraydec.dimensions){
            arrayString+="[";
            arrayString+=evaluateExpression(expr);
            arrayString+="]";
        }
        arrayString+=";";
        CCode.add(arrayString);

        
    }

    private void evalBlockStatement(BlockStatement block){
        for(Statement stmt:block.statements){
            evaluateStatement(stmt);
        }
    }

    private void evalPrintStatement(PrintStatement print){
        String types="";
        String values="";
        boolean first=true;
        for(Expression expr:print.exprList){
            if(!first){
                values+=",";
            }else{
                first=false;
            }
            String exprType="";
            if(expr instanceof Expression.Literal){
                exprType=((Expression.Literal)expr).type;
            }
            else if (expr instanceof Expression.Variable){
                exprType=(String)environment.getVariable((((Expression.Variable)expr).name));
            }
            else if (expr instanceof Expression.ArrayVariable){
                exprType=(String)environment.getVariable((((Expression.ArrayVariable)expr).name));
            }
            if (exprType.equals("int")){
                types+="%d";
            } else if (exprType.equals("double")){
                types+="%f";
            } else if (exprType.equals("string")){
                types+="%s";
            }
            values+=evaluateExpression(expr);
        }
        types+="\\n";
        CCode.add("printf(\""+types+"\","+values+");");  
    }

    private void evalIfStatement(IfStatement ifstatement){
        
        CCode.add("if("+evaluateExpression(ifstatement.condition)+"){");
        evaluateStatement(ifstatement.ifBlock);
        if(!(ifstatement.elseBlock==null)){
            CCode.add("}");
            CCode.add("else {");
            evaluateStatement(ifstatement.elseBlock);
        }
        CCode.add("}");
    }

    private void evalDoStatement(DoStatement dostatement){
        String start = evaluateExpression(dostatement.variable)+"="+evaluateExpression(dostatement.start);
        String stop = evaluateExpression(dostatement.variable)+"<="+evaluateExpression(dostatement.stop);
        String step = evaluateExpression(dostatement.variable)+"++";
        if(!(dostatement.step==null)){
            step = evaluateExpression(dostatement.variable)+"+="+evaluateExpression(dostatement.step);
        }
        CCode.add("for("+start+";"+stop+";"+step+"){");
        evaluateStatement(dostatement.codeBlock);
        CCode.add("}");
    }

    private void evalDoWhileStatement(DoWhileStatement dowhilestatement){
        CCode.add("while("+evaluateExpression(dowhilestatement.condition)+"){");
        evaluateStatement(dowhilestatement.codeBlock);
        CCode.add("}");
    }

    private String evaluateExpression(Expression expression){
        switch(expression.getExpressionType()){
            case "binary":
                return evaluateBinaryExpression((Binary)expression);
            case "singular":
                return evaluateSingularExpression((Singular)expression);
            case "literal":
                return evaluateLiteralExpression((Literal)expression);
            case "bracket":
                return evaluateBracketedExpression((BracketedExpression)expression);
            case "assign":
                evaluateAssignmentExpression((AssignmentExpression)expression);
                return "";
            case "arrayvar":
                return evaluateArrayVariable((ArrayVariable)expression);
            case "var":
                return evaluateVariableExpression((Variable)expression);
            default:
                return null;
        }
    }

    private String evaluateBinaryExpression(Binary expr){
        switch (expr.op.type){
            case PLUS:
                //return "leftEval+rightEval"
                return evaluateExpression(expr.left)+"+"+evaluateExpression(expr.right);
            case STAR:
                return evaluateExpression(expr.left)+"*"+evaluateExpression(expr.right);
            case MINUS:
                return evaluateExpression(expr.left)+"-"+evaluateExpression(expr.right);
            case SLASH:
                return evaluateExpression(expr.left)+"/"+evaluateExpression(expr.right);
            case GREATER:
                return evaluateExpression(expr.left)+">"+evaluateExpression(expr.right);
            case LESS:
                return evaluateExpression(expr.left)+"<"+evaluateExpression(expr.right);
            case GREATER_EQUAL:
                return evaluateExpression(expr.left)+">="+evaluateExpression(expr.right);
            case LESS_EQUAL:
                return evaluateExpression(expr.left)+"<="+evaluateExpression(expr.right);
            case EQUALITY:
                return evaluateExpression(expr.left)+"=="+evaluateExpression(expr.right);
            case AND:
                return evaluateExpression(expr.left)+"&&"+evaluateExpression(expr.right);
            case OR:
                return evaluateExpression(expr.left)+"||"+evaluateExpression(expr.right);
                
            default:
                break;
        }
        return null;
    }

    private String evaluateSingularExpression(Singular expr){
        switch (expr.op.type){
            case NOT:
                return "!"+evaluateExpression(expr.right);
            default:
                break;
        }
        return null;
    }

    private String evaluateLiteralExpression(Literal expr){
        return (expr.value.value).toString();
    }

    private String evaluateBracketedExpression(BracketedExpression expr){
        return "("+evaluateExpression(expr.expr)+")";
    }

    private void evaluateAssignmentExpression(AssignmentExpression expr){
        Token name=null;
        if(expr.variable instanceof Expression.Variable){
            name = ((Expression.Variable)expr.variable).name;
        }
        else if(expr.variable instanceof Expression.ArrayVariable){
            name = ((Expression.ArrayVariable)expr.variable).name;
        }
        if(environment.checkVariable(name)){
            if(expr.value instanceof Expression.Literal){
                if(((Expression.Literal)expr.value).type.equals("string")){
                    CCode.add("strcpy("+evaluateExpression(expr.variable)+","+evaluateExpression(expr.value)+");");
                }else{
                    CCode.add(evaluateExpression(expr.variable)+"="+evaluateExpression(expr.value)+";");
                }
            }
            else{
                CCode.add(evaluateExpression(expr.variable)+"="+evaluateExpression(expr.value)+";");
            }
        }
    }

    private String evaluateArrayVariable(ArrayVariable expr){
        if(environment.checkVariable(expr.name)){
            String arrayString="";
            arrayString+=expr.name.text;
            for(Expression position:expr.positions){
                arrayString+="[";
                arrayString+=evaluateExpression(position);
                arrayString+="]";
            }
            return arrayString;
        }
        return null;
    }

    private String evaluateVariableExpression(Variable expr){
        return expr.name.text;
    }



}