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

import API.BuildParams;
import API.ExportInterface;
import API.Utils;
import common.Block;
import common.Code;
import common.Instruction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import targets.mips.Descriprion;
import targets.mips.MipsTemplateDescription;
import targets.mips.MipsUpdateAddressMiddleware;

/**
 * MipS tranSlation
 *
 * @author EUGENIO CARVALHO
 */
public class MultCoreJun implements ExportInterface {

    private Code target;

    @Override
    public void Exec(Code IR, Code Target) throws Exception {

//        return;
        try {
            this.target = Target;

            Code newCode;
            System.out.println("Export MIPSJUN");

            Long stackBaseAddress = 0L,
                    updateBaseAddress = 0L,
                    size;

            String id,
                    initFunction,
                    blockName,
                    format,
                    cacheBlockSize = BuildParams.GetFirst("cacheBlockSize"),
                    base = BuildParams.GetFirst("outputDirectory");
            String[] parts;

            Block func;
            JSONObject profile;

            MipsUpdateAddressMiddleware uap = new MipsUpdateAddressMiddleware();

            ArrayList<String> p = BuildParams.Get("profile");

            if (!p.isEmpty()) {
                JSONArray pobject = (JSONArray) new JSONParser().parse(p.get(0));
                for (Object pro : pobject) {

                    profile = (JSONObject) pro;
                    id = (String) profile.get("id");
                    initFunction = (String) profile.get("initFunction");

                    parts = initFunction.replace(")", "").split("\\(");
                    blockName = parts[0];
                    // Busca o bloco inicial referenciado pelo usuario
                    func = Target.Blocks().get(blockName);

                    if (profile.containsKey("stackBaseAddress")) {
                        stackBaseAddress = (Long) profile.get("stackBaseAddress");
                    } else {
//                        System.out.println("stackBaseAddress:(" + stackBaseAddress + ")(" + MaxStackSize(func) + ")");
                        size = MaxStackSize(func);
                        updateBaseAddress = stackBaseAddress
                                - size
                                - fixCacheBlockSize(size, Long.parseLong(cacheBlockSize));
                    }

                    // Cria um novo codigo para armazenas as instruções do core
                    newCode = NewCode(id);
                    // Copia o bloco da funcao e todos os blocos que gerem dependencia
                    CopyBlock(newCode,
                            func,
                            parts,
                            stackBaseAddress,
                            true);
                    // Atualiza os enderecos de salto do codigo
                    uap.Exec(newCode, null);

                    format = (String) profile.get("filename");

                    if (format == null) {
                        throw new Exception("Arquivo de saida para o core `" + id + "` não definido!");
                    }

                    SaveMips(newCode, base + "\\" + String.format(format, "mips"));
                    SaveDec(newCode, base + "\\" + String.format(format, "dec"));

                    stackBaseAddress = updateBaseAddress;

//                    ExecuteCode(newCode);
//                    break;
//                  System.out.println("value::" + code);
                }
                //  System.out.println("params:" + getIR().ast.getCompileParams().GetLists());
                //  System.out.println("params:" + p);
            }

//        } catch (ParseException ex) {
//            Logger.getLogger(MultCoreJun.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            Logger.getLogger(MultCoreJun.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    protected Long fixCacheBlockSize(Long size, Long sizeOfBlock) {
        Long t = sizeOfBlock;
        while (true) {
            if (sizeOfBlock > size) {
                break;
            }
            sizeOfBlock += t;
        }
        return sizeOfBlock - size;
    }

    protected void SaveMips(Code newCode, String filename) throws Exception {
        Utils.WriteFile(filename, newCode.Dump());
    }

    protected void SaveDec(Code newCode, String filename) throws Exception {
        String code = "", tmp;

        for (Map.Entry<String, Block> x : newCode.stmts.entrySet()) {
            for (Instruction instr : x.getValue().Instructions()) {
                tmp = instr.Get("inst.dec");
                if (tmp.equals("")) {
                    continue;
                }
                code = code.concat(Long.parseLong(tmp) + "\n");
            }
        }

        Utils.WriteFile(filename, code);
    }

    protected Code CopyBlock(Code newCode, Block func, String[] funcCall, Long stackBaseAddress, boolean main) throws Exception {
//        Instruction Ninst;
        int index = 1;
        String blockName = funcCall[0];

        newCode.OpenBlock(blockName);
        Block block = newCode.Block();
        // Carrega os argumentos da chamada
        if (funcCall.length > 1) {
//            System.out.println("Tinha parametro:" + funcCall[1]);
            String[] args = funcCall[1].split(",");
            int i = 0;
            for (String arg : args) {
//                Ninst = new Instruction();
//                Variaveis.List();
//                System.out.println("Variaveis.Get(arg):" + Variaveis.Get("main." + args));
                block.Add(Descriprion.Instruction("addiu")
                        .Set("rs", "zero")
                        .Set("rt", "a" + (i++))
                        .Set("offset", arg));

            }
            index += args.length;
        }

        for (Instruction inst : func.Instructions()) {
            block.Add(inst.copy());
        }

        if (main) {
            LinkedList<Instruction> instructions = newCode.Block().Instructions();
            // Atualiza o offset da pilha
            Instruction first = instructions.get(index);
//            System.out.println("CopyBlock[" + stackBaseAddress + "||" + first.getLong("offset") + "]");
            first.Set("offset", first.getLong("offset") + stackBaseAddress);

            // System.out.println("instructions.Get(0);" + first);
            // Atualiza a ultima instrucao de um retorno para ra -> stop
            block.Remove(instructions.get(instructions.size() - 1));
            block.Add(Descriprion.Instruction("stop")
                    .Set("comment", "end of programa"));
        }
        newCode.CloseBlock();

        // Adiciona as funcoes que geram dependencias 
        HashMap<String, Block> tb = target.Blocks();
        ArrayList<String> deps = tb.get(blockName).GetDependences();

        if (!deps.isEmpty()) {
            for (String dep : deps) {
                CopyBlock(newCode, tb.get(dep), new String[]{dep}, 0l, false);
            }
        }

        return newCode;
    }

    protected Code NewCode(String id) throws Exception {
        return new Code(id).Template(new MipsTemplateDescription());
    }

    protected Long MaxStackSize(Block blk) {
        HashMap<String, Block> tb = target.Blocks();
//        System.out.println("data:" + blk.Context());
        Long childSize = 0L, tmpChildSize;

        ArrayList<String> deps = blk.GetDependences();
        if (!deps.isEmpty()) {
            for (String dep : deps) {
                tmpChildSize = MaxStackSize(tb.get(dep));
                if (tmpChildSize > childSize) {
                    childSize = tmpChildSize;
                }
            }
        }
//        System.out.println("MaxStackSize:" + size + "|" + childSize);
        return blk.Context().Size() + childSize;
    }
}
