"""Discrete logic model; deliberately does not model metastability."""
from itertools import product


def full_adder(a, b, carry):
    return a ^ b ^ carry, (a & b) | (carry & (a ^ b))


class Debouncer:
    def __init__(self, threshold=3):
        if threshold < 1:
            raise ValueError("threshold must be positive")
        self.threshold = threshold
        self.stable = 0
        self.count = 0

    def sample(self, raw):
        if raw not in (0, 1):
            raise ValueError("binary samples only")
        if raw == self.stable:
            self.count = 0
            return None
        self.count += 1
        if self.count == self.threshold:
            self.stable = raw
            self.count = 0
            return "pressed" if raw else "released"
        return None


def main():
    for a, b, carry in product((0, 1), repeat=3):
        low, high = full_adder(a, b, carry)
        assert low + 2 * high == a + b + carry
    raw = [0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0]
    debouncer = Debouncer()
    events = [(i, event) for i, value in enumerate(raw)
              if (event := debouncer.sample(value)) is not None]
    assert events == [(6, "pressed"), (13, "released")]
    short = Debouncer(threshold=5)
    assert all(short.sample(v) is None for v in [1, 1, 1, 1, 0])
    print(f"full_adder: 8 combinations; events={events}")
    print("PASS")


if __name__ == "__main__":
    main()
