#!/usr/local/bin/python

"Some barcode twiddling classes."

import string, operator

class barcode:
    """Bar codes - all as EAN13.

    It has two attributes:
    	Code - an EAN13 bar code corresponding to what was passed in.
        Digits - an array of 13 digits corresponding to Code."""

    def __init__(my, scan):
        "Create a barcode object from the given digit string."

        my.scan = scan
        if len(scan) > 6 and scan[:3] == '978':	# IB5: EAN13 + price
            my.Code = scan[:13]	# So strip the price!
        elif len(scan) == 13:		# EAN13 is a superset of them all.
            my.Code = scan
        elif len(scan) == 12:		# UPCA: The "0x" types of EAN13
            my.Code = '0' + scan
        elif len(scan) == 8:		# UPCE is zero-compressed UPCA
            my.expandUPCE()
        else:
            raise ValueError, "Unrecognized bar code type: " + scan

        my.Digits = map(operator.sub, map(ord, my.Code),
                        len(my.Code) * [ord('0')])
        my.check()

    def check(my):
        "Verify the checksum, producing Digits along the way."

        even = [0, 0]
        odd = 0
        for digit in my.Digits[:-1]:
            if not 0 <= digit <= 9:
                raise ValueError, "Invalid bar code character: %d" % digit
            even[odd] = even[odd] + digit
            odd = 1 - odd
        if (10 - (3 * even[1] + even[0])) % 10 != ord(my.Code[-1]) - ord('0'):
            raise ValueError, "Invalid bar code: " + my.Code


    def expandUPCE(my):
        """Expand a UCPE string into it's EAN13 version.

        UCPE basically lets the manufacturer elide a string of four or five
        zeros from a UPCA code. The exact format depends on the last digit
        before the check digit (which is the last digit)."""
        
        string = my.string
        indicator = string[-2]		# Indicates which encoding we're using

        if indicator in '012':
            front = string[:3] + indicator + '00'
            back = '00' + string[3:6]
        elif indicator == '3':
            front = string[:4] + '00'
            back = '000' + string[4:6]
        elif indicator == '4':
            front = string[:5] + '0'
            back = '0000' + string[5:6]
        else:
            front = string[:6]
            back = '0000' + string[6:7]

        # Add the EAN13 indicator to the front, and the check digit to the back
        my.Code = '0' + front + back + string[-1]
        
    
EAN13 = barcode		# Default barcode type has a name.

class ISBN(barcode):
    """ISBN extracted from a bar code. Adds ISBN as a data item.

    Changes the two attributes:
    Code - the 10-digit ISBN, with proper checkcode
    Digits - those same 10 digits in a list."""

    def __init__(my, string):
        "Adds ISBN - a valid ISBN, complete with checkdigits."

        barcode.__init__(my, string)
        if my.Code[:3] != '978':
            raise ValueError, "Bar Code is not an ISBN: " + string

        my.Digits = my.Digits[3:-1]
        my.Code = my.Code[3:-1]
        my.addISBNcheck()

    def addISBNcheck(my):
        "Compute and add the ISBN checkdigit, completing the string."

        digit = (11 - (reduce(operator.add, map(operator.mul, 
                                                my.Digits, 
                                                range(10, 1, -1))))) % 11

        # Hack because 10 is a legal checksum, represented as X
        if digit == 10:
            my.Digits.append('X')
            my.Code = my.Code + 'X'
        else:
            my.Digits.append(digit)
            my.Code = my.Code + chr(digit + ord('0'))

class UPC(barcode):
    """A small class to provide UPC's from an EAN13 barcode.

    Adds two attributes:
    Code - the 12-digit UPC code.
    Digits - the array of digits in the UPC."""

    def __init__(my, string):
        "Strips the leading digit to make the UPC code happy."

        barcode.__init__(my, string)

        if my.Digits[0] != 0:
            raise ValueError, "Bar Code is not a UPC: " + string
            
        my.Code = my.Code[1:]
        my.Digits = my.Digits[1:]
