"""Draws the two square codes a page can carry — QR and Data Matrix — at the
correction levels, the modes, the shapes and the sizes a request can ask
for.

A square code is a grid of modules with a clear margin round it. Each box on
this page is filled the way a request fills it: a margin of `QUIET_ZONE`
modules each side — four for a QR code, one for a Data Matrix — and the grid
taking the rest of the width.

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_codes.py [out.pdf] [font.ttf]`
       `HQF_PDF_LANG=fr python examples/write_codes.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 stands above each row of codes, in the order the page draws them.
    captions: tuple[str, str, str, str]


# The sheet in English.
ENGLISH = Words(
    title="The codes a request can ask for by shape, by size, by level and by mode",
    heading="Codes asked for by shape, by size, by level and by mode",
    captions=(
        "The same text at the four correction levels: low, medium, quartile, high.",
        "One text to a mode: digits, capitals and punctuation, and any byte at all.",
        "A Data Matrix on a strip: the smallest oblong symbol that holds the text.",
        "A Data Matrix of a size the request names: two texts, one symbol size.",
    ),
)


# The sheet in French.
FRENCH = Words(
    title="Les codes qu’une requête peut demander par forme, taille, niveau et mode",
    heading="Des codes demandés par forme, taille, niveau et mode",
    captions=(
        (
            "Le même texte aux quatre niveaux de correction : faible, moyen, quartile, "
            "élevé."
        ),
        (
            "Un texte par mode : chiffres, majuscules et ponctuation, puis n’importe "
            "quel octet."
        ),
        (
            ("Un Data Matrix en bandeau : le plus petit symbole rectangulaire qui "
             "contient le texte.")
        ),
        (
            "Un Data Matrix de la taille que nomme la requête : deux textes, une seule "
            "taille."
        ),
    ),
)


# The sheet in German.
GERMAN = Words(
    title=(
        "Die Codes, die eine Anfrage nach Form, Größe, Stufe und Modus verlangen kann"
    ),
    heading="Codes nach Form, Größe, Stufe und Modus",
    captions=(
        "Derselbe Text in den vier Korrekturstufen: niedrig, mittel, Quartil, hoch.",
        "Ein Text pro Modus: Ziffern, Großbuchstaben mit Satzzeichen, beliebige Bytes.",
        (
            (("Ein Data-Matrix-Code als Streifen: das kleinste rechteckige Symbol für "
              "den Text."))
        ),
        ("Ein Data-Matrix-Code in angefragter Größe: zwei Texte, eine Symbolgröße."),
    ),
)


# The sheet in Spanish.
SPANISH = Words(
    title="Los códigos que una solicitud puede pedir por forma, tamaño, nivel y modo",
    heading="Códigos pedidos por forma, tamaño, nivel y modo",
    captions=(
        (
            "El mismo texto en los cuatro niveles de corrección: bajo, medio, cuartil, "
            "alto."
        ),
        "Un texto por modo: cifras, mayúsculas y puntuación, y cualquier byte.",
        ("Un Data Matrix en tira: el símbolo rectangular más pequeño que contiene el "
         "texto."),
        "Un Data Matrix del tamaño que indica la petición: dos textos, un solo tamaño.",
    ),
)


# The sheet in Italian.
ITALIAN = Words(
    title="I codici che una richiesta può chiedere per forma, misura, livello e modo",
    heading="Codici chiesti per forma, misura, livello e modo",
    captions=(
        (
            "Lo stesso testo ai quattro livelli di correzione: basso, medio, quartile, "
            "alto."
        ),
        "Un testo per ciascun modo: cifre; maiuscole e punteggiatura; qualsiasi byte.",
        (
            "Un Data Matrix a striscia: il simbolo oblungo più piccolo che contiene il "
            "testo."
        ),
        (
            "Un Data Matrix della misura indicata dalla richiesta: due testi, una sola "
            "misura."
        ),
    ),
)


# The sheet in Portuguese.
PORTUGUESE = Words(
    title="Os códigos que um pedido pode designar por forma, tamanho, nível e modo",
    heading="Códigos pedidos por forma, tamanho, nível e modo",
    captions=(
        "O mesmo texto nos quatro níveis de correção: baixo, médio, quartil, alto.",
        "Um texto por modo: algarismos, maiúsculas e pontuação, e qualquer byte.",
        "Um Data Matrix em tira: o menor símbolo retangular que contém o texto.",
        "Um Data Matrix do tamanho indicado pelo pedido: dois textos, um só tamanho.",
    ),
)


# The sheet in Dutch.
DUTCH = Words(
    title="De codes die een verzoek naar vorm, grootte, niveau en modus kan opvragen",
    heading="Codes opgevraagd naar vorm, grootte, niveau en modus",
    captions=(
        "Dezelfde tekst op de vier correctieniveaus: laag, gemiddeld, kwartiel, hoog.",
        ("Eén tekst per modus: cijfers, hoofdletters met leestekens, en willekeurige "
         "bytes."),
        (
            "Een Data Matrix als strook: het kleinste langwerpige symbool dat de tekst "
            "bevat."
        ),
        ("Een Data Matrix in de grootte die het verzoek noemt: twee teksten, één "
         "symboolgrootte."),
    ),
)

# 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": 56.000,
        "top": 813.200,
        "width": 483.000,
        "set": None,
        "order": 0,
    },
    {
        "text": lambda words: words.captions[0],
        "size": 10.5,
        "left": 56.000,
        "top": 782.600,
        "width": 483.000,
        "set": None,
        "order": 1,
    },
    {
        "text": lambda words: words.captions[1],
        "size": 10.5,
        "left": 56.000,
        "top": 640.600,
        "width": 483.000,
        "set": None,
        "order": 6,
    },
    {
        "text": lambda words: words.captions[2],
        "size": 10.5,
        "left": 56.000,
        "top": 490.600,
        "width": 483.000,
        "set": None,
        "order": 10,
    },
    {
        "text": lambda words: words.captions[3],
        "size": 10.5,
        "left": 56.000,
        "top": 370.600,
        "width": 483.000,
        "set": None,
        "order": 13,
    },
)

# 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.QrCode("https://example.test/lot/FR-1964413", mode=hqf_pdf.QrMode.Byte, correction=hqf_pdf.QrCorrection.Low),
        "drawn": "draw_qr",
        "across": "size",
        "quiet": hqf_pdf.QR_QUIET_ZONE,
        "left": 56.000,
        "low": 650.000,
        "width": 110.000,
        "height": 110.000,
        "order": 2,
    },
    {
        "code": lambda: hqf_pdf.QrCode("https://example.test/lot/FR-1964413", mode=hqf_pdf.QrMode.Byte, correction=hqf_pdf.QrCorrection.Medium),
        "drawn": "draw_qr",
        "across": "size",
        "quiet": hqf_pdf.QR_QUIET_ZONE,
        "left": 180.000,
        "low": 650.000,
        "width": 110.000,
        "height": 110.000,
        "order": 3,
    },
    {
        "code": lambda: hqf_pdf.QrCode("https://example.test/lot/FR-1964413", mode=hqf_pdf.QrMode.Byte, correction=hqf_pdf.QrCorrection.Quartile),
        "drawn": "draw_qr",
        "across": "size",
        "quiet": hqf_pdf.QR_QUIET_ZONE,
        "left": 304.000,
        "low": 650.000,
        "width": 110.000,
        "height": 110.000,
        "order": 4,
    },
    {
        "code": lambda: hqf_pdf.QrCode("https://example.test/lot/FR-1964413", mode=hqf_pdf.QrMode.Byte, correction=hqf_pdf.QrCorrection.High),
        "drawn": "draw_qr",
        "across": "size",
        "quiet": hqf_pdf.QR_QUIET_ZONE,
        "left": 428.000,
        "low": 650.000,
        "width": 110.000,
        "height": 110.000,
        "order": 5,
    },
    {
        "code": lambda: hqf_pdf.QrCode("31964413000271026", mode=hqf_pdf.QrMode.Numeric, correction=hqf_pdf.QrCorrection.Medium),
        "drawn": "draw_qr",
        "across": "size",
        "quiet": hqf_pdf.QR_QUIET_ZONE,
        "left": 56.000,
        "low": 500.000,
        "width": 110.000,
        "height": 110.000,
        "order": 7,
    },
    {
        "code": lambda: hqf_pdf.QrCode("LOT FR-1964413/2026", mode=hqf_pdf.QrMode.Alphanumeric, correction=hqf_pdf.QrCorrection.Medium),
        "drawn": "draw_qr",
        "across": "size",
        "quiet": hqf_pdf.QR_QUIET_ZONE,
        "left": 180.000,
        "low": 500.000,
        "width": 110.000,
        "height": 110.000,
        "order": 8,
    },
    {
        "code": lambda: hqf_pdf.QrCode("lot fr-1964413", mode=hqf_pdf.QrMode.Byte, correction=hqf_pdf.QrCorrection.Quartile),
        "drawn": "draw_qr",
        "across": "size",
        "quiet": hqf_pdf.QR_QUIET_ZONE,
        "left": 304.000,
        "low": 500.000,
        "width": 110.000,
        "height": 110.000,
        "order": 9,
    },
    {
        "code": lambda: hqf_pdf.DataMatrix.rectangular("SN:ACME-4472"),
        "drawn": "draw_data_matrix",
        "across": "width",
        "quiet": hqf_pdf.DATA_MATRIX_QUIET_ZONE,
        "left": 56.000,
        "low": 380.000,
        "width": 180.000,
        "height": 80.000,
        "order": 11,
    },
    {
        "code": lambda: hqf_pdf.DataMatrix.rectangular("TUBE-77"),
        "drawn": "draw_data_matrix",
        "across": "width",
        "quiet": hqf_pdf.DATA_MATRIX_QUIET_ZONE,
        "left": 260.000,
        "low": 380.000,
        "width": 180.000,
        "height": 80.000,
        "order": 12,
    },
    {
        "code": lambda: hqf_pdf.DataMatrix.with_size("FR-1964413", 16, 48),
        "drawn": "draw_data_matrix",
        "across": "width",
        "quiet": hqf_pdf.DATA_MATRIX_QUIET_ZONE,
        "left": 56.000,
        "low": 240.000,
        "width": 240.000,
        "height": 100.000,
        "order": 14,
    },
    {
        "code": lambda: hqf_pdf.DataMatrix.with_size("FR-2", 16, 48),
        "drawn": "draw_data_matrix",
        "across": "width",
        "quiet": hqf_pdf.DATA_MATRIX_QUIET_ZONE,
        "left": 300.000,
        "low": 240.000,
        "width": 240.000,
        "height": 100.000,
        "order": 15,
    },
    {
        "code": lambda: hqf_pdf.DataMatrix.with_size("FR-1964413/2026-08", 22, 22),
        "drawn": "draw_data_matrix",
        "across": "width",
        "quiet": hqf_pdf.DATA_MATRIX_QUIET_ZONE,
        "left": 56.000,
        "low": 120.000,
        "width": 120.000,
        "height": 110.000,
        "order": 16,
    },
)


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("codes.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()
