package TP2.llvm;

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

import TP2.llvm.Interface.*;
import TP2.llvm.ProgramLLVM.*;

public class PrettyprinterLLVM_Visitor implements ProgramLLVMVisitor<String,String>,
DefineLLVMVisitor<String,String>,
InstructionLLVMVisitor<String,String>,
ExpressionLLVMVisitor<String,String>,
TypeLLVMVisitor<String,String>
{
    static String INDENT = "   ";

    @Override
    public String visitProgramLLVM(ProgramLLVMImp prog, String indent) {
        StringBuilder str = new StringBuilder();
            str.append("; Target\n");
            str.append("target triple = \"x86_64-pc-linux-gnu\"\n");
            str.append("; External declaration of the printf function\n");
            str.append("declare i32 @printf (i8 * noalias nocapture, ...)\n");
            str.append("declare i32 @scanf (i8 * noalias nocapture, ...)\n");
            str.append("\n");
            str.append("; Actual code begins\n");

            for(int i = 0; i<prog.declarGlobal().size(); i++){
                str.append(prog.declarGlobal().get(i).accept(this, indent));
            }
            str.append("\n");


            //PROTO et FUNC

            for(DefineLLVM fonction : prog.fonctions()){
                str.append(fonction.accept(this,indent));
                str.append("\n");
            }
            return str.toString();
    }

    @Override
    public String visitDefineLLVM(DefineLLVMImp define, String indent) {
        StringBuilder str = new StringBuilder("define ");
            str.append(define.type().accept(this,indent)).append(" @").append(define.name()).append("(");

            List<VarLLVMImp> params = define.params();
            for (int i = 0; i < params.size(); i++) {
                VarLLVMImp param = params.get(i);
                str.append(param.getType().accept(this, indent)).append(" ").append(param.accept(this, indent));
                if (i < params.size() - 1) str.append(", ");
            }

            str.append("){\n");
            for(InstructionLLVM instr : define.instrs()){
                str.append(instr.accept(this,indent)).append("\n");
            }
            str.append("}");
            return str.toString();
    }

    @Override
    public String visitReturnLLVM(ReturnLLVMImp instr, String h) {
        if (instr.e() == null) {
            return INDENT + "ret " + instr.type().accept(this, h);
        } else {
            return INDENT + "ret " + instr.type().accept(this, h) + " " + instr.e().accept(this, h);
        }
    }

    @Override
    public String visitAssignLLVM(AssignLLVMImp instr, String h) {
        return INDENT+instr.var().accept(this, h) + " = " + instr.e().accept(this, h);
    }

    @Override
    public String visitCallVoidLLVM(CallVoidLLVMImp instr, String h) {
        String str = INDENT+ "call " + instr.f().type().accept(this, h) + " " + instr.str() + " @"+instr.f().name() + "(";
        for(int i = 0; i<instr.params().size(); i++){
            str += instr.params().get(i).getType().accept(this, h) + " " + instr.params().get(i).accept(this,h);
            if(i<instr.params().size()-1) str += ", ";
        }
        return str + ")";
    }

    @Override
    public String visitCallLLVM(CallLLVMImp instr, String h) {
        String str = "call " + instr.f().type().accept(this, h) + " " + instr.str() + " @" + instr.f().name() + "(";

        for (int i = 0; i < instr.params().size(); i++) {
            str += instr.params().get(i).getType().accept(this, h) + " " + instr.params().get(i).accept(this, h);
            if (i < instr.params().size() - 1) str += ", ";
        }
        return str + ")";
    }

    @Override
    public String visitBinOpLLVM(BinOpLLVMImp e, String h) {
        String str = "";
            switch(e.op()){
                case PLUS:
                    str += "add ";
                    break;
                case MINUS:
                    str += "sub ";
                    break;
                case TIMES:
                    str += "mul ";
                    break;
                case DIV:
                    str += "sdiv "; //division signé
                    break;
                case MOD:
                    str += "srem "; //modulo signé
                    break;
                default:
                    throw new AssertionError("Binop : opération inconne");
            }
            return str + e.type().accept(this,h) + " " + e.val1().accept(this,h) + ", " + e.val2().accept(this,h);
    }

    @Override
    public String visitGetElementPtrLLVM(GetElementPtr exp, String h) {
        PointerLLVMImp type = (PointerLLVMImp)exp.getType();
        //getelementptr <type de sortie>, <type d'entrée> <Var d'entrée>, <type de l'indice> <indice>
        return "getelementptr " + type.type().accept(this, h)+ ", " + exp.ptrVar().type().accept(this, h) + " " + exp.ptrVar().accept(this, h) + ", " + exp.i().getType().accept(this, h) + exp.i().accept(this, h);
    }

    @Override
    public String visitIcmpLLVM(IcmpLLVMImp e, String h) {
        return "icmp ne " + e.val1().getType().accept(this, h) +" "+ e.val1().accept(this, h) + ", " + e.val2().accept(this, h);
    }

    @Override
    public String visitAllocaLLVM(allocaLLVMImp e, String h) {
        return "alloca " + e.type().accept(this, h);
    }

    @Override
    public String visitStoreLLVM(StoreLLVMImp instr, String h) {
        return INDENT+"store " + instr.type().accept(this, "") + " " + instr.e().accept(this, "") + ", " + new PointerLLVMImp(instr.type()).accept(this, "") + " " + instr.var().accept(this,"");
    }


    @Override
    public String visitLoadLLVM(LoadLLVMImp e, String h) {
        return "load" + " " + e.getType().accept(this, h) + ", "+ new PointerLLVMImp(e.getType()).accept(this, h) + " " + e.val().accept(this, h);
    }


    @Override
    public String visitPrintLLVM(PrintLLVMImp instr, String h) { //TODO
        DefineLLVMImp printLLVM = new DefineLLVMImp("printf", new IntLLVMImp(), new ArrayList<>(), new ArrayList<>());
        CallVoidLLVMImp callPrint = new CallVoidLLVMImp(printLLVM, instr.l(),"(i8*, ...)");
        return callPrint.accept(this, h);
    }

    @Override
    public String visitScanLLVM(ScanLLVMImp instr, String h) { //TODO
        DefineLLVMImp readLLVM = new DefineLLVMImp("scanf", new IntLLVMImp(), new ArrayList<>(), new ArrayList<>());
        CallVoidLLVMImp callRead = new CallVoidLLVMImp(readLLVM, instr.l(),"(i8*, ...)");
        return callRead.accept(this, h);
    }

    @Override
    public String visitDeclarGlobalLLVM(DeclarGlobalLLVMImp instr, String h) {
        String str = "@"+instr.var().nom() + " = global";
        str += " [" + instr.size() + " x "+ instr.type().accept(this, h) + "] ";
        str+= "c\""+ instr.str()+"\"\n";

        return str;
    }

        //label
    @Override
    public String visitLabelLLVM(LabelLLVMImp instr, String h) {
        return instr.name()+":";
    }

    @Override
    public String visitBrLLVM(BrLLVMImp instr, String h) {
        return INDENT+"br label %" + instr.label(); 
    }

    @Override
    public String visitBrCondLLVM(BrCondLLVMImp instr, String h) {
        return INDENT+"br " + instr.var().type().accept(this, h) +" "+ instr.var().accept(this, h) + ", label %" + instr.label() + ", label %" + instr.labelElse() ; 
    }



    @Override
    public String visitValLLVM(ValLLVMImp e, String h) {
        return e.val() + "";
    }

    @Override
    public String visitVarLLVM(VarLLVMImp e, String h) {
        String prefix = "%";
        if(e.isGlobal()) prefix = "@";
        return prefix+e.nom();
    }

    @Override
    public String visitIntLLVM(IntLLVMImp e, String h) {
        return "i32";
    }

    @Override
    public String visitVoidLLVM(VoidLLVMImp e, String h) {
        return "void";
    }

    @Override
    public String visitBooleanLLVM(BooleanLLVMImp e, String h) {
        return "i1";
    }

    @Override
    public String visitCharLLVM(CharLLVMImp e, String h) {
        return "i8";
    }

    @Override
    public String visitPointerLLVM(PointerLLVMImp e, String h) {
        return e.type().accept(this, h) + "*";
    }

    @Override 
    public String visitArrayLLVM(ArrayLLVMImp e, String h){
        return "";
    }
}
