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

import API.Instruction;
import ast.AbstractSyntaxTree;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import template.TPLInterface;
import template.TemplateCtx;

/**
 * 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 HashMap<String, String> vars;
    public LinkedHashMap<String, Block> stmts = new LinkedHashMap<>();
    public Block current;
    protected LinkedList<String> currentBlock = new LinkedList<>();
    public AbstractSyntaxTree ast;
    public DataFrame Data;
    protected boolean limparCodigo = true;
    protected TemplateCtx GCtx = new Instruction();

    public static HashMap<String, String> reverseName;
    public int PosicaoLabel = 0;
    public HashMap<String, ArrayList<String>> labels = new HashMap<>();

    protected Integer gline;
    protected HashMap<String, String> templates = new HashMap<>();
    protected HashMap<String, TPLInterface> _templates = new HashMap<>();
//    protected String commentPrefix = "//";
    protected String name;
    public boolean HasCall = false;
    protected TPLInterface TPLManager;
    protected int PositionIncrement = 1;
//    private ArrayList<CodeProcessing> processing = new ArrayList<>();
    protected LinkedHashMap<String, CodeProcessing> afterClose = new LinkedHashMap<>();
    protected LinkedHashMap<String, CodeProcessing> afterOpen = new LinkedHashMap<>();
//    protected DataFrame gdata;

    public Code(String id, AbstractSyntaxTree arvore) {
        name = id;
        ast = arvore;
//        currentBlock = new LinkedList<>();
//        stmts = new HashMap<>();
        Data = new DataFrame(id, null);
    }

    public boolean HasCall() {
        return HasCall;
    }

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

    public DataFrame GData() {
        return Data;
    }

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

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

    protected 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);

//        incEndereco = 0;
//        ocorrences = new LinkedHashMap<>();
//        ocorrencesPerBlocks.put(id, ocorrences);
//        dataFrame = new DataFrame(id);
//        dataFrames.put(id, dataFrame);
//        current = new ArrayList<>();
//        stmts.put(id, current);
//        pointerCorrente = new Instruction();
//        pointers.put(id, pointerCorrente);
//        pointerCorrente.S("initAddress", "" + posicaoLabel);
//        pointerCorrente.S("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();
        Block().Close();
        currentBlock.pop();
        Use(currentBlock.peek());
//        if (!storeReturnAddress) {
//            dataFrame.delete("ra");
//        }
//        genRepresentation();
//        pointerCorrente.S("endAddress", "" + (posicaoLabel - 1));
//        pointerCorrente = null;
//        current = null;
//        ocorrences = null;
//        dataFrame = null;
//        
    }

    protected void AfterOpen() throws Exception {
        for (Map.Entry<String, CodeProcessing> x : afterOpen.entrySet()) {
            x.getValue().Exec(this, afterOpen);
        }
    }

    protected void AfterClose() throws Exception {
        for (Map.Entry<String, CodeProcessing> x : afterClose.entrySet()) {
            x.getValue().Exec(this, afterClose);
        }
    }

    public void BeforeOpen(String id, CodeProcessing c) {
        afterOpen.put(id, c);
    }

    public void AfterClose(String id, CodeProcessing c) {
        afterClose.put(id, c);
    }

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

    public Block Block() {
        return current;
    }

    public String getCodeStream() throws Exception {
        
        // Executa os tratamentos defindos para o codigo;
//        Processing();

        // Parametro para formatacao dos enderecos
        GCtx.Set("_addressLen", (PosicaoLabel + "").length() + "");

        // Definicao dos enderecos dos labels
//        System.out.println("UpdateLabels:{");
        for (Map.Entry<String, ArrayList<String>> x : labels.entrySet()) {
            GCtx.Set(x.getKey(), x.getValue().get(0));
            GCtx.Set("label." + x.getKey(), x.getValue().get(1));
        }
//        System.out.println(GCtx);
//        System.out.println("}");

        StringBuilder s = new StringBuilder();
        for (Map.Entry<String, Block> entry : stmts.entrySet()) {

            s.append(getCodeStream(entry.getKey()));

        }
        return s.toString();
    }

    public String getCodeStream(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.G("format");

        if (format.equals("norender")) {
            return "";
        }

        if (!_templates.containsKey(format)) {
            _templates.put(format, TPLManager.New(GetTemplate(format), GCtx));
        }
//        System.out.println("Format:" + inst);
        format = _templates.get(format).Render(inst);
//        System.out.println("Format:" + format + "-");
        return format;
    }

    protected String GetTemplate(String template) throws Exception {
//        System.out.println("Gettemplate:" + template + templates);
        if (!templates.containsKey(template)) {
            throw new Exception(String.format("Template '%s' não definido. {%s}", template, templates.keySet()));
        }
        return templates.get(template);
    }

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

        Instruction ur = Last();
        //        int posicaoLocal = LocalPosition();
        if (ur != null) {
            String label = ur.G("label");
            switch (ur.G("format")) {
                case "label":
////                    nr.S("line", "" + (posicaoLabel--));
////                    nr.S("value", label + "  " + nr.getText());
////                    nr.S("label", label);
////                    corrente.remove(ur);
////                    RegisterLabelAddress(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.G("format").equals("label")
                            && label.equals(nr.G("label"))) {
                        current.Remove(ur);
                        add = false;
                    }
                    break;
            }
        }
        if (add) {
            switch (nr.G("type")) {
                case "call":
                    current.HasCall = true;
                    break;
                case "label":
//                    nr.S("label.address", RegisterLabelAddress(nr.G("label")));
                    break;
            }
            current.Add(nr);
        }
        return nr;
    }

    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 FRAME ...................");

        System.out.println(Data);

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

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

    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);
        String translated = label.replaceAll("\\+_i\\d+", "+" + Block().CurrentAddress);
        ArrayList<String> p = new ArrayList<>();
        p.add(Position() + "");
        p.add(translated);
//        System.out.println("Register label:" + label + "|" + Block().CurrentAddress);
        labels.put(label, p);
//        }
        return translated;
    }

    public HashMap<String, Ocorrence> getVarOcorrences(String block) throws Exception {
//        if (!ocorrencesPerBlocks.containsKey(block)) {
//            throw new Exception("Bloco '" + block + "' não definido!");
//        }
//        ocorrences = ocorrencesPerBlocks.G(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.G(var);
//        }
////        } else if (line.equals("+1")) {
//        o.Add(posicaoLabel, access);
    }

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

    boolean has(String R_RA) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

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

    public Code Formats(HashMap<String, String> templates) {
        this.templates = templates;
        return this;
    }

//    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);
//            }
//        }
//        }
    }

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

    public Code Template(TPLInterface template) {
        this.TPLManager = template;
        return this;
    }

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

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

    public int Position() {
        return PosicaoLabel;
    }

    public int PositionInc() {
        int cur = PosicaoLabel;
        PosicaoLabel += PositionIncrement;
        return cur;
    }
//
//    public DataFrame GData() {
//        return gdata;
//    }

}
