from __future__ import annotations

from .decoder import is_ea_byte
from .model import Instruction


CYCLE_SOURCE = "manual Appendix A.4, tables A-7/A-8"
CYCLE_ASSUMPTION = "on-chip instruction fetch/operand access, no external wait states"


def annotate_cycles(instructions: dict[int, Instruction], mode: str) -> None:
    for instruction in instructions.values():
        instruction.cycles = estimate_cycles(instruction, mode)


def cycle_comment(cycles: dict[str, object] | None) -> str:
    if not cycles:
        return ""
    if "cycles" in cycles:
        return f"cycles={cycles['cycles']}"
    if "cycles_min" in cycles and "cycles_max" in cycles:
        return f"cycles={cycles['cycles_min']}-{cycles['cycles_max']}"
    if "not_taken" in cycles and "taken" in cycles:
        return f"cycles={cycles['not_taken']}/{cycles['taken']} nt/t"
    if "trap_not_taken" in cycles:
        return f"cycles={cycles['trap_not_taken']}/{cycles['trap_taken']} no-trap/trap"
    if "false" in cycles:
        return f"cycles={cycles['false']}/{cycles['count_minus_1']}/{cycles['taken']} false/-1/t"
    return ""


def estimate_cycles(instruction: Instruction, mode: str) -> dict[str, object] | None:
    if not instruction.valid:
        return None

    if instruction.raw and is_ea_byte(instruction.raw[0]):
        base = _estimate_general_cycles(instruction, mode)
    else:
        base = _estimate_direct_cycles(instruction, mode)
    if base is None:
        return None

    base.setdefault("source", CYCLE_SOURCE)
    base.setdefault("assumption", CYCLE_ASSUMPTION)
    return base


def _info(
    cycles: int,
    base_cycles: int | None = None,
    adjustment: int = 0,
    stack_adjustment: int = 0,
    note: str | None = None,
) -> dict[str, object]:
    info: dict[str, object] = {"cycles": cycles}
    if base_cycles is not None:
        info["base_cycles"] = base_cycles
    if adjustment:
        info["alignment_adjustment"] = adjustment
    if stack_adjustment:
        info["stack_adjustment"] = stack_adjustment
    if note:
        info["note"] = note
    return info


def _branch_info(not_taken: int, taken: int, instruction: Instruction) -> dict[str, object]:
    adjusted_taken = taken + _branch_adjust(instruction.address)
    info: dict[str, object] = {
        "not_taken": not_taken,
        "taken": adjusted_taken,
        "base_taken": taken,
    }
    if adjusted_taken != taken:
        info["alignment_adjustment_taken"] = adjusted_taken - taken
    return info


def _mode_min_max(mode: str, minimum: int, maximum: int) -> int:
    return maximum if mode == "max" else minimum


def _branch_adjust(address: int) -> int:
    # Table A-8(a): branch/system transfer adjustment is 0 at even starts, 1 at odd starts.
    return address & 1


def _ea_mode(raw: bytes) -> tuple[str, str] | None:
    if not raw:
        return None
    b = raw[0]
    if 0xA0 <= b <= 0xAF:
        return "reg", "W" if b & 0x08 else "B"
    if 0xB0 <= b <= 0xBF:
        return "predec", "W" if b & 0x08 else "B"
    if 0xC0 <= b <= 0xCF:
        return "postinc", "W" if b & 0x08 else "B"
    if 0xD0 <= b <= 0xDF:
        return "indirect", "W" if b & 0x08 else "B"
    if 0xE0 <= b <= 0xEF:
        return "disp8", "W" if b & 0x08 else "B"
    if 0xF0 <= b <= 0xFF:
        return "disp16", "W" if b & 0x08 else "B"
    if b == 0x04:
        return "imm", "B"
    if b == 0x0C:
        return "imm", "W"
    if b == 0x05:
        return "abs8", "B"
    if b == 0x0D:
        return "abs8", "W"
    if b == 0x15:
        return "abs16", "B"
    if b == 0x1D:
        return "abs16", "W"
    return None


def _a8_adjust(address: int, ea_mode: str, size: str, mov_imm_to_ea: bool = False) -> int:
    # Table A-8(b), simplified for decoded modes used by this tool.
    if mov_imm_to_ea:
        if size == "B":
            return 1
        even = {
            "reg": 2,
            "predec": 0,
            "postinc": 2,
            "indirect": 2,
            "disp8": 2,
            "disp16": 0,
            "abs8": 2,
            "abs16": 2,
        }
        odd = {
            "reg": 0,
            "predec": 2,
            "postinc": 0,
            "indirect": 0,
            "disp8": 0,
            "disp16": 2,
            "abs8": 0,
            "abs16": 0,
        }
        return (odd if address & 1 else even).get(ea_mode, 0)

    even_default = {
        "reg": 0,
        "predec": 1,
        "postinc": 0,
        "indirect": 1,
        "disp8": 1,
        "disp16": 1,
        "abs8": 0,
        "abs16": 1,
        "imm": 0,
    }
    odd_default = {
        "reg": 0,
        "predec": 0,
        "postinc": 1,
        "indirect": 0,
        "disp8": 0,
        "disp16": 0,
        "abs8": 1,
        "abs16": 0,
        "imm": 0,
    }
    return (odd_default if address & 1 else even_default).get(ea_mode, 0)


def _memory_read_base(ea_mode: str, size: str) -> int:
    if ea_mode == "reg":
        return 2 if size == "B" else 3
    if ea_mode == "imm":
        return 3 if size == "B" else 4
    if ea_mode in {"disp8", "disp16", "abs16"}:
        return 6
    return 5


def _memory_write_base(ea_mode: str, size: str) -> int:
    if ea_mode == "reg":
        return 2 if size == "B" else 3
    if ea_mode in {"disp8", "disp16", "abs16"}:
        return 6
    return 5


def _read_modify_write_base(ea_mode: str, size: str) -> int:
    if ea_mode == "reg":
        return 2 if size == "B" else 3
    if ea_mode in {"disp8", "disp16", "abs16"}:
        return 8
    return 7


def _imm_to_ea_base(ea_mode: str, size: str) -> int:
    if ea_mode == "reg":
        return 2 if size == "B" else 3
    if ea_mode in {"disp8", "disp16", "abs16"}:
        return 8 if size == "B" else 9
    return 7 if size == "B" else 8


def _estimate_general_cycles(instruction: Instruction, mode: str) -> dict[str, object] | None:
    parsed = _ea_mode(instruction.raw)
    if parsed is None or len(instruction.raw) < 2:
        return None
    ea_mode, ea_size = parsed
    mnemonic = instruction.mnemonic
    op = instruction.raw[1 + (1 if ea_mode in {"imm", "abs8"} else 2 if ea_mode == "abs16" else 0)]
    if ea_mode in {"disp8"}:
        op = instruction.raw[2]
    elif ea_mode == "disp16":
        op = instruction.raw[3]

    mov_imm_to_ea = mnemonic.startswith("MOV:G") and instruction.operands.startswith("#")
    adjustment = _a8_adjust(instruction.address, ea_mode, ea_size, mov_imm_to_ea)

    if mnemonic.startswith(("ADD:G", "ADDS", "SUB", "SUBS", "ADDX", "SUBX", "AND.", "OR.", "XOR.", "CMP:G")):
        base = _memory_read_base(ea_mode, ea_size)
    elif mnemonic.startswith("MOV:G"):
        base = _imm_to_ea_base(ea_mode, ea_size) if mov_imm_to_ea else _memory_write_base(ea_mode, ea_size)
    elif mnemonic.startswith(("BTST", "TST")):
        base = _memory_read_base(ea_mode, ea_size)
    elif mnemonic.startswith(("BSET", "BCLR", "BNOT", "CLR", "NEG", "NOT", "SHAL", "SHAR", "SHLL", "SHLR", "ROTL", "ROTR", "ROTXL", "ROTXR", "TAS")):
        base = _read_modify_write_base(ea_mode, ea_size)
    elif mnemonic.startswith("ADD:Q"):
        base = 4 if ea_mode == "reg" else _read_modify_write_base(ea_mode, ea_size)
    elif mnemonic.startswith("MULXU.B"):
        base = 18 if ea_mode == "reg" else 19
    elif mnemonic.startswith("MULXU.W"):
        base = 25
    elif mnemonic.startswith("DIVXU.B"):
        base = 21 if ea_mode == "reg" else 23
    elif mnemonic.startswith("DIVXU.W"):
        base = 28 if ea_mode == "reg" else 29
    elif mnemonic.startswith(("LDC.B", "STC.B")):
        base = 4 if ea_mode in {"reg", "imm"} else 6 if ea_mode in {"abs8", "indirect", "predec", "postinc"} else 7
    elif mnemonic.startswith(("LDC.W", "STC.W")):
        base = 6 if ea_mode in {"reg", "imm"} else 7 if ea_mode in {"abs8", "indirect", "predec", "postinc"} else 8
    elif mnemonic.startswith(("ORC", "ANDC", "XORC")):
        base = 4
        adjustment = 0
    elif mnemonic.startswith(("EXTS", "EXTU", "SWAP")):
        base = 3
        adjustment = 0
    elif mnemonic.startswith(("MOVFPE", "MOVTPE")):
        cycles = _mode_min_max(mode, 13, 20)
        return _info(cycles, note="E-clock peripheral transfer")
    else:
        return None

    return _info(base + adjustment, base, adjustment)


def _estimate_direct_cycles(instruction: Instruction, mode: str) -> dict[str, object] | None:
    raw = instruction.raw
    mnemonic = instruction.mnemonic
    if not raw:
        return None
    op = raw[0]

    if mnemonic.startswith("B") and 0x20 <= op <= 0x3F:
        info = _branch_info(3, 7, instruction)
        if mnemonic == "BRA":
            info["cycles"] = info["taken"]
        elif mnemonic == "BRN":
            info["cycles"] = info["not_taken"]
        return info
    if mnemonic.startswith("SCB/"):
        taken = 8 + _branch_adjust(instruction.address)
        return {
            "false": 3,
            "count_minus_1": 4,
            "taken": taken,
            "base_taken": 8,
        }
    if mnemonic == "BSR":
        base = 9
        adjust = _branch_adjust(instruction.address)
        stack = 4
        return _info(base + stack + adjust, base, adjust, stack, "PC word push to stack")
    if mnemonic == "JMP":
        if op == 0x10:
            base = 7
        elif len(raw) >= 2 and raw[0] == 0x11 and 0xD0 <= raw[1] <= 0xD7:
            base = 6
        elif len(raw) >= 2 and raw[0] == 0x11 and 0xE0 <= raw[1] <= 0xE7:
            base = 7
        elif len(raw) >= 2 and raw[0] == 0x11 and 0xF0 <= raw[1] <= 0xF7:
            base = 8
        else:
            return None
        adjust = _branch_adjust(instruction.address)
        return _info(base + adjust, base, adjust)
    if mnemonic == "JSR":
        if op == 0x18:
            base = 9
        elif len(raw) >= 2 and raw[0] == 0x11 and 0xD8 <= raw[1] <= 0xDF:
            base = 9
        elif len(raw) >= 2 and raw[0] == 0x11 and 0xE8 <= raw[1] <= 0xEF:
            base = 9
        elif len(raw) >= 2 and raw[0] == 0x11 and 0xF8 <= raw[1] <= 0xFF:
            base = 10
        else:
            return None
        adjust = _branch_adjust(instruction.address)
        stack = 4
        return _info(base + stack + adjust, base, adjust, stack, "PC word push to stack")
    if mnemonic == "PJMP":
        base = 9 if op == 0x13 else 8
        adjust = _branch_adjust(instruction.address)
        return _info(base + adjust, base, adjust)
    if mnemonic == "PJSR":
        base = 15 if op == 0x03 else 13
        adjust = _branch_adjust(instruction.address)
        stack = 6 if mode == "max" else 4
        return _info(base + stack + adjust, base, adjust, stack, "return address push to stack")
    if mnemonic == "TRAPA":
        return _info(_mode_min_max(mode, 17, 22))
    if mnemonic == "TRAP/VS":
        return {
            "trap_not_taken": 3,
            "trap_taken": _mode_min_max(mode, 18, 23) + _branch_adjust(instruction.address),
            "base_trap_taken": _mode_min_max(mode, 18, 23),
        }
    if mnemonic == "RTE":
        base = _mode_min_max(mode, 13, 15)
        adjust = _branch_adjust(instruction.address)
        return _info(base + adjust, base, adjust)
    if mnemonic == "RTS":
        base = 8
        adjust = _branch_adjust(instruction.address)
        stack = 4
        return _info(base + stack + adjust, base, adjust, stack, "PC word pop from stack")
    if mnemonic == "RTD":
        base = 9
        adjust = _branch_adjust(instruction.address)
        stack = 4
        return _info(base + stack + adjust, base, adjust, stack, "PC word pop from stack")
    if mnemonic == "PRTS":
        base = 12
        adjust = _branch_adjust(instruction.address)
        stack = 6 if mode == "max" else 4
        return _info(base + stack + adjust, base, adjust, stack, "return address pop from stack")
    if mnemonic == "PRTD":
        base = 13
        adjust = _branch_adjust(instruction.address)
        stack = 6 if mode == "max" else 4
        return _info(base + stack + adjust, base, adjust, stack, "return address pop from stack")
    if mnemonic == "UNLK":
        return _info(5)
    if mnemonic == "LINK":
        return _info(6 if len(raw) == 2 else 7)
    if mnemonic == "LDM.W":
        n = raw[1].bit_count()
        cycles = 6 + 4 * n
        return _info(cycles, note=f"6+4n, n={n}")
    if mnemonic == "STM.W":
        n = raw[1].bit_count()
        cycles = 6 + 3 * n
        return _info(cycles, note=f"6+3n, n={n}")
    if mnemonic in {"NOP", "SLEEP"}:
        return _info(2)
    if mnemonic.startswith("CMP:E"):
        return _info(2)
    if mnemonic.startswith("CMP:I"):
        return _info(3)
    if mnemonic.startswith("MOV:E"):
        return _info(2)
    if mnemonic.startswith("MOV:I"):
        return _info(3)
    if mnemonic.startswith(("MOV:L", "MOV:S", "MOV:F")):
        return _info(5)
    return None
