/*
 * 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 targets.mips;

import API.Api;
import API.Utils;
import common.RegistroBase;
import java.util.ArrayList;
import java.util.HashMap;
import templates.FunctionInterface;
import templates.Functions;

/**
 *
 * @author EUGENIO CARVALHO
 */
public class MipsTemplateDescription implements templates.TemplateDescription {

    public HashMap<String, String> Formats() throws Exception {
        String base = "{[HEX(global.position,G.addressLen,' ')]':  '}{[INSTDEC()]}{[T(1)]}{[inst]' '}",
                end = "{[T(2)]}{'.'[tac.position]}{' -- '[comment]}";

        return new HashMap<String, String>() {
            {
                put("S", base + end);

                put("label", "'  '{[HEX(global.reference.position,8,0)]}{[INSTDEC()]}{' <'[label]'>:'");

                // Template para instrucoes do tipo J
                put("J", base + "{[rs]}{[LM(label,'hex')]}" + end);

                // Template para instrucoes do tipo R
                put("R0", base + "{[rd]','}{[rs]','}{[rt]}" + end);
                put("R1", base + "{[rs]','}{[rt]}" + end);
                put("R2", base + "{[rs]}" + end);
                put("R3", base + "{[rt]','}{[rs]}" + end);
                put("R4", base + "{[rd]','}{[rt]','}{[sa]} " + end);
                put("R5", base + "{[rd]}" + end);

                // Template para instrucoes do tipo I
                put("I0", base + "{[rt]','}{[rs]','}{[LM(label,'hex')]|[offset]|[rd]}" + end); //[rs:sp,rt:7,rd:sp]  
                // SW,LW
                put("I1", base + "{[rt]','}{[OFFSET(offset,rs)]}" + end);
                // Templates avulsos
                put("PSEUDO", base);

                // Templates para instrucoes binarias 
                put("bin.R", "{[codop]}{[RBIN(rs,5)]}{[RBIN(rt,5)]}{[RBIN(rd,5)]}{[BIN(sa,5)]}{[func]}");
                put("bin.I", "{[codop]}{[RBIN(rs,5)]}{[RBIN(rt,5)]}{[BIN(offset,16)]}");
                put("bin.J", "{[codop]}{[target]}");
                put("bin.S", "{[txt]}");
            }
        };
    }

    public HashMap<String, FunctionInterface> Functions() throws Exception {

        return new HashMap<String, FunctionInterface>() {
            {
                put("OFFSET", (ctx, args) -> args.get(0) + "(" + args.get(1) + ")");

                put("INSTHEX", (ctx, args) -> MipsTemplateDescription.ConvertBin("hex", ctx, args));

                put("INSTDEC", (ctx, args) -> MipsTemplateDescription.ConvertBin("dec", ctx, args));

                put("INSTBIN", (ctx, args) -> MipsTemplateDescription.INSTBIN(ctx, args));

                put("RBIN", (ctx, args) -> {
                    String reg = args.get(0);
                    if (reg.equals("")) {
                        reg = "0";
                    }
                    if (!Utils.IsNumber(reg)) {
                        reg = Descriprion.Register(reg);
                    }
                    return Api.num2bin(reg, Integer.parseInt(args.get(1)));
                });

                put("LM", (ctx, args) -> {

                    String label = args.get(0).trim();
                    if (label.equals("")) {
                        return label;
                    }
//                    System.out.println("RUN LM: " + args + ctx);
                    String prefix = "G.";

                    String format = (args.size() > 1) ? args.get(1) : "dec",
                            // Corresponde ao nome do label que marca o inicio do bloco 
                            base = ctx.Get(prefix + "label." + label).split("\\+")[0],
                            // Corresponde ao endereco do label base (decimal)
                            baseAddress = ctx.Get(prefix + base),
                            // Corresponde a posicao do label destino (decimal)
                            laddress = ctx.Get(prefix + label);

//        System.out.printf("TemplateListener:{%s\n %s\n %s\n %s\n}\n", base, baseAddress, label, laddress);
                    Long value = Long.parseLong(laddress);
                    Long fromStart = value - Long.parseLong(baseAddress);

                    switch (ctx.Get("type")) {
                        case "I": // (bne...) Salta para posicao em relacao ao pc atual
                        case "J": // (j | jal) Salta para o endereco de destino
                            break;
                        default:
                            System.out.println(String.format("Label type '%s' not defined", ctx.Get("type")));
                    }

                    return laddress.trim().equals("")
                            ? ""
                            : (Utils.FormatNum(value, format)
                            + " <" + base + "+0x"
                            + Utils.FormatNum(fromStart, format)
                            + ">");
                });

            }
        };
    }

    // Shared functions 
    protected static String ConvertBin(String inst, RegistroBase ctx, ArrayList<String> args) {
        try {
            Integer size;
            String bin = MipsTemplateDescription.INSTBIN(ctx, args);

            if (Utils.Empty(bin)) {
                return "";
            }
            Long bin2long = Long.parseLong(bin, 2);
//            System.out.println("CONVERTbin:" + bin + "\nbin2long:" + bin2long);

            switch (inst) {
                case "hex":
                    size = 8;
                    bin = Long.toString(bin2long, 16);
                    break;
                case "dec":
                    size = 10;
                    bin = "" + bin2long;
                    break;
                default:
                    throw new Exception(String.format("Conversão para o tipo '%s' não definida.", inst));
            }

            String result = Utils.Pad(bin, "0", "", size);

            ctx.Set("inst." + inst, result);
            return result;

        } catch (Exception e) {
            if (ctx.Get("inst").equals("bgtz")) {
                System.out.println("hex-Error:" + e.getMessage());
            }
            System.out.println(e.getMessage());
            e.printStackTrace();
            return "error!";
        }
    }

    protected static String INSTBIN(RegistroBase ctx, ArrayList<String> args) throws Exception {

        String type = ctx.Get("type");
        if (type.equals("label")) {
            return "";
        }

        long shift;
        //
        switch (type) {
            case "S": // (j | jal) Salta para o endereco de destino
//                System.out.println("instbin:" + ctx);
                break;
            case "J": // (j | jal) Salta para o endereco de destino
//                System.out.println("JUMP:" + ctx.Get(ctx.Get("label")) + ":"
//                        + (Long.parseLong(ctx.Get(ctx.Get("label")), 10) >> 2));

                shift = (Long.parseLong(ctx.Get("G." + ctx.Get("label")), 10) >> 2);
                ctx.Set("target", Functions.BIN("" + shift, 26));
                break;
            case "I": // (bne...) Salta para posicao em relacao ao pc atual + 4

//                System.out.println("BIN iiiiii" + ctx);
//                shift = (Long.parseLong(ctx.Get(ctx.Get("label")), 10) >> 2);
//                ctx.Set("target", BIN("" + shift, 26));
//                if (ctx.Get("offset").equals("") && !ctx.Get("label").equals("")) {
//
//                    int target = Integer.parseInt(ctx.Get(ctx.Get("label")));
//                    int position = Integer.parseInt(ctx.Get("global.position")) + 1;
//
////                    System.out.println("INSTBIN:->" + FormatNum(target - position, "hex"));
//                    ctx.Set("offset", (target - position) + "");
//
//                }
//                System.out.println("INSTBIN[" + type + ":" + bin + "]:" + ctx);
                break;
            case "R": // (bne...) Salta para posicao em relacao ao pc atual
//                System.out.println("INSTBIN[" + type + "]:" + ctx);
                break;
            default:
        }
        String bin = templates.Template.FormatRender("bin." + type).Render(ctx);

        ctx.Set("inst.bin", bin);
//        System.out.println("bin." + type + "::" + bin);
        return bin;
    }

}

//    @Override
//    public String Render(RegistroBase ctx) {
//
//        if (tpls.isEmpty()) {
//            tpls.put("bin.R", New("{[codop]}{[RBIN(rs,5)]}{[RBIN(rt,5)]}{[RBIN(rd,5)]}{[BIN(sa,5)]}{[func]}", ctx));
//            tpls.put("bin.I", New("{[codop]}{[RBIN(rs,5)]}{[RBIN(rt,5)]}{[BIN(offset,16)]}", ctx));
//            tpls.put("bin.J", New("{[codop]}{[target]}", ctx));
//            tpls.put("bin.S", New("{[txt]}", ctx));
//        }
//
//        TemplateFunctions listener = new TemplateFunctions(ctx, ctx);
////        listener.AddressGap = 4;
//        listener.templates = tpls;
//
//        walker.walk(listener, tree);
//
//        return listener.toString();
//    }
//    @Override
//    public TPLInterface New(String tpl, RegistroBase ctx) {
//        return new Template(tpl, ctx);
//    }
