/*
 * 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 common.Block;
import common.Code;
import common.DataLayout;
import common.Instruction;
import common.IvannosysTargetArch;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

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

    protected HashMap<String, ExportInterface> exportMethods = new HashMap<>();
    protected Code target;
    protected Code IR;
    protected Instruction prev = null, next = null;
    protected Instruction currentIrInstruction;
    protected Instruction currentTargetInstruction;

    public TargetGen(String id) {
        BaseGenInit(id);
    }

    /**
     * Like Sprintf
     *
     * @param format
     * @param args
     * @return
     */
    protected String F(String format, Object... args) {
        return String.format(format, args);
    }

    /**
     * Retorna o contexto do bloco atual
     *
     * @return
     */
    public DataLayout Context() {
        return Context("");
    }

    /**
     * Retorna o contexto do bloco atual <br>
     * Se var contem "G" retorna o contexto global
     *
     * @param var
     * @return
     */
    public DataLayout Context(String var) {
        if (var.contains("G")) {
            return IR.GlobalContext();
        }
        return IR.Block().Context();
    }

    public common.Offset Offset(String var) throws Exception {
        return Context(var).Offset(var);
    }

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

    public Code getTarget() {
        return target;
    }

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

    public Code getIR() {
        return IR;
    }

    public ExportInterface GetExportMethod(String id) throws Exception {
        id = id.trim();
        if (!exportMethods.containsKey(id)) {
            Set<String> set = exportMethods.keySet();
            String[] valids = new String[set.size()];
            set.toArray(valids);
            throw new Exception(String.format(
                    "O método de exportação '%s' não foi definido no alvo '%s'. Métodos disponiveis: %s",
                    id,
                    target.Name(),
                    Utils.Join(valids, ", ")));
        }
        return exportMethods.get(id);
    }

    public HashMap<String, ExportInterface> GetExportMethods() {
        return exportMethods;
    }

    public IvannosysTargetArch Init(Code tac) throws Exception {
        // Verifica se o codigo gerado vai ser util caso contrario aborta o processo;
        ArrayList<String> methods = BuildParams.Get("export");

        if (methods.isEmpty()) {
            throw new Exception(String.format("Nenhum método de exportação definido para o alvo '%s'", target.Name()));
        }

        IR = tac;
//        target.GlobalContext().copy(tac.GlobalContext());
        target.Parent = IR;
        return this;
    }

    protected Instruction Add(Instruction inst) throws Exception {
        if (currentIrInstruction != null) {
            inst.Set("tac.position", currentIrInstruction.Get("block.position"));
        }
        target.Block().Add(inst);

        currentTargetInstruction = inst;

        return inst;
    }

    public TargetGen AddExportOption(String id, ExportInterface exp) {
        exportMethods.put(id.trim(), exp);
        return this;
    }

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

        } catch (Exception ex) {
            Logger.getLogger(TargetGen.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(TargetGen.class.getName()).log(Level.SEVERE, null, ex);
        }
        return this;
    }

    protected void Translate() throws Exception {
        String targetID = target.Name(), id;
        target.Parent = IR;
        Block block;
        Middleware.Trigger(target, targetID, "before.translate");

        for (Map.Entry<String, Block> b : IR.Blocks().entrySet()) {
            id = b.getKey();
            block = b.getValue();

            if (block.Type() == Block.TYPE_CTRL) {
                continue;
            }

            IR.Use(id);

            target.OpenBlock(id);

            Middleware.Trigger(target, targetID, "before.translate.block");

            Prolog(id);

            TranslateBlock(block);

            Epilog(id);

            target.CloseBlock();

            Middleware.Trigger(target, targetID, "after.translate.block");
        }

        target.UpdatePositions();

        Middleware.Trigger(target, targetID, "after.translate");
    }

    @Override
    public IvannosysTargetArch Export() throws Exception {
        ArrayList<String> methods = BuildParams.Get("export");

        for (String method : methods) {
            GetExportMethod(method).Exec(IR, target);
        }
        return this;
    }

    protected void TranslateBlock(Block b) throws Exception {
//        System.out.println("Translate block:" + b.getName() + "::" + b.Instructions().size());
        LinkedList<Instruction> instructions = b.Instructions();
        Integer index = 0, limit = instructions.size(), nextIndex;
        for (Iterator<Instruction> it = instructions.iterator(); it.hasNext();) {
            currentIrInstruction = it.next();
            nextIndex = index + 1;
            next = (nextIndex < limit) ? instructions.get(nextIndex) : null;
            //            System.out.println("Translate: " + inst.Get("global.position") + "::read["
//                    + inst.Get("reg.dst.load")
//                    + "|"
//                    + inst.Get("reg.p1.load")
//                    + "|"
//                    + inst.Get("reg.p2.load")
//                    + "] >> write["
//                    + inst.Get("reg.dst.store")
//                    + "|"
//                    + inst.Get("reg.p1.store")
//                    + "|"
//                    + inst.Get("reg.p2.store")
//                    + "]"
//                    + inst.Get("type")
//            );
//
            switch (currentIrInstruction.Get("type")) {
                case "alloc":
                    break;
                case "label":
                    if (!currentIrInstruction.Get("label").equals(getTarget().Block().getName())) {
                        TranslateLabel(currentIrInstruction);
                    }
                    break;
                case "assign":
                    TranslateAssign(currentIrInstruction);
                    break;
                case "unary":
                    TranslateUnary(currentIrInstruction);
                    break;
                case "copy":
                    TranslateCopy(currentIrInstruction);
                    break;
                case "jump":
                    TranslateJump(currentIrInstruction);
                    break;
                case "call":
                    TranslateCall(currentIrInstruction);
                    break;
                case "return":
                    TranslateReturn(currentIrInstruction);
                    break;
                case "push_param":
                    TranslatePushParam(currentIrInstruction);
                    break;
                case "push_return":
                    TranslatePushReturn(currentIrInstruction);
                    break;
                case "pop_return":
                    TranslatePopReturn(currentIrInstruction);
                    break;
                case "pop_param":
                    TranslatePopParam(currentIrInstruction);
                    break;
                case "branch":
                    TranslateBranch(currentIrInstruction);
                    break;
                case "indexed_assign":
                    TranslateIndexedAssignment(currentIrInstruction);
                    break;
                case "pointer_assign":
                    TranslatePointerAssignment(currentIrInstruction);
                    break;
                case "store":
                    TranslateStore(currentIrInstruction);
                    break;
                case "load":
                    TranslateLoad(currentIrInstruction);
                    break;
                default:
                    throw new Exception(String.format("TAC instruction type '%s' not defined", currentIrInstruction.Get("type")));
            }
            prev = currentIrInstruction;
            index++;
        }
    }

    protected Instruction Next() {
        return next;
    }

    protected Instruction Prev() {
        return prev;
    }

}
