from riscemu.colors import FMT_ERROR, FMT_NONE, FMT_BOLD, FMT_GREEN
from riscemu.instructions import InstructionSet
from riscemu.types import Instruction, CPU
from riscemu.decoder import RISCV_REGS

FMT_SUCCESS = FMT_GREEN + FMT_BOLD


def assert_equals(ins: Instruction, cpu: CPU):
    a, b = (get_arg_from_ins(ins, i, cpu) for i in (0, 2))
    return a == b


def assert_equals_mem(ins: Instruction, cpu: CPU):
    a, b = (get_arg_from_ins(ins, i, cpu) for i in (0, 2))
    a = cpu.mmu.read_int(a)
    return a == b


def assert_in(ins: Instruction, cpu: CPU):
    a = get_arg_from_ins(ins, 0, cpu)
    others = [get_arg_from_ins(ins, i, cpu) for i in range(2, len(ins.args))]
    return a in others


def _not(func):
    def test(ins: Instruction, cpu: CPU):
        return not func(ins, cpu)

    return test


def get_arg_from_ins(ins: Instruction, num: int, cpu: CPU):
    a = ins.args[num]
    if a in RISCV_REGS:
        return cpu.regs.get(a)
    return ins.get_imm(num)


assert_ops = {
    "==": assert_equals,
    "!=": _not(assert_equals),
    "in": assert_in,
    "not_in": _not(assert_in),
}


class Z_test(InstructionSet):
    def __init__(self, cpu: "CPU"):
        print(
            "[Test] loading testing ISA, this is only meant for running testcases and is not part of the RISC-V ISA!"
        )
        self.failed = False
        super().__init__(cpu)

    def instruction_assert(self, ins: Instruction):
        if len(ins.args) < 3:
            print(
                FMT_ERROR + "[Test] Unknown assert statement: {}".format(ins) + FMT_NONE
            )
            return
        op = ins.args[1]
        if op not in assert_ops:
            print(
                FMT_ERROR
                + "[Test] Unknown operation statement: {} in {}".format(op, ins)
                + FMT_NONE
            )
            return

        if assert_ops[op](ins, self.cpu):
            print(FMT_SUCCESS + "[TestCase] 🟢 passed assertion {}".format(ins))
        else:
            print(FMT_ERROR + "[TestCase] 🔴 failed assertion {}".format(ins))
            self.cpu.halted = True
            self.failed = True

    def instruction_fail(self, ins: Instruction):
        print(FMT_ERROR + "[TestCase] 🔴 reached fail instruction! {}".format(ins))
        self.cpu.halted = True
        self.failed = True
