import json
import math

SOURCE = """OPENQASM 3;
qubit[2] q;
bit[2] c;
h q[0];
cx q[0], q[1];
c[0] = measure q[0];
c[1] = measure q[1];
"""

def indexed(token, register, size):
    prefix = register + "["
    if not token.startswith(prefix) or not token.endswith("]"):
        raise ValueError("wire must name a declared register")
    index = int(token[len(prefix):-1])
    if not 0 <= index < size:
        raise ValueError("wire index out of range")
    return index

def parse(source):
    qubits = classical = None
    operations = []
    classical_writes = set()
    for raw in source.splitlines():
        line = raw.strip()
        if not line:
            continue
        if not line.endswith(";"):
            raise ValueError("every statement must end with a semicolon")
        line = line[:-1].strip()
        if line == "OPENQASM 3":
            continue
        if line.startswith("qubit["):
            left, name = line.split()
            qubits = int(left[6:-1])
            if name != "q" or qubits < 1:
                raise ValueError("unsupported qubit declaration")
        elif line.startswith("bit["):
            left, name = line.split()
            classical = int(left[4:-1])
            if name != "c" or classical < 1:
                raise ValueError("unsupported bit declaration")
        elif line.startswith("h "):
            if qubits is None:
                raise ValueError("qubits must be declared before gates")
            operations.append(("H", indexed(line[2:].strip(), "q", qubits)))
        elif line.startswith("cx "):
            if qubits is None:
                raise ValueError("qubits must be declared before gates")
            operands = [part.strip() for part in line[3:].split(",")]
            if len(operands) != 2:
                raise ValueError("cx needs a control and target")
            control = indexed(operands[0], "q", qubits)
            target = indexed(operands[1], "q", qubits)
            if control == target:
                raise ValueError("control and target must differ")
            operations.append(("CNOT", control, target))
        elif "= measure " in line:
            if qubits is None or classical is None:
                raise ValueError("measurement registers must be declared")
            destination, source_wire = [part.strip() for part in line.split("= measure ")]
            cbit = indexed(destination, "c", classical)
            qubit = indexed(source_wire, "q", qubits)
            if cbit in classical_writes:
                raise ValueError("classical destination written twice")
            classical_writes.add(cbit)
            operations.append(("MEASURE", qubit, cbit))
        else:
            raise ValueError("unknown or ambiguous instruction")
    if qubits is None or classical is None or not operations:
        raise ValueError("incomplete circuit")
    return {"qubits": qubits, "bits": classical, "operations": operations}

def serialize(ir):
    lines = ["OPENQASM 3;", f"qubit[{ir['qubits']}] q;", f"bit[{ir['bits']}] c;"]
    for operation in ir["operations"]:
        if operation[0] == "H":
            lines.append(f"h q[{operation[1]}];")
        elif operation[0] == "CNOT":
            lines.append(f"cx q[{operation[1]}], q[{operation[2]}];")
        else:
            lines.append(f"c[{operation[2]}] = measure q[{operation[1]}];")
    return "\n".join(lines) + "\n"

def dependency_edges(operations):
    last_on_wire = {}
    edges = set()
    for index, operation in enumerate(operations):
        wires = operation[1:3] if operation[0] == "CNOT" else operation[1:2]
        for wire in wires:
            if wire in last_on_wire:
                edges.add((last_on_wire[wire], index))
            last_on_wire[wire] = index
    return edges

def apply_h(state, qubit, count):
    mask = 1 << (count - 1 - qubit)
    output = state[:]
    scale = math.sqrt(0.5)
    for basis in range(len(state)):
        if basis & mask == 0:
            paired = basis | mask
            output[basis] = (state[basis] + state[paired]) * scale
            output[paired] = (state[basis] - state[paired]) * scale
    return output

def apply_cnot(state, control, target, count):
    control_mask = 1 << (count - 1 - control)
    target_mask = 1 << (count - 1 - target)
    output = [0j] * len(state)
    for basis, amplitude in enumerate(state):
        destination = basis ^ target_mask if basis & control_mask else basis
        output[destination] += amplitude
    return output

def execute(ir):
    state = [0j] * (1 << ir["qubits"])
    state[0] = 1.0
    measurement_map = {}
    for operation in ir["operations"]:
        if operation[0] == "H":
            state = apply_h(state, operation[1], ir["qubits"])
        elif operation[0] == "CNOT":
            state = apply_cnot(state, operation[1], operation[2], ir["qubits"])
        else:
            measurement_map[operation[2]] = operation[1]
    if set(measurement_map) != set(range(ir["bits"])):
        raise ValueError("fixture requires every classical bit to be assigned")
    probabilities = {}
    for basis, amplitude in enumerate(state):
        bits = []
        for cbit in range(ir["bits"]):
            qubit = measurement_map[cbit]
            bits.append(str((basis >> (ir["qubits"] - 1 - qubit)) & 1))
        outcome = "".join(bits)
        probabilities[outcome] = probabilities.get(outcome, 0.0) + abs(amplitude) ** 2
    return state, {key: value for key, value in probabilities.items() if value > 1e-12}

ir = parse(SOURCE)
assert parse(serialize(ir)) == ir
assert dependency_edges(ir["operations"]) == {(0, 1), (1, 2), (1, 3)}
canonical_json = json.dumps(ir, sort_keys=True)
assert '"operations": [["H", 0], ["CNOT", 0, 1]' in canonical_json
state, counts = execute(ir)
assert all(abs(actual - expected) < 1e-12 for actual, expected in zip(state, [math.sqrt(0.5), 0, 0, math.sqrt(0.5)]))
assert set(counts) == {"00", "11"} and all(abs(value - 0.5) < 1e-12 for value in counts.values())

mutated = {**ir, "operations": [ir["operations"][1], ir["operations"][0], *ir["operations"][2:]]}
mutated_counts = execute(mutated)[1]
assert set(mutated_counts) == {"00", "10"} and all(abs(value - 0.5) < 1e-12 for value in mutated_counts.values())

def with_line(index, replacement):
    lines = SOURCE.splitlines()
    lines[index] = replacement
    return "\n".join(lines) + "\n"

for invalid in (
    with_line(4, "cx q[0], q[0];"),
    with_line(6, "measure q[1];"),
    with_line(6, "c[0] = measure q[1];"),
):
    rejected = False
    try:
        parse(invalid)
    except ValueError:
        rejected = True
    assert rejected
print(f"PASS: 16 circuit parser round-trips {len(ir['operations'])} operations with {len(dependency_edges(ir['operations']))} dependencies; Bell support={sorted(counts)}, mutation support={sorted(mutated_counts)}")
