#![allow(dead_code)]
use super::decode::{Instruction};
use super::global::*;

const NAMES_OP: [&str; 8] = ["add", "sll", "slt", "sltu", "xor", "sr", "or", "and"];
const NAMES_OPI: [&str; 8]  = ["addi", "slli", "slti", "sltiu", "xori", "slri", "ori", "andi"];
const NAMES_MUL: [&str; 8] = ["mul", "mulh", "mulhsu", "mulhu", "div", "divu", "rem", "remu"];
const NAMES_BR: [&str; 8] = ["beq", "bne", "", "", "blt", "bge", "bltu", "bgeu"];
const NAMES_ST: [&str; 4] = ["sb", "sh", "sw", "sd"];
const NAMES_LD: [&str; 7] = ["lb", "lh", "lw", "ld", "lbu", "lhu", "lwu"];
const NAMES_OPW: [&str; 8]  = ["addw", "sllw", "", "", "", "srw", "", ""];
const NAMES_OPIW: [&str; 8] = ["addiw", "slliw", "", "", "", "sri", "", ""];


// Register name mapping
pub const REG_X: [&str; 32] = ["zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1",
"a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
"s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11",
"t3", "t4", "t5", "t6"];

const REG_F: [&str; 32] = ["ft0", "ft1", "ft2", "ft3", "ft4", "ft5", "ft6", "ft7", "fs0", "fs1",
"fa0", "fa1", "fa2", "fa3", "fa4", "fa5", "fa6", "fa7",
"fs2", "fs3", "fs4", "fs5", "fs6", "fs7", "fs8", "fs9", "fs10", "fs11",
"ft8", "ft9", "ft10", "ft11"];


pub fn print(ins: Instruction, pc: i32) -> String { //TODO pc should be u64
    let rd = ins.rd as usize;
    let rs1 = ins.rs1 as usize;
    let rs2 = ins.rs2 as usize;
    let rs3 = ins.rs3 as usize;

    match ins.opcode {
        RISCV_OP => {
            let name: &str;
            if ins.funct7 == 1 { // Use mul array
                name = NAMES_MUL[ins.funct3 as usize]
            } else if ins.funct3 == RISCV_OP_ADD {
                // Add or Sub
                if ins.funct7 == RISCV_OP_ADD_ADD {
                    name = "add";
                } else {
                    name = "sub";
                }
            } else if ins.funct3 == RISCV_OP_SR {
                // Srl or Sra
                if ins.funct7 == RISCV_OP_SR_SRL {
                    name = "srl";
                } else {
                    name = "sra";
                }
            } else {
                name = NAMES_OP[ins.funct3 as usize];
            }
            format!("{}\t{},{},{}", name, REG_X[rd], REG_X[rs1], REG_X[rs2])
        },
        RISCV_OPI => {
            // SHAMT OR IMM
            if ins.funct3 == RISCV_OPI_SRI {
                if ins.funct7 == RISCV_OPI_SRI_SRLI {
                    format!("srli\t{},{},{}", REG_X[rd], REG_X[rs1], ins.shamt)
                } else {
                    format!("srai\t{},{},{}", REG_X[rd], REG_X[rs1], ins.shamt)
                }
            } else if ins.funct3 == RISCV_OPI_SLLI {
                format!("{}\t{},{},{}", NAMES_OPI[ins.funct3 as usize], REG_X[rd], REG_X[rs1], ins.shamt)
            } else {
                format!("{}\t{},{},{}", NAMES_OPI[ins.funct3 as usize], REG_X[rd], REG_X[rs1], ins.imm12_I_signed)
            }
        },
        RISCV_LUI => {
            format!("lui\t{},{:x}", REG_X[rd], ins.imm31_12)
        },
        RISCV_AUIPC => {
            format!("auipc\t{},{:x}", REG_X[rd], ins.imm31_12)
        },
        RISCV_JAL => {
            format!("jal\t{},{:x}", REG_X[rd], (pc + ins.imm21_1_signed)) 
        },
        RISCV_JALR => {
            format!("jalr\t{},{:x}({})", REG_X[rd], ins.imm12_I_signed, REG_X[rs1])
        },
        RISCV_BR => {
            format!("{}\t{},{},{:x}", NAMES_BR[ins.funct3 as usize], REG_X[rs1], REG_X[rs2], pc + (ins.imm13_signed as i32))
        },
        RISCV_LD => {
            format!("{}\t{},{}({})", NAMES_LD[ins.funct3 as usize], REG_X[rd], ins.imm12_I_signed, REG_X[rs1])
        },
        RISCV_ST => {
            format!("{}\t{},{}({})", NAMES_ST[ins.funct3 as usize], REG_X[rs2], ins.imm12_S_signed, REG_X[rs1])
        },
        RISCV_OPIW => {
            if ins.funct3 == RISCV_OPIW_SRW {
                if ins.funct7 == RISCV_OPIW_SRW_SRLIW {
                    format!("srliw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2])
                } else {
                    format!("sraiw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2])
                }
            } else {
                format!("{}\t{},{},0x{:x}", NAMES_OPIW[ins.funct3 as usize], REG_X[rd], REG_X[rs1], ins.imm12_I_signed)
            }
        },
        RISCV_OPW => {
            if ins.funct7 == 1 {
                format!("{}w\t{},{},{}", NAMES_MUL[ins.funct3 as usize], REG_X[rd], REG_X[rs1], REG_X[rs2])
            } else if ins.funct3 == RISCV_OP_ADD {
                if ins.funct7 == RISCV_OPW_ADDSUBW_ADDW {
                    format!("addw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2])
                } else {
                    format!("subw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2])
                }
            } else if ins.funct3 == RISCV_OPW_SRW {
                if ins.funct7 == RISCV_OPW_SRW_SRLW {
                    format!("srlw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2])
                } else {
                    format!("sraw\t{},{},{}", REG_X[rd], REG_X[rs1], REG_X[rs2])
                }
            } else {
                    format!("{}\t{},{},{}", NAMES_OPW[ins.funct3 as usize], REG_X[rd], REG_X[rs1], REG_X[rs2])
            }
        },
        // RV32F Standard Extension
        RISCV_FLW => {
            format!("flw\t{},{},({})", REG_F[rd], ins.imm12_I_signed,  REG_F[rs1])
        },
        RISCV_FSW => {
            format!("fsw\t{},{},({})", REG_F[rs2], "OFFSET TODO", REG_F[rs1]) // TODO Offset in decode
        },
        RISCV_FMADD => {
            format!("fmadd\t{}{}{}{}", REG_F[rd], REG_F[rs1], REG_F[rs2], REG_F[rs3])
        },
        RISCV_FMSUB => {
            format!("fmsub\t{}{}{}{}", REG_F[rd], REG_F[rs1], REG_F[rs2], REG_F[rs3])
        },
        RISCV_FNMSUB => {
            format!("fnmsub\t{}{}{}{}", REG_F[rd], REG_F[rs1], REG_F[rs2], REG_F[rs3])
        },
        RISCV_FNMADD => {
            format!("fnmadd\t{}{}{}{}", REG_F[rd], REG_F[rs1], REG_F[rs2], REG_F[rs3])
        },
        RISCV_FP => {
            match ins.funct7 {
                RISCV_FP_ADD => {
                    format!("{}\t{}{}{}", "fadd", REG_F[rd], REG_F[rs1], REG_F[rs2])
                },
                RISCV_FP_SUB => {
                    format!("{}\t{}{}{}", "fsub.s", REG_F[rd], REG_F[rs1], REG_F[rs2])
                },
                RISCV_FP_MUL => {
                    format!("{}\t{}{}{}", "fmul.s", REG_F[rd], REG_F[rs1], REG_F[rs2])
                },
                RISCV_FP_DIV => {
                    format!("{}\t{}{}{}", "fdiv.s", REG_F[rd], REG_F[rs1], REG_F[rs2])
                },
                RISCV_FP_SQRT => {
                    format!("{}\t{}{}", "fsqrt.s", REG_F[rd], REG_F[rs1])
                },
                RISCV_FP_FSGN => {
                    match ins.funct3 {
                        RISCV_FP_FSGN_J => {
                            format!("{}\t{}{}{}", "fsgnj.s", REG_F[rd], REG_F[rs1], REG_F[rs2])
                        },
                        RISCV_FP_FSGN_JN => {
                            format!("{}\t{}{}{}", "fsgnn.s", REG_F[rd], REG_F[rs1], REG_F[rs2])
                        },
                        RISCV_FP_FSGN_JX => {
                            format!("{}\t{}{}{}", "fsgnx.s", REG_F[rd], REG_F[rs1], REG_F[rs2])
                        },
                        _ => todo!("Unknown code")
                    }
                },
                RISCV_FP_MINMAX => {
                    if ins.funct3 == 0 {
                        format!("{}\t{}{}{}", "fmin.s", REG_F[rd], REG_F[rs1], REG_F[rs2])
                    } else {
                        format!("{}\t{}{}{}", "fmax.s", REG_F[rd], REG_F[rs1], REG_F[rs2])
                    }
                },
                RISCV_FP_FCVTW => {
                    if rs2 == 0 {
                        format!("{}\t{}{}", "fcvt.w.s", REG_F[rd], REG_F[rs1])
                    } else {
                        format!("{}\t{}{}", "fcvt.wu.s", REG_F[rd], REG_F[rs1])
                    }
                },
                RISCV_FP_FMVXFCLASS => {
                    if ins.funct3 == 0 {
                        format!("{}\t{}{}", "fmv.x.w", REG_F[rd], REG_F[rs1])
                    } else {
                        format!("{}\t{}{}", "fclass.s", REG_F[rd], REG_F[rs1])
                    }
                },
                RISCV_FP_FCMP => {
                    if ins.funct3 == 0 {
                        format!("{}\t{}{}{}", "fle.s", REG_F[rd], REG_F[rs1], REG_F[rs2])
                    } else if ins.funct3 == 1 {
                        format!("{}\t{}{}{}", "flt.s", REG_F[rd], REG_F[rs1], REG_F[rs2])
                    } else {
                        format!("{}\t{}{}{}", "feq.s", REG_F[rd], REG_F[rs1], REG_F[rs2])
                    }
                },
                RISCV_FP_FCVTS => {
                    if rs2 == 0 {
                        format!("{}\t{}{}", "fcvt.s.w", REG_F[rd], REG_F[rs1])
                    } else {
                        format!("{}\t{}{}", "fcvt.s.wu", REG_F[rd], REG_F[rs1])
                    }
                },
                RISCV_FP_FMVW => {
                    format!("{}\t{}{}", "fmv.w.x", REG_F[rd], REG_F[rs1])
                },
                _ => todo!("Unknown code")
            }
        },
        
        RISCV_SYSTEM => {
            "ecall".to_string()
        },
        _ => todo!("{:x} opcode non géré  pc : {:x}, value : {:x}", ins.opcode, pc, ins.value) // Change todo! to panic! in the future, I put todo! because there's a lot of opcode currently not implemented
    }

}


#[cfg(test)]
mod test {
    #![allow(clippy::unusual_byte_groupings)]

    use crate::simulator::{decode, print};


    #[test]
    fn test_op() {
        let sub = decode::decode(0b0100000_10000_10001_000_11100_0110011);
        let add = decode::decode(0b0000000_10000_10001_000_11100_0110011);
        let xor = decode::decode(0b0000000_10000_10001_100_11100_0110011);
        let slr = decode::decode(0b0000000_10000_10001_101_11100_0110011);
        let sra = decode::decode(0b0100000_10000_10001_101_11100_0110011);

        assert_eq!("sub\tt3,a7,a6", print::print(sub, 0));
        assert_eq!("xor\tt3,a7,a6", print::print(xor, 0));
        assert_eq!("srl\tt3,a7,a6", print::print(slr, 0));
        assert_eq!("sra\tt3,a7,a6", print::print(sra, 0));
        assert_eq!("add\tt3,a7,a6", print::print(add, 0));

    }

    #[test]
    fn test_opi() {
        let addi = decode::decode(0b0000000000_10001_000_11100_0010011);
        let slli = decode::decode(0b0000000000_10001_001_11100_0010011);
        let slti = decode::decode(0b0000000000_10001_010_11100_0010011);
        let sltiu = decode::decode(0b0000000000_10001_011_11100_0010011);
        let xori = decode::decode(0b_0000000000010001_100_11100_0010011);
        let ori = decode::decode(0b00000000000_10001_110_11100_0010011);
        let andi = decode::decode(0b000000000000_10001_111_11100_0010011);
        assert_eq!("andi\tt3,a7,0", print::print(andi, 0));
        assert_eq!("addi\tt3,a7,0", print::print(addi, 0));
        assert_eq!("slli\tt3,a7,0", print::print(slli, 0));
        assert_eq!("slti\tt3,a7,0", print::print(slti, 0));
        assert_eq!("sltiu\tt3,a7,0", print::print(sltiu, 0));
        assert_eq!("xori\tt3,a7,0", print::print(xori, 0));
        assert_eq!("ori\tt3,a7,0", print::print(ori, 0));
    }

    #[test]
    fn test_lui() {
        let lui = decode::decode(0b01110001000011111000_11100_0110111);
        let lui_negatif = decode::decode(0b11110001000011111000_11100_0110111);
        assert_eq!("lui\tt3,710f8000", print::print(lui, 0));
        assert_eq!("lui\tt3,f10f8000", print::print(lui_negatif, 0));
    }

    #[test]
    fn test_ld() {
        //                      imm            rs1   f3    rd    opcode
        let lb = decode::decode(0b010111110000_10001_000_11100_0000011);
        let lh = decode::decode(0b010111110000_10001_001_11100_0000011);
        let lw = decode::decode(0b010111110000_10001_010_11100_0000011);
        let lbu = decode::decode(0b010111110000_10001_100_11100_0000011);
        let lhu = decode::decode(0b010111110000_10001_101_11100_0000011);
        let ld = decode::decode(0b010111110000_10001_011_11100_0000011);
        let lwu = decode::decode(0b010111110000_10001_110_11100_0000011);

        assert_eq!("lb\tt3,1520(a7)", print::print(lb, 0));
        assert_eq!("lh\tt3,1520(a7)", print::print(lh, 0));
        assert_eq!("lw\tt3,1520(a7)", print::print(lw, 0));
        assert_eq!("lbu\tt3,1520(a7)", print::print(lbu, 0));
        assert_eq!("lhu\tt3,1520(a7)", print::print(lhu, 0));
        assert_eq!("ld\tt3,1520(a7)", print::print(ld, 0));
        assert_eq!("lwu\tt3,1520(a7)", print::print(lwu, 0));
    }

    #[test]
    fn test_opw() {                                         
        let addw: decode::Instruction = decode::decode(0b0000000_10000_10001_000_11100_0111011);
        let sllw: decode::Instruction = decode::decode(0b0000000_10000_10001_001_11100_0111011);
        let srlw: decode::Instruction = decode::decode(0b0000000_10000_10001_101_11100_0111011);
        let sraw: decode::Instruction = decode::decode(0b0100000_10000_10001_101_11100_0111011);

        assert_eq!("addw\tt3,a7,a6", print::print(addw, 0));
        assert_eq!("sllw\tt3,a7,a6", print::print(sllw, 0));
        assert_eq!("srlw\tt3,a7,a6", print::print(srlw, 0));
        assert_eq!("sraw\tt3,a7,a6", print::print(sraw, 0));
    }

    #[test]
    fn test_opwi() {
        let addiw: decode::Instruction =decode::decode(0b000000000000_10001_000_11100_0011011);
        let slliw: decode::Instruction = decode::decode(0b0000000_10000_10001_001_11100_0011011);
        let srai: decode::Instruction = decode::decode(0b010000010001_10001_101_11100_0010011);
        assert_eq!("addiw\tt3,a7,0x0", print::print(addiw, 0));
        assert_eq!("slliw\tt3,a7,0x10", print::print(slliw, 0));
        assert_eq!("srai\tt3,a7,17", print::print(srai, 0));

    }

    #[test]
    fn test_br() {                                     
        let beq: decode::Instruction = decode::decode(0b0000000_10000_10001_000_00000_1100011);
        let bne: decode::Instruction = decode::decode(0b0000000_10000_10001_001_00000_1100011);
        let blt: decode::Instruction = decode::decode(0b0000000_10000_10001_100_00000_1100011);
        let bge: decode::Instruction = decode::decode(0b0000000_10000_10001_101_00000_1100011);
        let bge2: decode::Instruction = decode::decode(0x00f75863);
        let bltu: decode::Instruction = decode::decode(0b0000000_10000_10001_110_00000_1100011);
        let bgeu: decode::Instruction = decode::decode(0b0000000_10000_10001_111_00000_1100011);
        assert_eq!("blt\ta7,a6,0", print::print(blt, 0));
        assert_eq!("bge\ta7,a6,0", print::print(bge, 0));
        assert_eq!("bge\ta4,a5,104d4", print::print(bge2, 0x104c4));
        assert_eq!("bltu\ta7,a6,0", print::print(bltu, 0));
        assert_eq!("bgeu\ta7,a6,0", print::print(bgeu, 0));
        assert_eq!("bne\ta7,a6,0", print::print(bne, 0));
        assert_eq!("beq\ta7,a6,0", print::print(beq, 0));
    }

    #[test]
    fn test_small_program() {
        /* Code for :
            int a = 0;
            int b = 5;
            a = b;
            a = a * b;
            a = a + b;
            b = a - b;
         */
        assert_eq!("addi	sp,sp,-32", print::print(decode::decode(0xfe010113), 0));          	
        assert_eq!("sd	s0,24(sp)", print::print(decode::decode(0x00813c23), 0));          	
        assert_eq!("addi	s0,sp,32", print::print(decode::decode(0x02010413), 0));          	
        assert_eq!("sw	zero,-20(s0)", print::print(decode::decode(0xfe042623), 0));          	
        assert_eq!("addi	a5,zero,5", print::print(decode::decode(0x00500793), 0));          	
        assert_eq!("sw	a5,-24(s0)", print::print(decode::decode(0xfef42423), 0));          	
        assert_eq!("lw	a5,-24(s0)", print::print(decode::decode(0xfe842783), 0));          	
        assert_eq!("sw	a5,-20(s0)", print::print(decode::decode(0xfef42623), 0));
        assert_eq!("lw	a5,-20(s0)", print::print(decode::decode(0xfec42783), 0));          	
        assert_eq!("addi	a4,a5,0", print::print(decode::decode(0x00078713), 0));          	
        assert_eq!("lw	a5,-24(s0)", print::print(decode::decode(0xfe842783), 0));          	
        assert_eq!("mulw	a5,a4,a5", print::print(decode::decode(0x02f707bb), 0));
        assert_eq!("sw	a5,-20(s0)", print::print(decode::decode(0xfef42623), 0));          	
        assert_eq!("lw	a5,-20(s0)", print::print(decode::decode(0xfec42783), 0));          	
        assert_eq!("addi	a4,a5,0", print::print(decode::decode(0x00078713), 0));          	
        assert_eq!("lw	a5,-24(s0)", print::print(decode::decode(0xfe842783), 0));          	
        assert_eq!("addw	a5,a4,a5", print::print(decode::decode(0x00f707bb), 0));          	
        assert_eq!("sw	a5,-20(s0)", print::print(decode::decode(0xfef42623), 0));          	
        assert_eq!("lw	a5,-20(s0)", print::print(decode::decode(0xfec42783), 0));          	
        assert_eq!("addi	a4,a5,0", print::print(decode::decode(0x00078713), 0));          	
        assert_eq!("lw	a5,-24(s0)", print::print(decode::decode(0xfe842783), 0));          	
        assert_eq!("subw	a5,a4,a5", print::print(decode::decode(0x40f707bb), 0));          	
        assert_eq!("sw	a5,-24(s0)", print::print(decode::decode(0xfef42423), 0));          	
        assert_eq!("addi	a5,zero,0", print::print(decode::decode(0x00000793), 0));          	
        assert_eq!("addi	a0,a5,0", print::print(decode::decode(0x00078513), 0));          	
        assert_eq!("ld	s0,24(sp)", print::print(decode::decode(0x01813403), 0));          	
        assert_eq!("addi	sp,sp,32", print::print(decode::decode(0x02010113), 0));          	
        assert_eq!("jalr	zero,0(ra)", print::print(decode::decode(0x00008067), 0));          	

    }


    #[test]
    fn test_fibo() {
        assert_eq!("jal	zero,10504", print::print(decode::decode(0x0500006f), 0x104b4));
        assert_eq!("blt	a4,a5,104b8", print::print(decode::decode(0xfaf740e3), 0x10518));
    }

    #[test]
    fn test_mul_prog() {
        assert_eq!("addi	sp,sp,-32", print::print(decode::decode(0xfe010113), 0));             
        assert_eq!("sd	s0,24(sp)", print::print(decode::decode(0x00813c23), 0));
        assert_eq!("addi	s0,sp,32", print::print(decode::decode(0x02010413), 0));
        assert_eq!("addi	a5,zero,5", print::print(decode::decode(0x00500793), 0));
        assert_eq!("sw	a5,-20(s0)", print::print(decode::decode(0xfef42623), 0));
        assert_eq!("lw	a5,-20(s0)", print::print(decode::decode(0xfec42783), 0));
        assert_eq!("addi	a4,a5,0", print::print(decode::decode(0x00078713), 0));
        assert_eq!("addi	a5,a4,0", print::print(decode::decode(0x00070793), 0));
        assert_eq!("slliw	a5,a5,0x2", print::print(decode::decode(0x0027979b), 0));
        assert_eq!("addw	a5,a5,a4", print::print(decode::decode(0x00e787bb), 0));
        assert_eq!("sw	a5,-24(s0)", print::print(decode::decode(0xfef42423), 0));
        assert_eq!("lw	a5,-20(s0)", print::print(decode::decode(0xfec42783), 0));
        assert_eq!("addi	a4,a5,0", print::print(decode::decode(0x00078713), 0));
        assert_eq!("lw	a5,-24(s0)", print::print(decode::decode(0xfe842783), 0));
        assert_eq!("mulw	a5,a4,a5", print::print(decode::decode(0x02f707bb), 0));
        assert_eq!("sw	a5,-28(s0)", print::print(decode::decode(0xfef42223), 0));
        assert_eq!("lw	a5,-28(s0)", print::print(decode::decode(0xfe442783), 0));
        assert_eq!("addi	a4,a5,0", print::print(decode::decode(0x00078713), 0));
        assert_eq!("lw	a5,-24(s0)", print::print(decode::decode(0xfe842783), 0));
        assert_eq!("divw	a5,a4,a5", print::print(decode::decode(0x02f747bb), 0));
        assert_eq!("sw	a5,-20(s0)", print::print(decode::decode(0xfef42623), 0));
        assert_eq!("addi	a5,zero,0", print::print(decode::decode(0x00000793), 0));
        assert_eq!("addi	a0,a5,0", print::print(decode::decode(0x00078513), 0));
        assert_eq!("ld	s0,24(sp)", print::print(decode::decode(0x01813403), 0));
        assert_eq!("addi	sp,sp,32", print::print(decode::decode(0x02010113), 0));
        assert_eq!("jalr	zero,0(ra)", print::print(decode::decode(0x00008067), 0));
    }

}