/*
 * 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 common;

import API.Middleware;
import ast.AbstractSyntaxTree;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import templates.TemplateDescription;

/**
 * TipoS de SentençaS: – atribuicao : x := y op z ou x := y ou x:= op y – Salto:
 * goto L – deSvio_condicional: if x relop y goto L – chamda_procedimento: param
 * x and call p,n – retorno:	return y – arrayS:	x := y[i] ou x[i]:=y
 *
 * @author Eugenio
 */
public class Code {

    public static int uniq = 0;
    public static HashMap<String, String> reverseName;

    protected Boolean limparCodigo = true;
    protected HashMap<Integer, Integer> leaders = new HashMap<>();
    protected Integer gline;
//    protected Integer PositionIncrement = 1;
    protected LinkedList<String> currentBlock = new LinkedList<>();
    protected RegistroBase ctx = new Instruction();
    protected String name;

    public AbstractSyntaxTree ast;
    public Boolean HasCall = false;
    public Block current;
    public Code Parent;
    public DataLayout Data;
    public HashMap<String, Label> labels = new HashMap<>();
    public HashMap<String, Integer> labelsReferenceCount = new HashMap<>();
    public Integer PosicaoLabel = 0;
    public LinkedHashMap<String, Block> stmts = new LinkedHashMap<>();
    public int AddressIncrement = 1;
    public String[] ControlInstructions = "label".split(",");
    public String DEFAULT_WORD_TYPE = "int32";

//    public HashMap<String, String> vars;
//    protected HashMap<String, String> templates = new HashMap<>();
//    protected HashMap<String, TPLInterface> _templates = new HashMap<>();
//    protected String commentPrefix = "//";
//    protected TPLInterface TPLManager;
//    private ArrayList<CodeProcessing> processing = new ArrayList<>();
    public Code(String id, AbstractSyntaxTree tree) {
        name = id;
        ast = tree;
        Data = new DataLayout(id);
    }

    public Code(String id) {
        name = id;
//        currentBlock = new LinkedList<>();
//        stmts = new HashMap<>();
        Data = new DataLayout(id);
    }

    public int getAddressIncrement() {
        return AddressIncrement;
    }

    public Code setAddressIncrement(int AddressIncrement) {
        this.AddressIncrement = AddressIncrement;
        return this;
    }

    public boolean HasCall() {
        return HasCall;
    }

    public DataLayout GlobalContext() {
        return Data;
    }

    public void GlobalContext(DataLayout data) {
        Data = data;
    }

    public void SetData(DataLayout Data) {
        this.Data = Data;
    }

    public void listBlocks() {
        System.out.println("Blocks:" + stmts.keySet());
    }

    public void setOtimizacao(boolean lc) {
        limparCodigo = lc;
    }

    public boolean OpenBlock(String id) throws Exception {
//        if (!bloco.hasChildrens()) {
//            return false;
//        }
//        System.out.println("RegisterBlock:" + id);
        currentBlock.push(id);
        current = new Block(id, this);
        stmts.put(id, current);
        Middleware.Trigger(this, this.Name(), "open.block");

//        incEndereco = 0;
//        ocorrences = new LinkedHashMap<>();
//        ocorrencesPerBlocks.put(id, ocorrences);
//        dataFrame = new DataLayout(id);
//        dataFrames.put(id, dataFrame);
//        current = new ArrayList<>();
//        stmts.put(id, current);
//        pointerCorrente = new Instruction();
//        pointers.put(id, pointerCorrente);
//        pointerCorrente.Set("initAddress", "" + posicaoLabel);
//        pointerCorrente.Set("bloco", id);
//        registerVar("sp", 1, dataFrame.TYPE_STACK);
//        storeReturnAddress = false;
//        registerVar("ra", 1, dataFrame.TYPE_STACK);
//        AfterOpen();
        return true;
    }

    public void CloseBlock() throws Exception {
//        AfterClose();
        Middleware.Trigger(this, this.Name(), "close.block");
        Block().Close();
        currentBlock.pop();
        Use(currentBlock.peek());
//        if (!storeReturnAddress) {
//            dataFrame.delete("ra");
//        }
//        genRepresentation();
//        pointerCorrente.Set("endAddress", "" + (posicaoLabel - 1));
//        pointerCorrente = null;
//        current = null;
//        ocorrences = null;
//        dataFrame = null;
//        
    }

    public void Use(String id) {
//        System.out.println("Use '" + id + "'");
        if (id == null || id.equals("")) {
            return;
        }
        current = stmts.get(id);
//        dataFrame = dataFrames.Get(id);
//        pointerCorrente = pointers.Get(id);
    }

    public Block Block() {
        return current;
    }

    public String Dump() throws Exception {

        // Parametro para formatacao dos enderecos
        ctx.Set("G.addressLen", (PosicaoLabel + "").length() + "");
        Label L;
        // Definicao dos enderecos dos labels
        for (Map.Entry<String, Label> x : labels.entrySet()) {
            L = x.getValue();
            ctx.Set("G." + x.getKey(), L.GetGlobalPosition());
            ctx.Set("G.label." + x.getKey(), L.GetLabel());
        }
//        System.out.println("CTX:" + ctx);
        StringBuilder s = new StringBuilder();

        for (Map.Entry<String, Block> entry : stmts.entrySet()) {
            s.append(Code.this.Dump(entry.getKey()));
        }
        return s.toString();
    }

    public String Dump(String bloco) throws Exception {
        StringBuilder s = new StringBuilder();
        String f;

        for (Instruction instruction : stmts.get(bloco).Instructions()) {

            f = FormatInstruction(instruction);
            if (!f.equals("")) {
                s.append(f).append("\n");
            }
        }
        return s.toString();
    }

    protected String FormatInstruction(Instruction inst) throws Exception {
        String format = inst.Get("format");
        String result = "";

        if (!format.equals("norender")) {

            result = templates.Template
                    .FormatRender(format)
                    // Copia todos os atributos do ctx para a instrução
                    .Render(inst.copy("", ctx));

        }
        return result;
    }

    public Code Template(TemplateDescription template) throws Exception {
        templates.Template.Register(template);
        return this;
    }

    public void RemoveLabelReference(String label) {
        if (!labelsReferenceCount.containsKey(label)) {
            return;
        }
        int count = labelsReferenceCount.get(label);
        if (count > 0) {
            labelsReferenceCount.put(label, count - 1);
        }
    }

    public void AddLabelReference(String label) {
        int count = 0;
        if (labelsReferenceCount.containsKey(label)) {
            count = labelsReferenceCount.get(label);
        }
        labelsReferenceCount.put(label, count + 1);
    }

    public Instruction Add(Instruction nr) throws Exception {
        boolean add = true;

        // Se for um acesso ao label registra. a informação é utilizada para remover labels não referenciadas;
        switch (nr.Get("type")) {
            case "jump":
            case "branch":
                AddLabelReference(nr.Get("label"));
        }

        Instruction ur = Last();
        //        int posicaoLocal = LocalPosition();
        if (ur != null) {
            String label = ur.Get("label");
            switch (ur.Get("format")) {
                case "label":
                    break;
                case "jump":
                    /**
                     * Salto para a proxima linha quando o label da nova linha é
                     * igual ao label do salto anterior. Remove o anterior e não
                     * adiciona o novo
                     */
                    if (nr.Get("format").equals("label")
                            && label.equals(nr.Get("label"))) {
                        current.Remove(ur);
                        add = false;
                    }
                    break;
            }
        }
        if (add) {
            switch (nr.Get("type")) {
                case "call":
                    current.HasCall = true;
                    break;
                case "label":
                    break;
            }
            current.Add(nr);
        }
        return nr;
    }

    public void Add(int i, Instruction inst) {
        current.Add(i, inst);
    }

    public Instruction Last() {
        if (current == null) {
            return null;
        }
        int size = current.Instructions().size();
        if (size == 0) {
            return null;
        }
        return current.Instructions().get(size - 1);
    }

    public String getCurrentBlockName() {
        return Block().getName();
    }

    public void PrintData() {
        System.out.println("DATA LAYOUT ...................");

        System.out.println(Data);

        for (Map.Entry<String, Block> entry : stmts.entrySet()) {
            System.out.println(entry.getValue().Context());
        }
    }

    public void Print() throws Exception {
        System.out.println("INSTRUNCTIONS FRAME ...................");
        System.out.println(Dump());
    }

    public String Format() throws Exception {
        return null;
    }

    protected int LocalPosition() throws Exception {
        return 0;
//todo verificar metodo todo
        //        return posicaoLabel - pointerCorrente.GetInt("initAddress");
    }

    public String RegisterLabelAddress(String label) {
//        todo verificar metodo todo
//        if (!labels.containsKey(label)) {
//        System.out.println("Registerlabel:" + label.replaceAll("\\+_i\\d+", "+" + Block().CurrentAddress));
//            System.out.println("label:" + label);
        Label p = new Label();
        p.SetGlobalPosition((long) Position())
                .SetLabel(label)
                .SetPosition((long) Block().CurrentAddress);
//        System.out.println("Register label:" + label + "|" + Block().CurrentAddress);
        labels.put(label, p);

        String translated = label.replaceAll("\\+_i\\d+", "+" + Block().CurrentAddress);
        return translated;
    }

    public static String reverseLabel(String var) {
        return reverseName.get(var.replaceAll("(\\..*|\\[.*)", ""));
    }

    public HashMap<String, Block> Blocks() {
        return this.stmts;
    }

    public String Name() {
        return this.name;
    }

    public String genLabel() {
        return getCurrentBlockName() + "+" + Block().CurrentAddress;
    }

    public void UpdatePositions() throws Exception {
        // Reseta a posicao inicial;
        PosicaoLabel = 0;
        for (Map.Entry<String, common.Block> x : stmts.entrySet()) {
            x.getValue().Update();
        }
    }

    public int Position() {
        return PosicaoLabel;
    }

    public int PositionInc() {
        int cur = PosicaoLabel;
        PosicaoLabel += AddressIncrement;
        return cur;
    }

}

//    public HashMap<String, Ocorrence> getVarOcorrences(String block) throws Exception {
////        if (!ocorrencesPerBlocks.containsKey(block)) {
////            throw new Exception("Bloco '" + block + "' não definido!");
////        }
////        ocorrences = ocorrencesPerBlocks.Get(block);
////
////        ArrayList<Ocorrence> vars = new ArrayList<>();
////
////        for (Map.Entry<String, Ocorrence> entry : ocorrences.entrySet()) {
////            vars.Add(entry.getValue());
////        }
////        Collections.sort(vars);
//
//        HashMap<String, Ocorrence> varsOrd = new LinkedHashMap<>();
//
////        for (Ocorrence entry : vars) {
////            varsOrd.put(entry.var, entry);
////        }
//        return varsOrd;
//    }
//
//    public static void addVarOcorrence(String var, int access) throws Exception {
//        Ocorrence o;
//        //todo verificar metodo todo
////        var = Utils.clearName(var);
////
////        if (!ocorrences.containsKey(var)) {
////            o = new Ocorrence(var);
////            ocorrences.put(var, o);
////        } else {
////            o = ocorrences.Get(var);
////        }
//////        } else if (line.equals("+1")) {
////        o.Add(posicaoLabel, access);
//    }
//    public Code CommentPrefix(String cp) {
//        this.commentPrefix = cp;
//        return this;
//    }
//    public void printOcorrences() {
////        System.out.println("VARIABLES OCORRENCES ...................");
////        for (Map.Entry<String, HashMap<String, Ocorrence>> entry : ocorrenciasGerais.entrySet()) {
//        //todo
////        for (Map.Entry<String, HashMap<String, Ocorrence>> o : ocorrencesPerBlocks.entrySet()) {
////            System.out.println("Block: " + o.getKey());
////            for (Map.Entry<String, Ocorrence> x : o.getValue().entrySet()) {
////                System.out.println(x);
////            }
////        }
////        }
//    }
//    boolean has(String R_RA) {
//        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
//    }
