/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package API;

import IntermediaryCode.Block;
import IntermediaryCode.Code;
import IntermediaryCode.CodeProcessing;
import compiler.IvannosysTargetArch;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author EUGENIO CARVALHO
 */
public abstract class Gen implements IvannosysTargetArch {

    protected Code target;
    protected Code tac;
    protected LinkedHashMap<String, CodeProcessing> beforeTranslate = new LinkedHashMap<>();
    protected LinkedHashMap<String, CodeProcessing> beforeTranslateBlock = new LinkedHashMap<>();
    protected LinkedHashMap<String, CodeProcessing> afterTranslate = new LinkedHashMap<>();
    protected LinkedHashMap<String, CodeProcessing> afterTranslateBlock = new LinkedHashMap<>();

    public Gen(String id) {
        target = new Code(id);
    }

    public Code getTarget() {
        return target;
    }

    public void setTarget(Code target) {
        this.target = target;
    }

    public Code getTac() {
        return tac;
    }

//    public void setTac(Code tac) {
//        this.tac = tac;
//    }
    public IvannosysTargetArch SetTAC(Code tac) {
        this.tac = tac;
        target.GData().copy(tac.GData());
        return this;
    }

    protected Instruction Add(Instruction inst) {
        target.Block().Add(inst);
        return inst;
    }

    @Override
    public IvannosysTargetArch Format() {
        try {
            String code = target.getCodeStream();
            System.out.println("Target[" + target.Name() + "]:\n" + code);

        } catch (Exception ex) {
            Logger.getLogger(Gen.class.getName()).log(Level.SEVERE, null, ex);
        }
        return this;
    }

    @Override
    public IvannosysTargetArch Compile() {
        try {
            // Traduz o codigo intermediario para o alvo
            Translate();

        } catch (Exception ex) {
            Logger.getLogger(Gen.class.getName()).log(Level.SEVERE, null, ex);
        }
        return this;
    }

    protected void Translate() throws Exception {
        String id;
        
        BeforeTranslate();
        
        for (Map.Entry<String, Block> b : tac.getBlocks().entrySet()) {
            id = b.getKey();

            tac.Use(id);

            BeforeTranslateBlock();
//            AllocatorRegister.Alloc(tac);
            target.OpenBlock(id);

            Prolog(id);

            TranslateBlock(b.getValue());

            Epilog(id);
            target.CloseBlock();

            target.UpdatePositions();
            AfterTranslateBlock();
        }
        
        AfterTranslate();
    }

    public Gen BeforeTranslate(String id, CodeProcessing processor) {
        beforeTranslate.put(id, processor);
        return this;
    }

    public Gen BeforeTranslateBlock(String id, CodeProcessing processor) {
        beforeTranslateBlock.put(id, processor);
        return this;
    }

    public Gen AfterTranslate(String id, CodeProcessing processor) {
        afterTranslate.put(id, processor);
        return this;
    }

    public Gen AfterTranslateBlock(String id, CodeProcessing processor) {
        afterTranslateBlock.put(id, processor);
        return this;
    }

    protected Gen BeforeTranslate() throws Exception {
        for (Map.Entry<String, CodeProcessing> codeProcessing : beforeTranslate.entrySet()) {
            codeProcessing.getValue().Exec(tac, beforeTranslate);
        }
        return this;
    }

    protected Gen BeforeTranslateBlock() throws Exception {
        for (Map.Entry<String, CodeProcessing> codeProcessing : beforeTranslateBlock.entrySet()) {
            codeProcessing.getValue().Exec(tac, beforeTranslateBlock);
        }
        return this;
    }

    protected Gen AfterTranslate() throws Exception {
//        System.out.println("AfterTranslate:" + afterTranslate.entrySet().size());
        for (Map.Entry<String, CodeProcessing> codeProcessing : afterTranslate.entrySet()) {
            codeProcessing.getValue().Exec(target, afterTranslate);
        }
        return this;
    }

    protected Gen AfterTranslateBlock() throws Exception {
        for (Map.Entry<String, CodeProcessing> codeProcessing : afterTranslateBlock.entrySet()) {
            codeProcessing.getValue().Exec(target, afterTranslateBlock);
        }
        return this;
    }

    protected void TranslateBlock(Block b) throws Exception {
//        System.out.println("Translate block:" + b.getName() + "::" + b.Instructions().size());

        for (Instruction inst : b.Instructions()) {
//            System.out.println("Translate: " + inst.G("global.position") + "::read["
//                    + inst.G("reg.dst.load")
//                    + "|"
//                    + inst.G("reg.p1.load")
//                    + "|"
//                    + inst.G("reg.p2.load")
//                    + "] >> write["
//                    + inst.G("reg.dst.store")
//                    + "|"
//                    + inst.G("reg.p1.store")
//                    + "|"
//                    + inst.G("reg.p2.store")
//                    + "]"
//                    + inst.G("type")
//            );
//            
            switch (inst.G("type")) {
                case "label":
                    if (!inst.G("label").equals(getTarget().Block().getName())) {
                        TranslateLabel(inst);
                    }
                    break;
                case "assign":
                    TranslateAssign(inst);
                    break;
                case "unary":
                    TranslateUnary(inst);
                    break;
                case "copy":
                    TranslateCopy(inst);
                    break;
                case "jump":
                    TranslateJump(inst);
                    break;
                case "call":
                    TranslateCall(inst);
                    break;
                case "return":
                    TranslateReturn(inst);
                    break;
                case "push_param":
                    TranslatePushParam(inst);
                    break;
                case "push_return":
                    TranslatePushReturn(inst);
                    break;
                case "pop_return":
                    TranslatePopReturn(inst);
                    break;
                case "pop_param":
                    TranslatePopParam(inst);
                    break;
                case "branch":
                    TranslateBranch(inst);
                    break;
                case "indexed_assign":
                    TranslateIndexedAssignment(inst);
                    break;
                case "pointer_assignment":
                    TranslatePointerAssignment(inst);
                    break;
                default:
                    throw new Exception(String.format("TAC instruction type '%s' not defined", inst.G("type")));
            }
        }
    }

}
//    protected void Exec() {
//        StringBuilder S = new StringBuilder();
//        for (Map.Entry<String, Block> entry : tac.getBlocks().entrySet()) {
//            TranslateBlock(entry.getValue());
//            
//            S.append("<" + entry.getKey() + ">:\n");
//
//            for (Instruction registro : entry.getValue()) {
//                String line = registro.G("line");
//                spacer = registro.G("sentenca").equals("jump") ? "\t" : "";
//                inst = fm.Format(registro);
//                registro.S("mipsInstruction", inst);
//                S += line + ". " + spacer + "\t" + inst + "\t\t-- ";
//                S += registro.G("inst3end") + "\n";
//            }
//        }
//    }

//        StringBuilder S = new StringBuilder();
//        for (Map.Entry<String, Block> entry : tac.stmts.entrySet()) {
//            S.append("<" + entry.getKey() + ">:\n");
//
//            for (Instruction registro : entry.getValue()) {
//                String line = registro.G("line");
//                spacer = registro.G("sentenca").equals("jump") ? "\t" : "";
//                inst = fm.Format(registro);
//                registro.S("mipsInstruction", inst);
//                S += line + ". " + spacer + "\t" + inst + "\t\t-- ";
//                S += registro.G("inst3end") + "\n";
//            }
//        }
