"""Draws every linear barcode a request can ask for by name, each one beside
what a scanner reads back from it.

A linear code is a run of bars and spaces built out of one width — a module
— and a reader needs a clear margin each side. Each box on this page is
filled the way a request fills it: a margin of `QUIET_ZONE` modules each
side, and the bars taking the rest of the width at the box's full height.

What the page says about the codes is held in `Words`, once per language, and
`HQF_PDF_LANG` picks which set the sheet is written in. What the codes carry stands
apart from them: a scanner reads it, and it is the same in every language.

Usage: `python examples/write_barcodes.py [out.pdf] [font.ttf]`
       `HQF_PDF_LANG=fr python examples/write_barcodes.py`
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

@dataclass(frozen=True)
class Words:
    """Every word the sheet is written in, in one language."""

    # What the file says it is, in what it says of itself.
    title: str
    # The heading at the top of the sheet.
    heading: str
    # What the sheet says under its heading, a line each.
    lines: tuple[str, str]
    # What stands under each code, in the order the page draws them: the symbology,
    # where it is met, and what a scanner reads back from it.
    captions: tuple[str, str, str, str, str, str, str, str, str]


# The sheet in English.
ENGLISH = Words(
    title="The linear barcodes a request can ask for by name",
    heading="The barcodes a workshop, a warehouse and a till read",
    lines=(
        "Each box below is one module to the point, so every bar edge lands on a whole",
        "pixel of a printer. Under each code is what a scanner reads back from it.",
    ),
    captions=(
        "Code 39, the workshop label: BAY 12-A",
        "Code 39 carrying its modulo 43 check character: 159F",
        "Code 93, the same text in half the room: BAY 12-A",
        "ITF-14, the outer case a shop takes delivery of: 00012345678905",
        "Codabar, the blood bag and the borrower's card: A31600$D",
        "UPC-E, the small North American package: 01234565",
        "A magazine, its issue number in a two-digit add-on: 9780201379624 53",
        (
            "A book, the price its publisher suggests in a five-digit add-on: "
            "9781234567897 51999"
        ),
        "GS1 DataBar, the loose cut of meat and the small pack: 00012345678905",
    ),
)


# The sheet in French.
FRENCH = Words(
    title="Les codes-barres linéaires qu’une requête peut demander par leur nom",
    heading="Codes-barres d’atelier, d’entrepôt et de caisse",
    lines=(
        (
            "Ci-dessous, un module mesure un point : chaque bord de barre tombe ainsi "
            "sur un"
        ),
        "pixel entier de l’imprimante. Sous chaque code, ce qu’un lecteur y lit.",
    ),
    captions=(
        "Code 39, l’étiquette d’atelier : BAY 12-A",
        "Code 39 avec son caractère de contrôle modulo 43 : 159F",
        "Code 93, le même texte dans deux fois moins de place : BAY 12-A",
        "ITF-14, le carton livré au magasin : 00012345678905",
        "Codabar, la poche de sang et la carte de bibliothèque : A31600$D",
        "UPC-E, le petit emballage nord-américain : 01234565",
        "Un magazine et son numéro, en extension à deux chiffres : 9780201379624 53",
        (
            "Un livre et son prix conseillé, en extension à cinq chiffres : "
            "9781234567897 51999"
        ),
        (
            "GS1 DataBar, la viande à la coupe et le petit conditionnement : "
            "00012345678905"
        ),
    ),
)


# The sheet in German.
GERMAN = Words(
    title="Die linearen Strichcodes, die eine Anfrage namentlich verlangen kann",
    heading="Strichcodes für Werkstatt, Lager und Kasse",
    lines=(
        "Ein Modul misst hier einen Punkt, sodass jede Balkenkante auf ein ganzes",
        "Druckerpixel fällt. Unter jedem Code steht, was ein Scanner daraus liest.",
    ),
    captions=(
        "Code 39, das Werkstattetikett: BAY 12-A",
        "Code 39 mit Prüfzeichen nach Modulo 43: 159F",
        "Code 93, derselbe Text auf halber Breite: BAY 12-A",
        "ITF-14, der Umkarton, den ein Laden geliefert bekommt: 00012345678905",
        "Codabar, der Blutbeutel und der Bibliotheksausweis: A31600$D",
        "UPC-E, die kleine nordamerikanische Packung: 01234565",
        (
            ("Eine Zeitschrift mit der Heftnummer im zweistelligen Zusatzcode: "
             "9780201379624 53")
        ),
        (
            (("Ein Buch mit Preisempfehlung im fünfstelligen Zusatzcode: 9781234567897"
              " 51999"))
        ),
        "GS1 DataBar, das Fleisch von der Theke und die kleine Packung: 00012345678905",
    ),
)


# The sheet in Spanish.
SPANISH = Words(
    title="Los códigos de barras lineales que una solicitud puede pedir por su nombre",
    heading="Códigos de barras de taller, almacén y caja",
    lines=(
        (
            (("Abajo, un módulo mide un punto: así cada borde de barra cae en un píxel"
              " entero"))
        ),
        "de la impresora. Bajo cada código figura lo que lee un escáner.",
    ),
    captions=(
        "Code 39, la etiqueta del taller: BAY 12-A",
        "Code 39 con su carácter de control módulo 43: 159F",
        "Code 93, el mismo texto en la mitad de espacio: BAY 12-A",
        "ITF-14, la caja de envío que recibe una tienda: 00012345678905",
        "Codabar, la bolsa de sangre y el carné de biblioteca: A31600$D",
        "UPC-E, el envase pequeño norteamericano: 01234565",
        "Una revista y su número, en un suplemento de dos cifras: 9780201379624 53",
        (
            ("Un libro y su precio recomendado, en un suplemento de cinco cifras: "
             "9781234567897 51999")
        ),
        "GS1 DataBar, la carne al corte y el envase pequeño: 00012345678905",
    ),
)


# The sheet in Italian.
ITALIAN = Words(
    title="I codici a barre lineari che una richiesta può chiedere per nome",
    heading="Codici a barre per officina, magazzino e cassa",
    lines=(
        (
            "Qui sotto un modulo misura un punto: ogni bordo di barra cade così su un "
            "pixel"
        ),
        "intero della stampante. Sotto ogni codice, ciò che un lettore vi legge.",
    ),
    captions=(
        "Code 39, l’etichetta d’officina: BAY 12-A",
        "Code 39 con il suo carattere di controllo modulo 43: 159F",
        "Code 93, lo stesso testo in metà dello spazio: BAY 12-A",
        "ITF-14, il cartone che un negozio riceve in consegna: 00012345678905",
        "Codabar, la sacca di sangue e la tessera della biblioteca: A31600$D",
        "UPC-E, la piccola confezione nordamericana: 01234565",
        ("Una rivista e il suo numero, in un codice aggiuntivo a due cifre: "
         "9780201379624 53"),
        (
            ("Un libro e il prezzo consigliato, in un codice aggiuntivo a cinque "
             "cifre: 9781234567897 51999")
        ),
        "GS1 DataBar, la carne al taglio e la piccola confezione: 00012345678905",
    ),
)


# The sheet in Portuguese.
PORTUGUESE = Words(
    title="Os códigos de barras lineares que um pedido pode designar pelo nome",
    heading="Códigos de barras de oficina, armazém e loja",
    lines=(
        "Abaixo, cada módulo mede um ponto: assim, cada bordo de barra cai num píxel",
        "inteiro da impressora. Sob cada código está o que um leitor ótico lê nele.",
    ),
    captions=(
        "Code 39, a etiqueta da oficina: BAY 12-A",
        "Code 39 com o seu carácter de controlo módulo 43: 159F",
        "Code 93, o mesmo texto em metade do espaço: BAY 12-A",
        "ITF-14, a caixa de transporte que uma loja recebe: 00012345678905",
        "Codabar, a bolsa de sangue e o cartão de leitor: A31600$D",
        "UPC-E, a pequena embalagem norte-americana: 01234565",
        ("Uma revista e o número da edição, num código suplementar de dois dígitos: "
         "9780201379624 53"),
        (
            ((("Um livro e o seu preço, num código suplementar de cinco dígitos: "
               "9781234567897 51999")))
        ),
        "GS1 DataBar, a carne ao corte e a embalagem pequena: 00012345678905",
    ),
)


# The sheet in Dutch.
DUTCH = Words(
    title="De lineaire streepjescodes die een verzoek bij naam kan opvragen",
    heading="Streepjescodes voor werkplaats, magazijn en kassa",
    lines=(
        ("Hieronder meet één module één punt, zodat elke streeprand op een hele pixel"),
        "van de printer valt. Onder elke code staat wat een scanner eruit leest.",
    ),
    captions=(
        "Code 39, het werkplaatsetiket: BAY 12-A",
        "Code 39 met modulo-43-controleteken: 159F",
        "Code 93, dezelfde tekst in half zoveel ruimte: BAY 12-A",
        "ITF-14, de omdoos die een winkel binnenkrijgt: 00012345678905",
        "Codabar, de bloedzak en de bibliotheekpas: A31600$D",
        "UPC-E, de kleine Noord-Amerikaanse verpakking: 01234565",
        (
            ("Een tijdschrift, met het nummer in een aanvullende code van twee "
             "cijfers: 9780201379624 53")
        ),
        (
            (("Een boek, met de adviesprijs in een aanvullende code van vijf cijfers: "
              "9781234567897 51999"))
        ),
        "GS1 DataBar, los verkocht vlees en de kleine verpakking: 00012345678905",
    ),
)

# Every language the example is written in. A language is added by writing its own set
# of words and naming it here.
WORDS = {
    _language.ENGLISH: ENGLISH,
    _language.FRENCH: FRENCH,
    _language.GERMAN: GERMAN,
    _language.SPANISH: SPANISH,
    _language.ITALIAN: ITALIAN,
    _language.PORTUGUESE: PORTUGUESE,
    _language.DUTCH: DUTCH,
}


# The blocks of words the page carries, each with what it says, the box it goes in and
# the place it holds in the order the page is drawn.
BLOCKS = (
    {
        "text": lambda words: words.heading,
        "size": 16.0,
        "left": 72.000,
        "top": 813.200,
        "width": 451.276,
        "set": None,
        "order": 0,
    },
    {
        "text": lambda words: words.lines[0],
        "size": 10.5,
        "left": 72.000,
        "top": 782.600,
        "width": 451.276,
        "set": None,
        "order": 1,
    },
    {
        "text": lambda words: words.lines[1],
        "size": 10.5,
        "left": 72.000,
        "top": 767.600,
        "width": 451.276,
        "set": None,
        "order": 2,
    },
    {
        "text": lambda words: words.captions[0],
        "size": 9.0,
        "left": 72.000,
        "top": 684.000,
        "width": 451.276,
        "set": None,
        "order": 4,
    },
    {
        "text": lambda words: words.captions[1],
        "size": 9.0,
        "left": 72.000,
        "top": 612.000,
        "width": 451.276,
        "set": None,
        "order": 6,
    },
    {
        "text": lambda words: words.captions[2],
        "size": 9.0,
        "left": 72.000,
        "top": 540.000,
        "width": 451.276,
        "set": None,
        "order": 8,
    },
    {
        "text": lambda words: words.captions[3],
        "size": 9.0,
        "left": 72.000,
        "top": 468.000,
        "width": 451.276,
        "set": None,
        "order": 10,
    },
    {
        "text": lambda words: words.captions[4],
        "size": 9.0,
        "left": 72.000,
        "top": 396.000,
        "width": 451.276,
        "set": None,
        "order": 12,
    },
    {
        "text": lambda words: words.captions[5],
        "size": 9.0,
        "left": 72.000,
        "top": 324.000,
        "width": 451.276,
        "set": None,
        "order": 14,
    },
    {
        "text": lambda words: words.captions[6],
        "size": 9.0,
        "left": 72.000,
        "top": 252.000,
        "width": 451.276,
        "set": None,
        "order": 17,
    },
    {
        "text": lambda words: words.captions[7],
        "size": 9.0,
        "left": 72.000,
        "top": 180.000,
        "width": 451.276,
        "set": None,
        "order": 20,
    },
    {
        "text": lambda words: words.captions[8],
        "size": 9.0,
        "left": 72.000,
        "top": 108.000,
        "width": 451.276,
        "set": None,
        "order": 22,
    },
)

# The codes the page carries: how each is built, how it is drawn, what says how many
# modules it is across, and the clear margin its symbology keeps.
CODES = (
    {
        "code": lambda: hqf_pdf.Code39("BAY 12-A"),
        "drawn": "draw_code39",
        "across": "module_count",
        "quiet": hqf_pdf.BARCODE_QUIET_ZONE,
        "left": 72.000,
        "low": 690.000,
        "width": 179.000,
        "height": 40.000,
        "order": 3,
    },
    {
        "code": lambda: hqf_pdf.Code39.with_check_digit("159"),
        "drawn": "draw_code39",
        "across": "module_count",
        "quiet": hqf_pdf.BARCODE_QUIET_ZONE,
        "left": 72.000,
        "low": 618.000,
        "width": 115.000,
        "height": 40.000,
        "order": 5,
    },
    {
        "code": lambda: hqf_pdf.Code93("BAY 12-A"),
        "drawn": "draw_code93",
        "across": "module_count",
        "quiet": hqf_pdf.BARCODE_QUIET_ZONE,
        "left": 72.000,
        "low": 546.000,
        "width": 129.000,
        "height": 40.000,
        "order": 7,
    },
    {
        "code": lambda: hqf_pdf.Itf14("00012345678905"),
        "drawn": "draw_itf14",
        "across": "module_count",
        "quiet": hqf_pdf.BARCODE_QUIET_ZONE,
        "left": 72.000,
        "low": 474.000,
        "width": 155.000,
        "height": 40.000,
        "order": 9,
    },
    {
        "code": lambda: hqf_pdf.Codabar("A31600$D"),
        "drawn": "draw_codabar",
        "across": "module_count",
        "quiet": hqf_pdf.BARCODE_QUIET_ZONE,
        "left": 72.000,
        "low": 402.000,
        "width": 119.000,
        "height": 40.000,
        "order": 11,
    },
    {
        "code": lambda: hqf_pdf.UpcE("01234565"),
        "drawn": "draw_upc_e",
        "across": "module_count",
        "quiet": hqf_pdf.BARCODE_QUIET_ZONE,
        "left": 72.000,
        "low": 330.000,
        "width": 71.000,
        "height": 40.000,
        "order": 13,
    },
    {
        "code": lambda: hqf_pdf.Ean13("9780201379624"),
        "drawn": "draw_ean13",
        "across": "module_count",
        "quiet": hqf_pdf.BARCODE_QUIET_ZONE,
        "left": 72.000,
        "low": 258.000,
        "width": 115.000,
        "height": 40.000,
        "order": 15,
    },
    {
        "code": lambda: hqf_pdf.AddOn2("53"),
        "drawn": "draw_add_on2",
        "across": "module_count",
        "quiet": hqf_pdf.BARCODE_QUIET_ZONE,
        "left": 176.000,
        "low": 258.000,
        "width": 40.000,
        "height": 40.000,
        "order": 16,
    },
    {
        "code": lambda: hqf_pdf.Ean13("9781234567897"),
        "drawn": "draw_ean13",
        "across": "module_count",
        "quiet": hqf_pdf.BARCODE_QUIET_ZONE,
        "left": 72.000,
        "low": 186.000,
        "width": 115.000,
        "height": 40.000,
        "order": 18,
    },
    {
        "code": lambda: hqf_pdf.AddOn5("51999"),
        "drawn": "draw_add_on5",
        "across": "module_count",
        "quiet": hqf_pdf.BARCODE_QUIET_ZONE,
        "left": 176.000,
        "low": 186.000,
        "width": 67.000,
        "height": 40.000,
        "order": 19,
    },
    {
        "code": lambda: hqf_pdf.DataBar("00012345678905"),
        "drawn": "draw_data_bar",
        "across": "module_count",
        "quiet": hqf_pdf.BARCODE_QUIET_ZONE,
        "left": 72.000,
        "low": 114.000,
        "width": 116.000,
        "height": 40.000,
        "order": 21,
    },
)


def draw_words(
    content: hqf_pdf.Content, handle: hqf_pdf.FontHandle, block, words: Words
) -> None:
    """Draws one block of words in the box it is given."""
    settings = {}
    if block["set"] is not None:
        leading, align = block["set"]
        settings = {"leading": leading, "align": align}
    flow = hqf_pdf.TextFlow(handle, block["size"], **settings)
    lines = flow.break_lines(block["text"](words), block["width"])
    # The Python side opens and closes the text object itself, where the Rust side
    # leaves that to the caller.
    flow.draw(content, lines, block["left"], block["top"], block["width"])


def draw_code(content: hqf_pdf.Content, one) -> None:
    """Draws one code in the box it is given, with its own clear margin each
    side and the code taking the rest of the width."""
    code = one["code"]()
    modules = float(getattr(code, one["across"]))
    quiet = float(one["quiet"])
    unit = one["width"] / (modules + 2.0 * quiet)
    content.save_state()
    # Rust says this as `set_fill_rgb`; Python takes the colour itself.
    content.set_fill(hqf_pdf.Rgb(0.0, 0.0, 0.0))
    drawn = getattr(content, one["drawn"])
    left = one["left"] + quiet * unit
    if one["drawn"] in ("draw_qr", "draw_data_matrix"):
        drawn(code, left, one["low"] + quiet * unit, modules * unit)
    else:
        drawn(code, left, one["low"], modules * unit, one["height"])
    content.restore_state()


def sheet(handle: hqf_pdf.FontHandle, words: Words) -> hqf_pdf.Page:
    """The one page: every code, each under the words that name it."""
    content = hqf_pdf.Content()
    for one in sorted(BLOCKS + CODES, key=lambda one: one["order"]):
        if "text" in one:
            draw_words(content, handle, one, words)
        else:
            draw_code(content, one)

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    return page


def main() -> None:
    """Writes the file, and says where it went."""
    language = _language.from_environment()
    words = _language.words_of(WORDS, language)

    # A named file is written as named; the default one carries the language, so the
    # languages do not overwrite each other in `tmp/`.
    out = _out.output_path(Path(_language.file_name("barcodes.pdf", language)).stem)

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    document.set_info("Title", words.title)

    handle = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))
    document.add_page(sheet(handle, words))

    data = document.to_bytes()
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_bytes(data)
    print(f"wrote {out}: {len(data)} bytes")


if __name__ == "__main__":
    main()
