/*?
 * 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 ast.Node;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

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

//    public static AbstractSyntaxTree ast;
    public static HashMap<String, Node> _types = new HashMap<>();
    // Tamanho em bytes de cada tipo 
    public static HashMap<String, Integer> _sizes = new HashMap<>();

    public static HashMap<String, Integer> primitiveTypes = new HashMap<>();
    public static HashMap<String, HashMap<String, String>> _propList = new HashMap<>();
    public static HashMap<String, Integer> _offsetList = new HashMap<>();
    public static HashMap<String, TreeMap<Integer, ArrayList<String>>> baseLayoutMeta = new HashMap<>();

    // Tamanho de um ponteiro em bytes
    public static Integer PTR_SIZE = 4;

    public static HashMap<String, Integer> getSizes() {
        return _sizes;
    }

    public static void _init() throws Exception {
        HashMap<String, Integer> types = new HashMap<String, Integer>() {
            {
                //Numeric types
//                uint8       the set of all unsigned  8-bit integers (0 to 255)
//                uint16      the set of all unsigned 16-bit integers (0 to 65535)
//                uint32      the set of all unsigned 32-bit integers (0 to 4294967295)
//                uint64      the set of all unsigned 64-bit integers (0 to 18446744073709551615)
//                int8        the set of all signed  8-bit integers (-128 to 127)
//                int16       the set of all signed 16-bit integers (-32768 to 32767)
//                int32       the set of all signed 32-bit integers (-2147483648 to 2147483647)
//                int64       the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
//                float32     the set of all IEEE-754 32-bit floating-point numbers
//                float64     the set of all IEEE-754 64-bit floating-point numbers
//                complex64   the set of all complex numbers with float32 real and imaginary parts
//                complex128  the set of all complex numbers with float64 real and imaginary parts
//                byte        alias for uint8
//                rune        alias for int32
                put("byte", 1);
                put("uint8", 1);
                put("uint16", 2);
                put("uint32", 4);
                put("uint64", 8);
                put("int8", 1);
                put("int16", 2);
                put("int32", 4);
                put("int64", 8);
                put("float32", 4);
                put("float64", 8);
                put("complex64", 8);
                put("complex128", 16);
                put("bool", 1);
                put("char", 1);
            }
        };
        String type;
        for (Map.Entry<String, Integer> entry : types.entrySet()) {
            type = entry.getKey();
            Add(type, new Node(type)
                    .Set("size", entry.getValue())
                    .Set("file", "std")
                    .Set("scope", "")
                    .Set("primitive", "true")
                    .Set("block", "true") // Diz que não deve editar o tipo
                    .Set("class", "type")
                    .Set("public", "true")
            );
        }

    }

    // Deve ser chamada no fim da leitura do listner para atuaizar os tamanhos dos tipos
    public static void Update() throws Exception {
        Node def;

        for (Map.Entry<String, Node> entry : _types.entrySet()) {
            Node type = entry.getValue();

            if (type.eq("block", "true")) {
                continue;
            }

            // Completa a definicao da struct importando os atributos herdados
            for (Node t : type.findAll("type", "class", 1)) {
                def = Get(t.Get("type"));
                for (Node attr : def.findAll("attr", "class")) {
                    attr.Set("path", def.getText());
                    type.addFilho(attr);
                }
            }

            LayoutType(type);
        }

    }

    protected static void LayoutType(Node type) throws Exception {
//        System.out.println("type:" + type);
        String id, t = type.Get("type");
        Integer size,
                // Atualiza o tamanho to tipo
                structSize = 0;

        if (baseLayoutMeta.containsKey(t)) {
            return;
        }

        TreeMap<Integer, ArrayList<String>> layout = new TreeMap<>();

        baseLayoutMeta.put(t, layout);

        for (Node attr : type.findAll("attr", "class", 1)) {
            id = attr.Get("value");
            size = Size(attr.find("type").Get("type"));
            if (!layout.containsKey(size)) {
                layout.put(size, new ArrayList<>());
            }
            layout.get(size).add(id);

            switch (attr.Get("pointer")) {
                case "*":
                    structSize += PTR_SIZE;
                    break;
                default:
                    structSize += Size(attr.childrens().get(0).Get("type"));
            }
        }
//        System.out.println("map:" + layout);
        _sizes.put(type.getText(), structSize);
    }

    protected static void LayoutType(String type) {

    }

//    protected static void UpdateSize(Node type) throws Exception {
//
//        for (Node attr : type.findAll("attr", "class")) {
//
//            switch (attr.Get("pointer")) {
//                case "*":
//                    size += PTR_SIZE;
//                    break;
//                default:
//                    size += Size(attr.childrens().get(0).Get("type"));
//            }
//
//        }
//        _sizes.put(type.getText(), structSize);
//    }
    /**
     * Adiciona um tipo a linguagem
     *
     * @param id
     * @param type
     * @throws java.lang.Exception
     */
    public static void Add(String id, Node type) throws Exception {

        if (_types.containsKey(id)) {
            throw new Exception(String.format("Tipo '%s' previamente definido na linha %s", id, _types.get(id).Get("line", "")));
        }

        if (!type.Has("public")) {
            type.Set("public", Base.IsPublic(id) ? "true" : "false");
        }
        type.Set("type", id)
                .Set("value", id);

        if (type.Has("size")) {
            _sizes.put(id, type.GetInt("size"));
        }

        _types.put(id, type);

        // Adiciona ao controle de tipos primitivos
        if (type.eq("primitive", "true")) {
            getPrimitiveTypes().put(type.Get("value"), 1);
        }
    }

    /**
     * Retorna verdadeiro Se o tipo exiStir
     *
     * @param type
     * @return true Se o tipo exiSte
     * @throws Exception caSo tipo não exiSta
     */
    public static Node Get(String id) throws Exception {
        if (!_types.containsKey(id)) {
            throw new Exception(String.format("Tipo '%s' não definido.", id));
        }
        return _types.get(id);
    }

    public static boolean Defined(String id) {
        return _types.containsKey(id);
    }

    public static void List() {
        System.out.println("Types:\n\t" + _sizes);
    }

    public static Integer Size(String type) throws Exception {
        if (!_sizes.containsKey(type)) {
            Node t = Get(type);
            LayoutType(t);
        }
        return _sizes.get(type);
    }

    public static Integer Shift(String type, ArrayList<String> path) throws Exception {
        return Shift(type, path.get(path.size() - 1));
    }

    public static Integer Shift(String type, String attrib) throws Exception {

        Node T = Get(type);
        String attrType;
        Integer shift = 0;
//        System.out.println("Shift header:" + type + ":" + attrib + ":" + T);
//        System.out.println("Shift:{");
        for (Node attr : T.findAll("attr", "class", 1)) {

            attrType = attr.childrens().get(0).Get("type");

//            System.out.println("Shift:" + attr.getText() + "[" + attrib + "]");
            if (!attr.getText().equals(attrib)) {
                shift += Size(attrType);
                //incrementar e buscar no proximo
                continue;
            }
            break;
        }
//        System.out.println("}");
        return shift;
    }

    public static HashMap<String, Integer> getPrimitiveTypes() {
        return primitiveTypes;
    }

    public static boolean Primitive(String type) {
        return primitiveTypes.containsKey(type);
    }

    public static String[] getPrimitiveTypesList() {
        Set<String> set = primitiveTypes.keySet();
        String[] ret = new String[set.size()];
        Integer i = 0;
        for (String s : set) {
            ret[i++] = s;
        }
        return ret;
    }

}
