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

import API.AllocatorInterface;
import API.Api;
import API.Instruction;
import IntermediaryCode.BlockOcorrences;
import IntermediaryCode.Code;
import IntermediaryCode.CodeProcessing;
import IntermediaryCode.Interval;
import IntermediaryCode.OcorrenceFinderProcessor;
import java.util.LinkedHashMap;
import java.util.LinkedList;

/**
 *
 * @author EUGENIO CARVALHO
 */
public class AllocatorMipsProcessor implements AllocatorInterface, CodeProcessing {

    protected Code tac;
    protected Registers registers;
    protected LinkedList<String> free = new LinkedList<>();

    public AllocatorMipsProcessor() {

    }

    @Override
    public void Exec(Code c, LinkedHashMap<String, CodeProcessing> cp) throws Exception {
        registers = new Registers();
//        System.out.println("Exec Allocation:" + c.getCurrentBlockName());
        BlockOcorrences ocorrences = ((OcorrenceFinderProcessor) cp.get("live.var"))
                .getBlocks()
                .get(c.Block().getName());
        
//        System.out.println(ocorrences);

        for (Instruction x : c.Block().Instructions()) {
            Assign(x, ocorrences);
        }

        System.out.println(ocorrences);
    }

    @Override
    public void Alloc(Code code) {
        this.tac = code;
    }

    @Override
    public void Get(String id, int position) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    protected void Assign(Instruction x, BlockOcorrences ocorrences) throws Exception {
        // tipos onde não se usa registrador
        switch (x.G("type")) {
            case "label":
            case "jump":
            case "call":
                return;
        }
        // Libera registradores alocados temporariamente
        while (!free.isEmpty()) {
            registers.Free(free.pop());
        }

        String reg;

        if (x.Has("dst")) {
            x.S("reg.dst", GetRegister(x, "dst", ocorrences));
        }

        for (String param : new String[]{"p1", "p2", "dst.indice", "p1.indice"}) {
            if (!x.Has(param)) {
                continue;
            }
//            System.out.println("PARAM:[" + param + "value" + "]" + x.eq(param + "value", "true"));

            if (x.eq(param + "value", "true") || x.isNumber(param)) {
                reg = registers.Lock("t");
                free.push(reg);
                Load(x, param);
//                System.out.println("Push:" + reg + "-");

            } else {
//                System.out.println("GX:" + param + "->" + x);
                reg = GetRegister(x, param, ocorrences);
            }

            x.S("reg." + param, reg);
        }

//        System.out.println("Assign:" + x);
    }

    protected void Load(Instruction x, String param) {
        x.S("reg." + param + ".load", "true");
    }

    protected String GetRegister(Instruction x, String address, BlockOcorrences ocorrences) throws Exception {
//        System.out.println("GerRegister:" +);

//        String reg, addr = Api.clearID(x.G(address));
        String reg, addr = ocorrences.ToAliase(x, address);
        int position = x.getInt("block.position");
        boolean dst = address.equals("dst");

//        System.out.println("GetRegister:" + address + ":" + addr + ":" + position + "{");
        Interval interval = ocorrences.Get(addr, position);
        // Define um registrador se não possuir um definido no intervalo
        if (!interval.RegisterDefined()) {
//            System.out.println("Não tinha definido: Carregar da memoria(" + (!dst) + ")");

            // Verifica a existencia de um registrador disponivel
            reg = AvaliableRegister(addr);

            if (!dst) {
                // Parametro define que o valor deve ser carregado em registrador;
                Load(x, address);
            }

            // Libera um registrador ocupado
            if (reg == null) {
                reg = ocorrences.Spill(position).getRegister();
            }
            // Vincula o registrador com o intervalo
            interval.setRegister(reg);
        } else {
            reg = interval.getRegister();

            // Se escreve no registrador e não é o ultimo intervalo marca para 
            // ser salvo marca para ser salvo.
//            if (dst && !ocorrences.Last(addr).equals(interval)) {
            // Libera o registrador
//            System.out.println("Tinha definido::" + reg + ":Liberadp{" + addr + "}:" + (interval.End == position));
        }

        if (dst) {
            interval.Perisist(true);
        }
        // Se for 
        // -> A ultima ocorrencia do intervalo e
        // -> O registrador é saved e
        // -> Ocorreu escrita no registrador
        // -> Ponteiros e variaveis globais devem persistidas apos cada operacao de escrita
        // marca para gravar em memoria
        if (interval.End == position) {
            registers.Free(reg);

//            System.out.println("reg(" + reg + ")persist(" + interval.Perisist() + ")");
            if (reg.substring(0, 1).equals("s")
                    && interval.Perisist()) {
                x.S("reg." + address + ".store", "true");
            }
        }
//        System.out.println("}" + reg);
        return reg;
    }

    protected String AvaliableRegister(String alias) throws Exception {
        String reg = "";
        switch (Api.clearID(alias).substring(1, 2)) {
            case "V":
            case "G":
                reg = "s";
                break;
            case "T":
            case "C":
                reg = "t";
                break;
            default:
                throw new Exception(String.format("Not avaliable register to alias '%s'", alias));
        }
        return registers.Lock(reg);
    }

    protected String PopReturnRegister() {
        return null;
    }

}
