#!/usr/bin/env python3
"""Generate hardcoded testbench vectors for bloom_filter_membership."""

from dataclasses import dataclass


@dataclass(frozen=True)
class Cycle:
    tag: str
    rst: int
    req_valid: int
    req_insert: int
    key: int


SEQUENCE = [
    Cycle("startup_reset", 1, 0, 0, 0x0000),
    Cycle("startup_reset", 1, 0, 0, 0x0000),
    Cycle("post_reset_idle", 0, 0, 0, 0x0000),
    Cycle("empty_query", 0, 1, 0, 0x1234),
    Cycle("empty_query_resp", 0, 0, 0, 0x0000),
    Cycle("insert_fresh", 0, 1, 1, 0x1234),
    Cycle("query_same", 0, 1, 0, 0x1234),
    Cycle("reinsert_same", 0, 1, 1, 0x1234),
    Cycle("reinsert_resp_idle", 0, 0, 0, 0x0000),
    Cycle("scenario_reset", 1, 0, 0, 0x0000),
    Cycle("overlap_insert_a", 0, 1, 1, 0x002F),
    Cycle("overlap_query_target", 0, 1, 0, 0x0002),
    Cycle("falsepos_insert_b", 0, 1, 1, 0x0141),
    Cycle("falsepos_query_target", 0, 1, 0, 0x0002),
    Cycle("query_inserted_a", 0, 1, 0, 0x002F),
    Cycle("dup_insert_first", 0, 1, 1, 0x003C),
    Cycle("dup_query", 0, 1, 0, 0x003C),
    Cycle("dup_insert_second", 0, 1, 1, 0x003C),
    Cycle("dup_resp_idle", 0, 0, 0, 0x0000),
    Cycle("discard_pending_issue", 0, 1, 1, 0x00AA),
    Cycle("discard_pending_reset", 1, 0, 0, 0x0000),
    Cycle("after_reset_query", 0, 1, 0, 0x00AA),
    Cycle("after_reset_resp", 0, 0, 0, 0x0000),
    Cycle("final_idle", 0, 0, 0, 0x0000),
]


def hash_indices(key: int) -> tuple[int, int, int]:
    h0 = (key ^ (key >> 7)) & 0x7F
    h1 = (key + (key >> 4) + 0x2D) & 0x7F
    h2 = ((key << 3) ^ (key >> 2) ^ 0x53) & 0x7F
    return h0, h1, h2


def simulate(sequence: list[Cycle]) -> list[int]:
    bloom = [0] * 128
    pending_valid = False
    pending_result = 0
    outputs: list[int] = []

    for cycle in sequence:
        outputs.append(0 if cycle.rst else (pending_result if pending_valid else 0))

        if cycle.rst:
            bloom = [0] * 128
            pending_valid = False
            pending_result = 0
            continue

        if cycle.req_valid:
            h0, h1, h2 = hash_indices(cycle.key)
            pending_result = bloom[h0] & bloom[h1] & bloom[h2]
            pending_valid = True

            if cycle.req_insert:
                bloom[h0] = 1
                bloom[h1] = 1
                bloom[h2] = 1
        else:
            pending_valid = False
            pending_result = 0

    return outputs


def emit_verilog(sequence: list[Cycle], outputs: list[int]) -> str:
    lines = []
    lines.append("// Generated by generate_golden.py. Do not edit by hand.")
    lines.append(f"localparam integer NUM_TESTS = {len(sequence)};")
    lines.append("reg test_rst [0:NUM_TESTS-1];")
    lines.append("reg test_req_valid [0:NUM_TESTS-1];")
    lines.append("reg test_req_insert [0:NUM_TESTS-1];")
    lines.append("reg [15:0] test_key [0:NUM_TESTS-1];")
    lines.append("reg expected_maybe [0:NUM_TESTS-1];")
    lines.append("reg [255:0] test_tag [0:NUM_TESTS-1];")
    lines.append("")
    lines.append("initial begin")

    for idx, (cycle, output) in enumerate(zip(sequence, outputs)):
        lines.append(f"    test_rst[{idx}] = 1'b{cycle.rst};")
        lines.append(f"    test_req_valid[{idx}] = 1'b{cycle.req_valid};")
        lines.append(f"    test_req_insert[{idx}] = 1'b{cycle.req_insert};")
        lines.append(f"    test_key[{idx}] = 16'h{cycle.key:04x};")
        lines.append(f"    expected_maybe[{idx}] = 1'b{output};")
        lines.append(f'    test_tag[{idx}] = "{cycle.tag}";')
    lines.append("end")
    return "\n".join(lines)


def emit_summary(sequence: list[Cycle], outputs: list[int]) -> str:
    lines = []
    for idx, (cycle, output) in enumerate(zip(sequence, outputs)):
        lines.append(
            f"{idx:02d}: rst={cycle.rst} valid={cycle.req_valid} insert={cycle.req_insert} "
            f"key=0x{cycle.key:04x} expected={output} tag={cycle.tag}"
        )
    return "\n".join(lines)


def main() -> None:
    outputs = simulate(SEQUENCE)
    print(emit_summary(SEQUENCE, outputs))
    print()
    print(emit_verilog(SEQUENCE, outputs))


if __name__ == "__main__":
    main()
