"""Tiny Code 128 barcode generator (no third-party code), used for job labels.

Code 128 is read by practically every barcode scanner, including cheap USB and Bluetooth ones.
Job numbers are digits, so they are packed with subset C (two digits per bar group) where possible,
which keeps the barcode short and easy to scan on a small label.
"""
from html import escape

# Bar/space widths for symbol values 0..106, alternating bar, space, bar, space, bar, space.
# Value 106 (stop) has a trailing extra bar, so it has 7 elements.
PATTERNS = [
    "212222", "222122", "222221", "121223", "121322", "131222", "122213", "122312", "132212", "221213",
    "221312", "231212", "112232", "122132", "122231", "113222", "123122", "123221", "223211", "221132",
    "221231", "213212", "223112", "312131", "311222", "321122", "321221", "312212", "322112", "322211",
    "212123", "212321", "232121", "111323", "131123", "131321", "112313", "132113", "132311", "211313",
    "231113", "231311", "112133", "112331", "132131", "113123", "113321", "133121", "313121", "211331",
    "231131", "213113", "213311", "213131", "311123", "311321", "331121", "312113", "312311", "332111",
    "314111", "221411", "431111", "111224", "111422", "121124", "121421", "141122", "141221", "112214",
    "112412", "122114", "122411", "142112", "142211", "241211", "221114", "413111", "241112", "134111",
    "111242", "121142", "121241", "114212", "124112", "124211", "411212", "421112", "421211", "212141",
    "214121", "412121", "111143", "111341", "131141", "114113", "114311", "411113", "411311", "113141",
    "114131", "311141", "411131", "211412", "211214", "211232", "2331112",
]
START_B, START_C, CODE_C, CODE_B, STOP = 104, 105, 99, 100, 106


def symbols(text):
    """Return the list of symbol values for text, including start, checksum and stop."""
    text = str(text)
    if not text:
        raise ValueError("nothing to encode")
    if any(not 32 <= ord(ch) <= 126 for ch in text):
        raise ValueError("only printable ASCII can be encoded")
    values = []
    i = 0
    if text.isdigit() and len(text) >= 2:
        if len(text) % 2:
            values += [START_B, ord(text[0]) - 32, CODE_C]
            i = 1
        else:
            values.append(START_C)
        while i < len(text):
            values.append(int(text[i:i + 2]))
            i += 2
    else:
        values.append(START_B)
        values += [ord(ch) - 32 for ch in text]
    checksum = (values[0] + sum(pos * val for pos, val in enumerate(values[1:], start=1))) % 103
    return values + [checksum, STOP]


def widths(text):
    """Bar/space widths (in modules) for text, alternating bar then space."""
    return [int(ch) for value in symbols(text) for ch in PATTERNS[value]]


def svg(text, module=2, height=70, quiet=10, label=None):
    """Return an inline SVG string. One module is `module` units wide; quiet zone is 10 modules."""
    ws = widths(text)
    x = quiet * module
    bars = []
    for index, w in enumerate(ws):
        if index % 2 == 0:  # even positions are bars
            bars.append(f'<rect x="{x}" y="0" width="{w * module}" height="{height}"/>')
        x += w * module
    total = x + quiet * module
    title = escape(label or f"Barcode {text}", quote=True)
    return (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {total} {height}" role="img" '
            f'aria-label="{title}" shape-rendering="crispEdges" preserveAspectRatio="none">'
            f'<rect width="{total}" height="{height}" fill="#fff"/><g fill="#000">{"".join(bars)}</g></svg>')
