/*
 * 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.Types;
import API.Utils;
import ast.Node;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author Eugenio
 */
public class DataLayout {

    protected String ID;
    protected LinkedHashMap<String, Node> values = new LinkedHashMap<>();
    // address guarda o endereço de cada variavel
    protected LinkedHashMap<String, DataEntry> entrys = new LinkedHashMap<>();

    protected LinkedHashMap<String, Integer> addressMap = new LinkedHashMap<>();
    // Quantidade minima de bytes que
    protected Integer minWordSize = 1, // 8 bits
            // Quantidade maximo de bytes que corresponde a uma palavra
            maxWordSize = 4; // 32 bits

    protected boolean initialized = false;
    protected Pattern normalId = Pattern.compile("(?<id>[\\w_]+)(\\[(?<index>\\w+)\\])?");

    //Endereço base do conjunto
    public Long BaseAddress = 0L;
//    public int offsetctrl = 0;

//    protected DataLayout global = null;
    // Guarda a quantidade de palavras do frame
    // É atualizado sempre que o mapeamento dos endereços sofre alteração.
    protected Long FrameSize = 0L;
    protected Long FP = 0L;

    public DataLayout(String ID) {
        this.ID = ID;
    }

    public void SetInitialized(boolean init) {
        initialized = init;
    }

    public Long Size() {
        return FrameSize;
    }

    @Override
    public String toString() {
        StringBuilder s = new StringBuilder("Data Frame <" + ID + ">:" + Size() + "\n");
        DataEntry value;
        for (Map.Entry<String, DataEntry> x : entrys.entrySet()) {
            value = x.getValue();
            s.append(x.getKey())
                    .append(": ")
                    .append(value.toString())
                    .append("\n");

        }

        return s.toString();
    }

    protected String baseName(String id) {
        Matcher m = normalId.matcher(id);
        if (m.find()) {
            id = m.group("id");
        }
        return id;
    }

    protected String normalize(String id) {
        String shift = "0", index;

        Matcher m = normalId.matcher(id);
        if (m.find()) {
            id = m.group("id");
            index = m.group("index");
            if (Utils.IsNumber(index)) {
                shift = index;
            }
        }

        if (!id.contains(".")) {
            id += "." + shift;
        }
//        System.out.println("Normalize:" + id + "::" + or);
        return id;
    }

    public void Replace(String dst, String ori) {
        String rest;
        LinkedHashMap<String, Node> add = new LinkedHashMap<>();

        for (Map.Entry<String, Node> x : this.values.entrySet()) {

            rest = x.getKey().replace(dst, "");

            if (rest.equals("") || rest.substring(0, 1).equals(".")) {
                add.put(ori + rest, x.getValue());
            } else {
                add.put(x.getKey(), x.getValue());
            }
        }
        this.values = add;
    }

    void Remove(String dst) throws Exception {
        String alias;
        DataEntry de;
        Boolean updateFP = false;
        Long diff = 0L;
        for (Map.Entry<String, DataEntry> entry : entrys.entrySet()) {
            alias = entry.getKey();
            de = entry.getValue();

            if (alias.equals(dst)) {
                diff = de.getSize();
                FrameSize -= diff;
                entrys.remove(de);
                updateFP = true;
            }

            if (updateFP) {
                de.SetAddress(de.getAddress() - diff);
            }

        }
        FP -= diff;
        System.out.println("Remove:" + dst + "\n" + values);
    }

    // Pra baixo OK
    public Long Shift(String id) throws Exception {
        Long shift = 0L;
        Matcher m = normalId.matcher(id);
        if (m.find()) {
            String index = m.group("index");
            if (Utils.IsNumber(index)) {
                shift = Long.parseLong(index);
            }
        }
        return shift;
    }

    public Offset Offset(String id) throws Exception {
        String var = baseName(id);
        Long offset = 0L, shift = Shift(id);
//        if (id.contains("_G") && global != null) {
//            return global.Offset(id);
//        }

        try {
            offset = entrys.get(var).getAddress();
//            System.out.println("Offset from " + var);
//            System.out.println("Offset:" + id + ":\n" + offset);
        } catch (Exception e) {
//            System.out.println("address(" + id + ")[" + frameSize + "].:" + address + values);    
//            System.out.println("ERROR:" + e.getMessage());
            throw new Exception(String.format("Invalid offset %s in context %s.", var, ID));
        }
        return new Offset(offset, shift);
    }

    public DataEntry Add(String alias, String type, Integer numElements) throws Exception {

        if (entrys.containsKey(alias)) {
            throw new Exception("A variavel '%s' previamente definida no frame.");
        }

        Long size = AllocSizeOf(type) * numElements;
        DataEntry de = new DataEntry(size);

        de.SetAddress(FP);
        // Atualiza o frame pointer
        FP += size;
        FrameSize += size;
        entrys.put(alias, de);
        return de;
    }

    protected Long AllocSizeOf(String type) throws Exception {
        // Se o tipo for um ponteiro retorna o tamanho maximo de uma palavra em bytes
        if (type.contains("*")) {
            return maxWordSize.longValue();
        }

        Integer size = Types.Size(type), i = maxWordSize;

        while (i < size) {
            i += maxWordSize;
        }
        return i.longValue();
    }

    public boolean contains(String id) {
        return entrys.get(id) != null;
    }
}
//            System.out.println("Value:" + value);
//            s.append(x.getKey())
//                    .append(": ")
//                    .append(value.Get("ID"))
//                    .append("\tsize:")
//                    .append(value.Get("size"))
//                    .append("\tvalue:")
//                    .append(value.Get("default.value"))
//                    .append("\n");
//            break;
//    protected LinkedList<Node> values = new LinkedList<>();//Map< String, String> Copy = new TreeMap<>();
//    protected HashMap<String, Integer> size = new LinkedHashMap<>();
//    protected HashMap<String, Integer> type = new LinkedHashMap<>();
//    protected HashMap<String, Integer> _offset_ = new LinkedHashMap<>();
//    protected static AbstractSyntaxTree ast;
//    public static int TYPE_ARGUMENT = 0;
//    public static int TYPE_VAR = 1;
//    public static int TYPE_RETURN = 2;
//    public static int TYPE_STACK = 3;
//
//    /**
//     * Formata oS haSh para a impreSSão no metoglobalo toString
//     *
//     * @param hash
//     * @return
//     */
//    protected String hashToString(HashMap<String, Node> hash) {
//        String r = "";
//        for (Map.Entry<String, Node> entry : hash.entrySet()) {
//            String ID = entry.getKey();
//            Node value = entry.getValue();
////            int type = this.type.Get(ID);
//            r += "{" + ID + ":" + value + "},";
//        }
//        return r;
//    }
//    public DataLayout copy(DataLayout data) {
//        String k;
//        for (Map.Entry<String, Node> x : data.values().entrySet()) {
//            System.out.println("Copy:" + x.getKey() + "-" + x.getValue());
//            k = x.getKey();
//            values.put(k, x.getValue());
//            address.put(k, data.address.get(x));
//        }
//        frameSize = data.frameSize;
//        return this;
//    }
//
//    public void Set(String vname, Node copy) throws Exception {
//        values.put(
//                vname,
//                copy
//        );
//        address.put(vname, frameSize);
//        frameSize += copy.getInt("size");
//    }
//    public DataLayout Add(String alias, Node var, int elements) throws Exception {
//        //        System.out.println("Add global to frame:" + var);
////        int vSize = 1;
////        int size = var.getInt("size"), vSize = 1;
////        boolean isArray = var.eq("array", "true");
////        if (isArray) {
////            vSize = Tipos.Size(var.Get("type"));
////        }
//        ArrayList<String> defaults = var.getList("default.values");
//
//        Integer defSize = defaults.size();
//        String vname;
//        Node copy;
//        for (int i = 0; i < elements; i++) {
//            vname = alias + "." + i;
//            copy = var.copy().Set("default.value", (i < defSize ? defaults.get(i) : "0"));
//            Set(vname, copy);
////            values.put(
////                    vname,
////                    Copy
////            );
////
////            address.put(vname, frameSize);
////            frameSize += Copy.getInt("size");
//        }
//
//        return this;
//    }
//
//    public LinkedHashMap<String, Node> values() {
//        return values;
//    }
//
//    public boolean has(String id) {
//        return values.containsKey(id);
////        for (Map.Entry<String, Node> x : values.entrySet()) {
////            if (x.getKey().equals(id)) {
////                return true;
////            }
////        }
////        return false;
//    }
//    public static void setAbstractSyntaxTree(AbstractSyntaxTree ast) {
//        DataLayout.ast = ast;
//    }
