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

import API.Instruction;
import API.Utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import target.mips.Mips;

/**
 *
 * @author EUGENIO CARVALHO
 */
public class MipsProcessor {

    public MipsSettings settings;
    public Memory MD;
    public Memory MI;
    public long PC = 0;
    public RegisterBank RBank;
    public HashMap<String, Instruction> instructions;

    public HashMap<String, Boolean> breakPoint = new HashMap<>();

    public MipsProcessor(MipsSettings settings) throws IOException {
        this.settings = settings;
        int size = (16 * 1024); //16KB
        MI = new Memory(settings.Get("mi"), size, false);
        MD = new Memory(settings.Get("md"), size, true);
        // Limpa todos os dados armazenados 
        MD.Reset();
        RBank = new RegisterBank(32);

        InitInstructions();

//        SetBreak("44,48,58,5c");
//        InitInstructions2();
    }

    public MipsProcessor SetBreak(String address) {
        for (String addr : address.split(",")) {
            if (!addr.equals("")) {
                breakPoint.put(addr, true);
            }
        }
        return this;
    }

    public MipsProcessor Persist() {
        MD.Save();
        return this;
    }

    protected void Log(String msg) {
        if (settings.debugmode) {
            System.out.println(msg);
        }
    }

    public MipsProcessor Run() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // Fetch
//        System.out.println("MI:" + MI);

        try {
            Instruction instruction;
            int i = 0;
            String cAddress;
            while (true) {
                long inst = MI.R(PC);
                instruction = Decode(inst);

                if (instruction.eq("inst", "stop")) {
                    break;
                }
                cAddress = Integer.toHexString((int) PC);

                Log("Interation(" + (i++) + ")["
                        + cAddress + "]"
                        + instruction.G("inst"));

                Execute(instruction);

                Log("Registradores(\n" + RBank
                        + ")\b nextPC:" + Integer.toHexString((int) PC));

                if (settings.stepByStep || breakPoint.containsKey(cAddress)) {
                    br.readLine();
                }
                // Decode
                // Exec
                // Persist
            } //        return true;
        } catch (Exception ex) {
            Logger.getLogger(MipsProcessor.class.getName()).log(Level.SEVERE, null, ex);
        }
        return this;
    }

    public Instruction Decode(long instruction) {
        int val = (int) instruction;
        String bin = Utils.pad(32, Integer.toBinaryString(val));
//        System.out.println("Run:" + bin + ":" + bin.length());
        String codop = bin.substring(0, 6), key, func = "";
        if (codop.equals("000000")) {
            func = bin.substring(26);
            key = "@" + func;
        } else {
            key = codop;
        }

//        System.out.println(">>: "
//                //                + "\n" + instruction
//                + "\n" + bin
//                //                //                + "\n" + val
//                //                + "\nKey:" + key
//                + "\n" + instructions.get(key));
        if (!instructions.containsKey(key)) {
            System.out.println("Não encontrou a instrução " + key);
            return null;
        }

        Instruction i = instructions.get(key).copy();

        String rs = bin.substring(6, 11),
                rt = bin.substring(11, 16);

        switch (i.G("type")) {
            case "R":
                i.S("rs.bin", rs);
                i.S("rt.bin", rt);
                i.S("rd.bin", bin.substring(16, 21));
                i.S("shamt.bin", bin.substring(21, 26));
                i.S("funct.bin", func);

                i.S("rs", Long.parseLong(rs, 2));
                i.S("rt", Long.parseLong(rt, 2));
                i.S("rd", Long.parseLong(bin.substring(16, 21), 2));
                i.S("shamt", Long.parseLong(bin.substring(21, 26), 2));
                i.S("funct", func);
                break;
            case "I":
                i.S("rs.bin", rs);
                i.S("rt.bin", rt);
                i.S("imm.bin", bin.substring(16));

                i.S("rs", Long.parseLong(rs, 2));
                i.S("rt", Long.parseLong(rt, 2));
                i.S("imm", bin32ToDec(bin.substring(16)));

//                System.out.println("II:" + bin.substring(16)
//                        + "\n:" + bin32ToDec(Utils.padPreserveSignal(32, bin.substring(16)))
//                        + "\n:" + (Long.parseLong(Utils.padPreserveSignal(32, bin.substring(16)), 2) >> 0)
//                );
//                System.out.println("IIII:" + bin32ToDec(bin.substring(16)));
                break;
            case "J":
                i.S("addr.bin", bin.substring(6));
                i.S("imm", bin32ToDec(bin.substring(6)));
//                System.out.println("JJJJ:" + bin32ToDec(bin.substring(6)));
        }
        return i;
    }

    public static long bin32ToDec(String bin) {
        bin = Utils.padPreserveSignal(32, bin);
//        System.out.println("BIN32DEC:" + bin);

        if (bin.charAt(0) == '0') {
            return Long.parseLong(bin, 2);
        } else {
            long number = 0;
            for (int index = 0; index < 32; index++) {
                int b = bin.charAt(index) - '0';
                number = number << 1 | b;
            }
            return (int) number;
        }

    }

    public String IntToHexStr(Integer n) {
        return IntToHexStr(n) + "";
    }

    public Integer IntToHex(Integer n) {
        return Integer.valueOf(String.valueOf(n), 16);
    }

    protected void InitInstructions() {
        instructions = new HashMap<String, Instruction>();
        String key;
        for (Map.Entry<String, Instruction> entry : Mips.Codops.entrySet()) {
//            String string = entry.getKey();
            Instruction instruction = entry.getValue();

            key = instruction.G("codop");

            if (instruction.eq("codop", "000000")) {
                key = "@" + instruction.G("func");
            }
//            System.out.println("Add_Instruction: " + key + "\n");
//            System.out.println("===" + entry.getKey());
            instructions.put(key, instruction);
        };
    }

    private void Execute(Instruction inst) throws Exception {
//        System.out.println("Exec :" + inst);

        long rs = 0,
                rt = 0,
                imm = 0,
                rd = 0,
                shamt = 0;
        boolean hasImm = true;

        switch (inst.G("type")) {
            case "R":
                rd = inst.getInt("rd");
                shamt = inst.getInt("shamt");
                hasImm = false;
            case "I":
                rs = inst.getInt("rs");
                rt = inst.getInt("rt");
            case "J":
                if (hasImm) {
//                    System.out.println("HAS_IMM:" + inst.G("imm"));
                    imm = inst.getLong("imm");
                }
        }

        Log(inst.G("inst")
                + " [rd:" + RBank.names.get((int) rd)
                + "] [rs:" + RBank.names.get((int) rs)
                + "] [rt:" + RBank.names.get((int) rt)
                + "] [shamt:" + shamt
                + "] [imm:" + imm + "]");

        switch (inst.G("inst")) {
            // R
            case "add":
            case "addu":
                RBank.W(rd, RBank.R(rs) + RBank.R(rt));
                break;
            case "addi":
            case "addiu":
//                System.out.println("RBank.R(rs) + imm:" + RBank.R(rs) + ":" + imm);
                RBank.W(rt, RBank.R(rs) + imm);
                break;
            case "stop":
                break;
            case "and":
            case "andi":
                RBank.W(rd, RBank.R(rs) & RBank.R(rt));
                break;
            case "div":
            case "divu":
                RBank.W(RBank.REG_LO, RBank.R(rs) / RBank.R(rt));
                RBank.W(RBank.REG_HI, RBank.R(rs) % RBank.R(rt));
                break;
            case "jr":
                PC = RBank.R(rs);
                break;
            case "mfhi":
                RBank.W(rd, RBank.R(RBank.REG_HI));
                break;
            case "mflo":
                RBank.W(rd, RBank.R(RBank.REG_LO));
                break;
            case "mthi":
                RBank.W(RBank.R(RBank.REG_HI), RBank.R(rs));
                break;
            case "mtlo":
                RBank.W(RBank.R(RBank.REG_LO), RBank.R(rs));
                break;
            case "mult":
            case "multu":
                // verificar a questao do hi e lo

//                System.out.println("Mult:"
//                        + rs + "(" + RBank.names.get((int) rs) + "):"
//                        + rt + "(" + RBank.names.get((int) rt) + ")");
                RBank.W(RBank.REG_LO, RBank.R(rs) * RBank.R(rt));
                break;
//            case "nor":
//                RBank.W(rd, rs * rt);
//                break;
            case "or":
                RBank.W(rd, RBank.R(rs) | RBank.R(rt));
                break;
            case "sll":
                RBank.W(rd, RBank.R(rt) << shamt);
                break;
            case "slt":
                RBank.W(rd, (RBank.R(rs) < RBank.R(rt)) ? 1 : 0);
                break;
            case "sra":
                RBank.W(rd, RBank.R(rt) >> shamt);
                break;
            case "srl":
                RBank.W(rd, RBank.R(rt) >>> shamt);
                break;
            case "sub":
            case "subu":
                RBank.W(rd, RBank.R(rs) - RBank.R(rt));
                break;
            case "xor":
                RBank.W(rd, RBank.R(rs) ^ RBank.R(rt));
                break;
//            // I
            case "beq":
                if (RBank.R(rs) == RBank.R(rt)) {
                    PC += ((imm + 1) << 2);
                    return;
                }
                break;
            case "bgez":
                if (RBank.R(rs) >= 0) {
                    PC += ((imm + 1) << 2);
                    return;
                }
                break;
            case "bgtz":
                if (RBank.R(rs) > 0) {
                    PC += ((imm + 1) << 2);
                    return;
                }
                break;

            case "blez":
                if (RBank.R(rs) <= 0) {
                    PC += ((imm + 1) << 2);
                    return;
                }
                break;
            case "bltz":
//                System.out.println("BLTZ:" + rs);
                if (RBank.R(rs) < 0) {
                    PC += ((imm + 1) << 2);
                    return;
                }
                break;
            case "bne":
                if (RBank.R(rs) != RBank.R(rt)) {
                    PC += ((imm + 1) << 2);
                    return;
                }
                break;

//            case "lb":
//                if (RBank.R(rs) != RBank.R(rt)) {
//                    PC += imm << 2;
//                    return;
//                }
//            case "lbu":
//            case "lh":
//            case "lhu":
            case "lui":
                RBank.W(rt, imm);
                break;
            case "lw":
                Log(String.format("Load endereco '%d'\n", RBank.R(rs) + imm));
                RBank.W(rt, MD.R(RBank.R(rs) + imm));
                break;
//            case "ori":
//                break;
            case "slti":
//            case "sltiu":
            case "sw":
                Log(String.format("Store '%d' no endereco '%d'\n", RBank.R(rt), RBank.R(rs) + imm));
                MD.W(RBank.R(rs) + imm, RBank.R(rt));
                break;
//            case "xori":
            // J
            case "jal":
                RBank.W(31, PC);
            case "j":
                PC = imm << 2;
                return;
            default:
                throw new Exception("Instrução " + inst.G("inst") + " não definida!");
        }
        PC += 4;
    }
}

//    protected void InitInstructions2() {
//        HashMap<String, Integer> x = new HashMap<String, Integer>();
//        //Constantes da instruções (Codop e Funct)
//        x.put("FUNCT_ADD", 32);
//        x.put("FUNCT_ADDU", 33);
//        x.put("FUNCT_SUB", 34);
//        x.put("FUNCT_SUBU", 35);
//        x.put("FUNCT_MULT", 24);
//        x.put("FUNCT_MULTU", 25);
//        x.put("FUNCT_AND", 36);
//        x.put("FUNCT_OR", 37);
//        x.put("FUNCT_XOR", 38);
//        x.put("FUNCT_NOR", 39);
//        x.put("FUNCT_DIV", 26);
//        x.put("FUNCT_DIVU", 27);
//        x.put("FUNCT_SLT", 42);
//
//        x.put("FUNCT_SRL", 2);
//        x.put("FUNCT_SLL", 0);
//        x.put("FUNCT_SRA", 3);
//        x.put("FUNCT_SRLV", 6);
//        x.put("FUNCT_SLLV", 4);
//        x.put("FUNCT_SRAV", 7);
//
//        x.put("FUNCT_MFHI", 16);
//        x.put("FUNCT_MFLO", 18);
//        x.put("FUNCT_JR", 8);
//
//        x.put("FUNCT_NOP", 63);
//        x.put("NOP", 63);
//
////Constante dos CODOPs
//        x.put("CODOP_ADDI", 8);
//        x.put("CODOP_ADDIU", 9);
//        x.put("CODOP_SLTI", 10);
//        x.put("CODOP_SLTIU", 11);
//        x.put("CODOP_ANDI", 12);
//        x.put("CODOP_ORI", 13);
//        x.put("CODOP_LUI", 15);
//
//        x.put("CODOP_LB", 32);
//        x.put("CODOP_LH", 33);
//        x.put("CODOP_LW", 35);
//        x.put("CODOP_LBU", 36);
//        x.put("CODOP_LHU", 37);
//        x.put("CODOP_SB", 40);
//        x.put("CODOP_SH", 41);
//        x.put("CODOP_SW", 43);
//
//        x.put("CODOP_BGEZ", 1);
//        x.put("CODOP_BEQ", 4);
//        x.put("CODOP_BNE", 5);
//        x.put("CODOP_BGTZ", 7);
//
//        x.put("CODOP_JUMP", 2);
//        x.put("CODOP_JAL", 3);
//
//        x.put("CODOP_STOP", 63);
////Constante das operações da ULA
//        x.put("SOMA", 1);
//        x.put("SUBTRACAO", 2);
//        x.put("MULTIPLICACAO", 3);
//        x.put("AND", 4);
//        x.put("OR", 5);
//        x.put("XOR", 6);
//        x.put("NOR", 7);
//        x.put("DIVISAO", 8);
//        x.put("SLT", 9);
//        x.put("SLTU", 10);
//        x.put("LUI", 11);
//
//        x.put("SRL", 12);  //Faz o shift usando o campo shamt
//        x.put("SLL", 13); //Faz o shift usando o campo shamt
//        x.put("SRA", 14); //Faz o shift usando o campo shamt
//
//        x.put("SRLV", 15); //Faz o shift usando registrador
//        x.put("SLLV", 16); //Faz o shift usando registrador
//        x.put("SRAV", 17); //Faz o shift usando registrador
//        x.put("MOVE", 18);
//
//        for (Map.Entry<String, Integer> entry : x.entrySet()) {
//            String string = entry.getKey();
//            System.out.println(string + ":" + Utils.pad(6, Integer.toBinaryString(entry.getValue())));
//        }
//    }
//CODOP_JUMP:000010
//CODOP_LBU:100100
//NOP:111111
//CODOP_STOP:111111
//FUNCT_DIVU:011011
//CODOP_LH:100001
//MULTIPLICACAO:000011
//SRLV:001111
//NOR:000111
//SLL:001101
//FUNCT_SRA:000011
//CODOP_ANDI:001100
//AND:000100
//FUNCT_SRL:000010
//CODOP_ORI:001101
//SRAV:010001
//SLT:001001
//SUBTRACAO:000010
//CODOP_LW:100011
//FUNCT_ADD:100000
//SOMA:000001
//FUNCT_MULTU:011001
//CODOP_SLTI:001010
//SLLV:010000
//SLTU:001010
//FUNCT_MULT:011000
//CODOP_ADDIU:001001
//LUI:001011
//CODOP_SH:101001
//XOR:000110
//FUNCT_MFLO:010010
//FUNCT_SRAV:000111
//DIVISAO:001000
//SRL:001100
//SRA:001110
//CODOP_SB:101000
//CODOP_ADDI:001000
//FUNCT_JR:001000
//FUNCT_MFHI:010000
//FUNCT_NOR:100111
//FUNCT_NOP:111111
//FUNCT_SUBU:100011
//FUNCT_OR:100101
//FUNCT_AND:100100
//FUNCT_XOR:100110
//FUNCT_DIV:011010
//FUNCT_SRLV:000110
//FUNCT_ADDU:100001
//CODOP_LUI:001111
//CODOP_BGEZ:000001
//MOVE:010010
//CODOP_SLTIU:001011
//CODOP_JAL:000011
//OR:000101
//CODOP_SW:101011
//FUNCT_SUB:100010
//FUNCT_SLLV:000100
//CODOP_LB:100000
//CODOP_BNE:000101
//FUNCT_SLL:000000
//CODOP_LHU:100101
//CODOP_BGTZ:000111
//CODOP_BEQ:000100
//FUNCT_SLT:101010

/*

 1652044928
 1100010011110000011010010000000
 1100010011110000011010010000000
 011000 10011110000011010010000000
 011000
 596080229

 00100011100001110111011001100101 - js
 100011100001110111011001100101
 
 c
 10010110000010000000001000101001 

 */
//1652044928 -> 01010010000001000100100100101000 ->

//0:  0062783480	addiu sp,sp,-8	
