"""Authored application framing, not RTP/WebSocket wire framing or an Opera API."""
import struct,json
H=struct.Struct('<4sBBBBIII');MAX_PAYLOAD=4096;MAX_BUFFER=2*(H.size+MAX_PAYLOAD)
def frame(seq,samples):
    data=struct.pack('<'+'h'*len(samples),*samples)
    return H.pack(b'PCM1',1,1,1,2,16000,seq,len(data))+data
class Parser:
    def __init__(self):self.buffer=bytearray();self.expected=0;self.failed=False;self.closed=False
    def feed(self,chunk):
        if self.failed or self.closed:raise ValueError('parser stopped')
        if not isinstance(chunk,bytes):self.failed=True;raise TypeError('binary bytes required, not JSON text')
        if len(self.buffer)+len(chunk)>MAX_BUFFER:self.failed=True;raise ValueError('local feed buffer cap')
        self.buffer.extend(chunk);out=[]
        try:
            while len(self.buffer)>=H.size:
                magic,kind,version,channels,width,rate,seq,n=H.unpack_from(self.buffer)
                if (magic,kind,version,channels,width,rate)!=(b'PCM1',1,1,1,2,16000):raise ValueError('unsupported local header')
                if not 2<=n<=MAX_PAYLOAD or n%2:raise ValueError('invalid PCM16 payload length')
                if seq!=self.expected:raise ValueError('out of order sequence')
                if len(self.buffer)<H.size+n:break
                data=bytes(self.buffer[H.size:H.size+n]);del self.buffer[:H.size+n]
                out.append({'sequence':seq,'samples':list(struct.unpack('<'+'h'*(n//2),data)),'rate':rate,'channels':channels})
                self.expected+=1
        except Exception:self.failed=True;raise
        return out
    def finish(self):
        if self.failed or self.closed:raise ValueError('parser stopped')
        if self.buffer:self.failed=True;raise ValueError('truncated final frame')
        self.closed=True
def parse(chunks):
    p=Parser();out=[]
    for chunk in chunks:out.extend(p.feed(chunk))
    p.finish();return out
if __name__=='__main__':
    first=frame(0,[100,-100,0]);second=frame(1,[32767,-32768,23,0]);stream=first+second
    expected=[{'sequence':0,'samples':[100,-100,0],'rate':16000,'channels':1},{'sequence':1,'samples':[32767,-32768,23,0],'rate':16000,'channels':1}]
    assert len(stream)==54 and parse([stream])==expected
    for cut in range(len(stream)+1):assert parse([stream[:cut],stream[cut:]])==expected
    assert parse([bytes([v]) for v in stream])==expected
    bad=[('truncated',[stream[:-1]]),('sequence',[second]),('oversize',[H.pack(b'PCM1',1,1,1,2,16000,0,4098)]),('odd-byte',[H.pack(b'PCM1',1,1,1,2,16000,0,3)]),('type',[H.pack(b'PCM1',0,1,1,2,16000,0,2)+b'00']),('JSON-text',['{"samples":[1,2]}']),('buffer-cap',[b'0'*(MAX_BUFFER+1)])]
    for name,chunks in bad:
        try:parse(chunks)
        except (ValueError,TypeError):pass
        else:raise AssertionError(name+' accepted')
    print(json.dumps({'header_bytes':H.size,'stream_hex':stream.hex(),'frames':expected,'two_chunk_boundaries_tested':55,'one_byte_feed_tested':True,'invalid_cases_rejected':[name for name,_ in bad],'real_network_or_audio_playback':False,'standard_protocol_or_product_API':False},indent=2))
