#include <core/Interpreter.hpp>
#include <log.hpp>
#include <CpuDefinitions.hpp>

namespace n64 {
void Interpreter::special(u32 instr) {
  u8 mask = (instr & 0x3F);
  // 00rr_rccc
  switch (mask) { // TODO: named constants for clearer code
    case SLL:
      if (instr != 0) {
        sll(instr);
      }
      break;
    case SRL: srl(instr); break;
    case SRA: sra(instr); break;
    case SLLV: sllv(instr); break;
    case SRLV: srlv(instr); break;
    case SRAV: srav(instr); break;
    case JR: jr(instr); break;
    case JALR: jalr(instr); break;
    case SYSCALL: FireException(regs, ExceptionCode::Syscall, 0, regs.oldPC); break;
    case BREAK: FireException(regs, ExceptionCode::Breakpoint, 0, regs.oldPC); break;
    case SYNC: break; // SYNC
    case MFHI: mfhi(instr); break;
    case MTHI: mthi(instr); break;
    case MFLO: mflo(instr); break;
    case MTLO: mtlo(instr); break;
    case DSLLV: dsllv(instr); break;
    case DSRLV: dsrlv(instr); break;
    case DSRAV: dsrav(instr); break;
    case MULT: mult(instr); break;
    case MULTU: multu(instr); break;
    case DIV: div(instr); break;
    case DIVU: divu(instr); break;
    case DMULT: dmult(instr); break;
    case DMULTU: dmultu(instr); break;
    case DDIV: ddiv(instr); break;
    case DDIVU: ddivu(instr); break;
    case ADD: add(instr); break;
    case ADDU: addu(instr); break;
    case SUB: sub(instr); break;
    case SUBU: subu(instr); break;
    case AND: and_(instr); break;
    case OR: or_(instr); break;
    case XOR: xor_(instr); break;
    case NOR: nor(instr); break;
    case SLT: slt(instr); break;
    case SLTU: sltu(instr); break;
    case DADD: dadd(instr); break;
    case DADDU: daddu(instr); break;
    case DSUB: dsub(instr); break;
    case DSUBU: dsubu(instr); break;
    case TGE: trap(regs.gpr[RS(instr)] >= regs.gpr[RT(instr)]); break;
    case TGEU: trap((u64)regs.gpr[RS(instr)] >= (u64)regs.gpr[RT(instr)]); break;
    case TLT: trap(regs.gpr[RS(instr)] < regs.gpr[RT(instr)]); break;
    case TLTU: trap((u64)regs.gpr[RS(instr)] < (u64)regs.gpr[RT(instr)]); break;
    case TEQ: trap(regs.gpr[RS(instr)] == regs.gpr[RT(instr)]); break;
    case TNE: trap(regs.gpr[RS(instr)] != regs.gpr[RT(instr)]); break;
    case DSLL: dsll(instr); break;
    case DSRL: dsrl(instr); break;
    case DSRA: dsra(instr); break;
    case DSLL32: dsll32(instr); break;
    case DSRL32: dsrl32(instr); break;
    case DSRA32: dsra32(instr); break;
    default:
      Util::panic("Unimplemented special {} {} ({:08X}) (pc: {:016X})", (mask >> 3) & 7, mask & 7, instr, (u64)regs.oldPC);
  }
}

void Interpreter::regimm(u32 instr) {
  u8 mask = ((instr >> 16) & 0x1F);
  // 000r_rccc
  switch (mask) { // TODO: named constants for clearer code
    case BLTZ: b(instr, regs.gpr[RS(instr)] < 0); break;
    case BGEZ: b(instr, regs.gpr[RS(instr)] >= 0); break;
    case BLTZL: bl(instr, regs.gpr[RS(instr)] < 0); break;
    case BGEZL: bl(instr, regs.gpr[RS(instr)] >= 0); break;
    case TGEI: trap(regs.gpr[RS(instr)] >= s64(s16(instr))); break;
    case TGEIU: trap(u64(regs.gpr[RS(instr)]) >= u64(s64(s16(instr)))); break;
    case TLTI: trap(regs.gpr[RS(instr)] < s64(s16(instr))); break;
    case TLTIU: trap(u64(regs.gpr[RS(instr)]) < u64(s64(s16(instr)))); break;
    case TEQI: trap(regs.gpr[RS(instr)] == s64(s16(instr))); break;
    case TNEI: trap(regs.gpr[RS(instr)] != s64(s16(instr))); break;
    case BLTZAL: blink(instr, regs.gpr[RS(instr)] < 0); break;
    case BGEZAL: blink(instr, regs.gpr[RS(instr)] >= 0); break;
    case BLTZALL: bllink(instr, regs.gpr[RS(instr)] < 0); break;
    case BGEZALL: bllink(instr, regs.gpr[RS(instr)] >= 0); break;
    default:
      Util::panic("Unimplemented regimm {} {} ({:08X}) (pc: {:016X})", (mask >> 3) & 3, mask & 7, instr, (u64)regs.oldPC);
  }
}

void Interpreter::cop2Decode(u32 instr) {
  if(!regs.cop0.status.cu2) {
    FireException(regs, ExceptionCode::CoprocessorUnusable, 2, regs.oldPC);
    return;
  }
  switch(RS(instr)) {
    case 0x00: mfc2(instr); break;
    case 0x01: dmfc2(instr); break;
    case 0x02: cfc2(instr); break;
    case 0x04: mtc2(instr); break;
    case 0x05: dmtc2(instr); break;
    case 0x06: ctc2(instr); break;
    default:
      FireException(regs, ExceptionCode::ReservedInstruction, 2, regs.oldPC);
  }
}

void Interpreter::Exec(u32 instr) {
  u8 mask = (instr >> 26) & 0x3f;
  // 00rr_rccc
  switch(mask) { // TODO: named constants for clearer code
    case SPECIAL: special(instr); break;
    case REGIMM: regimm(instr); break;
    case J: j(instr); break;
    case JAL: jal(instr); break;
    case BEQ: b(instr, regs.gpr[RS(instr)] == regs.gpr[RT(instr)]); break;
    case BNE: {
      //fmt::print("RS: {:016X}, RT: {:016X}", (u64)regs.gpr[RS(instr)], (u64)regs.gpr[RT(instr)]);
      b(instr, regs.gpr[RS(instr)] != regs.gpr[RT(instr)]);
    } break;
    case BLEZ: b(instr, regs.gpr[RS(instr)] <= 0); break;
    case BGTZ: b(instr, regs.gpr[RS(instr)] > 0); break;
    case ADDI: addi(instr); break;
    case ADDIU: addiu(instr); break;
    case SLTI: slti(instr); break;
    case SLTIU: sltiu(instr); break;
    case ANDI: andi(instr); break;
    case ORI: ori(instr); break;
    case XORI: xori(instr); break;
    case LUI: lui(instr); break;
    case COP0: regs.cop0.decode(*this, instr); break;
    case COP1: regs.cop1.decode(*this, instr); break;
    case COP2: cop2Decode(instr); break;
    case BEQL: bl(instr, regs.gpr[RS(instr)] == regs.gpr[RT(instr)]); break;
    case BNEL: bl(instr, regs.gpr[RS(instr)] != regs.gpr[RT(instr)]); break;
    case BLEZL: bl(instr, regs.gpr[RS(instr)] <= 0); break;
    case BGTZL: bl(instr, regs.gpr[RS(instr)] > 0); break;
    case DADDI: daddi(instr); break;
    case DADDIU: daddiu(instr); break;
    case LDL: ldl(instr); break;
    case LDR: ldr(instr); break;
    case 0x1F: FireException(regs, ExceptionCode::ReservedInstruction, 0, regs.oldPC); break;
    case LB: lb(instr); break;
    case LH: lh(instr); break;
    case LWL: lwl(instr); break;
    case LW: lw(instr); break;
    case LBU: lbu(instr); break;
    case LHU: lhu(instr); break;
    case LWR: lwr(instr); break;
    case LWU: lwu(instr); break;
    case SB: sb(instr); break;
    case SH: sh(instr); break;
    case SWL: swl(instr); break;
    case SW: sw(instr); break;
    case SDL: sdl(instr); break;
    case SDR: sdr(instr); break;
    case SWR: swr(instr); break;
    case CACHE: break; // CACHE
    case LL: ll(instr); break;
    case LWC1: regs.cop1.lwc1(*this, mem, instr); break;
    case LLD: lld(instr); break;
    case LDC1: regs.cop1.ldc1(*this, mem, instr); break;
    case LD: ld(instr); break;
    case SC: sc(instr); break;
    case SWC1: regs.cop1.swc1(*this, mem, instr); break;
    case SCD: scd(instr); break;
    case SDC1: regs.cop1.sdc1(*this, mem, instr); break;
    case SD: sd(instr); break;
    default:
      Util::panic("Unimplemented instruction {:02X} ({:08X}) (pc: {:016X})", mask, instr, (u64)regs.oldPC);
  }
}
}