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){
        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("}");

        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 "print":
                evalPrintStatement((PrintStatement)statement);
                break;
            case "ifStmt":
                evalIfStatement((IfStatement)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 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).text);
            }
            if (exprType.equals("int")){
                types+="%d";
            } else if (exprType.equals("double")){
                types+="%f";
            } else if (exprType.equals("string")){
                types+="%s";
            }
            values+=evaluateExpression(expr);
        }
        CCode.add("printf(\""+types+"\","+values+");");  
    }

    private void evalIfStatement(IfStatement ifstatement){
        
        CCode.add("if("+evaluateExpression(ifstatement.condition)+"){");
        for(Statement s:ifstatement.statements) {
            evaluateStatement(s);
        }
        if(ifstatement.elseStatements.size()>0){
            CCode.add("}");
            CCode.add("else {");
            for(Statement s:ifstatement.elseStatements) {
                evaluateStatement(s);
            }
        }
        CCode.add("}");
    }

    private String evaluateExpression(Expression expression){
        switch(expression.getExpressionType()){
            case "binary":
                return evaluateBinaryExpression((Binary)expression);
            case "literal":
                return evaluateLiteralExpression((Literal)expression);
            case "bracket":
                return evaluateBracketedExpression((BracketedExpression)expression);
            case "assign":
                evaluateAssignmentExpression((AssignmentExpression)expression);
                return "";
            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 EQUALITY:
                return evaluateExpression(expr.left)+"=="+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){
        if(expr.value instanceof Expression.Literal){
            if(((Expression.Literal)expr.value).type.equals("string")){
                CCode.add("strcpy("+expr.name.text+","+evaluateExpression(expr.value)+");");
            }
        }
        else{
            CCode.add(expr.name.text+"="+evaluateExpression(expr.value)+";");
        }
    }

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



}