#!/usr/bin/env python

import sys
import struct

input = open(sys.argv[1]).read()
lines = filter(lambda x: x, input.split("\n")) # split into lines, discard blanks

output = list()
zipperbit = 0
for line in lines:
    if line[0:3] != "001":
        raise RuntimeError, 'Did not find left alignment stripe'

    if line[40:46] == "110011":
        # we're looking at a header row
        pass
    elif line[41:45] != "1100":
        raise RuntimeError, 'Did not find right alignment stripe'
    else: 
        # we're looking at a data line
        # check vertical sync with zipper bit
        if int(line[45]) != zipperbit:
            raise RuntimeError, 'Lost sync with zipper bit'
        # check vertical sync with checkerboard
        if line[3:5] != ["10", "01"][zipperbit]:
            raise RuntimeError, 'Lost sync with checkerboard'
        zipperbit = 1-zipperbit

        data = list()
        dibits = line[5:41]
        while dibits:
            dibit = dibits[:2]
            dibits = dibits[2:]
            if dibit == '01':
                data.append(0)
            elif dibit == "10":
                data.append(1)
            else:
                raise RuntimeError, 'Bad dibit %r' % dibit

        left_parity = data[0]
        right_parity = data[17]

        d = data[1:17]

        if (sum(d[1::2]) & 1) != left_parity:
            raise RuntimeError, "Bad left parity"

        if (sum(d[::2]) & 1) != right_parity:
            raise RuntimeError, "Bad right parity"

        while d:
            byte = 0
            bit = 1
            for x in range(8):
                if d.pop(0):
                    byte |= bit
                bit <<= 1
            output.append(chr(byte))

output = ''.join(output)

# skip header bytes
while output[0] == chr(0x80):
    output = output[1:]

# typedef struct __attribute__ ((packed)) {
#     uint8_t data_sync;
#     uint16_t expansion1;
#     uint16_t length;
#     uint8_t checksum;
#     char strip_id[6];
#     uint8_t sequence;
#     uint8_t type;
#     uint16_t expansion;
#     uint8_t op_sys;
#     uint8_t num_files;
# } BarcodeFields;
barcode_fields_format = "<BHHB6sBBHBB"
barcode_fields_len = struct.calcsize(barcode_fields_format)
barcode_fields = struct.unpack(barcode_fields_format, output[:barcode_fields_len])
output = output[barcode_fields_len:]

if barcode_fields[0] or barcode_fields[1]:
    raise RuntimeError, 'Invalid header - expected zeroes'

print "Decoded payload has strip ID %r, length %d bytes, %d file(s)" % (barcode_fields[4], barcode_fields[2], barcode_fields[9])

# trim to expected length
output = output[:barcode_fields[2] - 13]

# typedef struct __attribute__ ((packed)) {
#     uint8_t cauzin_type;
#     uint8_t file_type;
#     unsigned int length:24;
#     char name[];
# } BarcodeFileEntry;
barcode_file_format = "<BBHB"
barcode_file_len = struct.calcsize(barcode_file_format)

# hmmm. not clear if we get all the headers at once, or if they are interspersed between the file data.
for filenum in range(0, barcode_fields[9]):
    barcode_file_header = struct.unpack(barcode_file_format, output[:barcode_file_len])
    output = output[barcode_file_len:]
    file_size = (barcode_file_header[3] << 16) | barcode_file_header[2]
    file_name = ''
    while output[0] != '\x00':
        file_name += output[0]
        output = output[1:]
    print "File name %r, length %d bytes" % (file_name, file_size)
    # skip over NUL string terminator and one other byte ("Block Expand length"?)
    output = output[2:]
    if len(output) < file_size:
        raise RuntimeError, 'Underrun'
    # now we have everything we need
    open(file_name, 'w').write(output[:file_size])
    output = output[file_size:]

if output != '':
    raise RuntimeError, 'Overrun'
