"""Bounded binary stream parser and local socketpair checks."""
from binascii import crc_hqx
from collections import deque
from dataclasses import dataclass
import socket
import struct

MAGIC = b"\xa5\x5a"
PAYLOAD_SIZE = 8
FRAME_SIZE = 13


@dataclass(frozen=True)
class Record:
    sequence: int
    millivolts: int
    flags: int


def encode(sequence, millivolts, version=1):
    payload = struct.pack("<BBIH", version, 0, sequence, millivolts)
    checked = bytes([len(payload)]) + payload
    return MAGIC + checked + struct.pack("<H", crc_hqx(checked, 0xFFFF))


class Parser:
    def __init__(self):
        self.buffer = bytearray()
        self.crc_errors = 0
        self.length_errors = 0
        self.version_errors = 0
        self.peak = 0

    def feed(self, data):
        records = []
        for start in range(0, len(data), 256):
            self.buffer.extend(data[start:start + 256])
            self.peak = max(self.peak, len(self.buffer))
            while True:
                offset = self.buffer.find(MAGIC)
                if offset < 0:
                    self.buffer[:] = self.buffer[-1:] if self.buffer[-1:] == MAGIC[:1] else b""
                    break
                if offset:
                    del self.buffer[:offset]
                if len(self.buffer) < 3:
                    break
                if self.buffer[2] != PAYLOAD_SIZE:
                    self.length_errors += 1
                    del self.buffer[0]
                    continue
                if len(self.buffer) < FRAME_SIZE:
                    break
                checked = bytes(self.buffer[2:11])
                expected = struct.unpack("<H", self.buffer[11:13])[0]
                if crc_hqx(checked, 0xFFFF) != expected:
                    self.crc_errors += 1
                    del self.buffer[0]
                    continue
                version, flags, sequence, millivolts = struct.unpack("<BBIH", checked[1:])
                del self.buffer[:FRAME_SIZE]
                if version != 1:
                    self.version_errors += 1
                    continue
                records.append(Record(sequence, millivolts, flags))
        return records


class RecentSequences:
    def __init__(self, capacity=64):
        if capacity <= 0:
            raise ValueError("positive capacity required")
        self.capacity = capacity
        self.order = deque()
        self.seen = set()

    def accept(self, sequence):
        if sequence in self.seen:
            return False
        if len(self.order) == self.capacity:
            self.seen.remove(self.order.popleft())
        self.order.append(sequence)
        self.seen.add(sequence)
        return True


def main():
    stream = encode(1, 1200) + encode(2, 1210)
    for split in range(len(stream) + 1):
        parser = Parser()
        records = parser.feed(stream[:split]) + parser.feed(stream[split:])
        assert [r.sequence for r in records] == [1, 2]
    broken = bytearray(encode(9, 999))
    broken[7] ^= 0x40
    parser = Parser()
    noisy = b"noise\xa5\x5a\xff" + broken + encode(3, 1220, version=2) + stream
    records = []
    for byte in noisy:
        records.extend(parser.feed(bytes([byte])))
    assert [r.sequence for r in records] == [1, 2]
    assert parser.length_errors >= 1 and parser.crc_errors >= 1
    assert parser.version_errors == 1
    assert parser.feed(b"x" * 100_000 + encode(4, 1230))[0].sequence == 4
    assert parser.peak <= 256 + FRAME_SIZE - 1
    dedup = RecentSequences()
    assert [n for n in (1, 1, 2, 2, 3) if dedup.accept(n)] == [1, 2, 3]
    left, right = socket.socketpair()
    with left, right:
        left.settimeout(2)
        right.settimeout(2)
        left.sendall(stream)
        left.shutdown(socket.SHUT_WR)
        parser = Parser()
        from_socket = []
        while True:
            chunk = right.recv(3)
            if not chunk:
                break
            from_socket.extend(parser.feed(chunk))
        assert [r.sequence for r in from_socket] == [1, 2]
    print("all splits, corruption, noise, version, dedup, bounded buffer, socketpair: PASS")


if __name__ == "__main__":
    main()
