"""Writes one page of each paper size the library names, and a few pages told how a
reader is to show them.

The Python twin of the `write_paper_sizes` example in Rust: the same pages, through the
binding rather than through the library directly.

Each page states its own name and its size, measured off the page in millimetres and in
inches, and is framed a centimetre in from its edges, so a reader that fits it to the
screen shows the whole sheet. The sizes run from A0 to A6, B4 and B5, then the four the
North American trade uses.

After them come an A5 page laid on its side; pages the reader is told to turn upside
down and a quarter to the left; pages that ask for the order a reader tabs through what
they carry; and a banner ten metres long, larger than a page may be, drawn on a page
whose unit is ten points.

The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks which set the
pages are written in.

Usage: python examples/write_paper_sizes.py [out.pdf]
       HQF_PDF_LANG=fr python examples/write_paper_sizes.py
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The font the pages are written in.
DEFAULT_FONT = _out.ROOT / "crates" / "hqf-pdf" / "tests" / "fonts" / "DejaVuSans.ttf"

# How many points make a millimetre.
POINTS_PER_MM = 72.0 / 25.4

# How far in from each edge the frame is drawn, in points: one centimetre.
FRAME = 10.0 * POINTS_PER_MM

# How long the banner is, in millimetres: ten metres.
BANNER_MM = 10_000.0

# How many points one unit of the banner's page stands for.
BANNER_UNIT = 10.0


@dataclass(frozen=True)
class Words:
    """Every word the pages say, in one language."""

    # What the file says of itself.
    title: str
    # What a page says of its size; `{name}`, `{mm}` and `{inches}` stand for them.
    size: str
    # What the A5 page laid on its side is called.
    side: str
    # What the three turned pages say: upside down, a quarter to the left, upright.
    turns: tuple[str, str, str]
    # What the pages asking for a tab order say, one per order: structure, annotations,
    # widgets.
    tabs: tuple[str, str, str]
    # What the banner says; `{metres}` and `{unit}` stand for its length and the points
    # one unit stands for.
    banner: str
    # The character separating the whole part of a figure from its tenths.
    decimal: str


# The pages in English.
ENGLISH = Words(
    title="Every paper size, and pages told how to be shown",
    size="{name}: {mm} mm, {inches} in",
    side="A5 on its side",
    turns=(
        "This page is to be shown upside down.",
        "This page is to be shown turned a quarter to the left.",
        "This page is to be shown as it was drawn.",
    ),
    tabs=(
        "A reader tabs through this page in the order its structure reads it.",
        "A reader tabs through this page in the order it lists its annotations.",
        "A reader tabs through this page's fields first, then the rest.",
    ),
    banner=(
        "A banner {metres} metres long: each unit of this page stands for {unit} "
        "points."
    ),
    decimal=".",
)

# The pages in French.
FRENCH = Words(
    title="Tous les formats de papier, et des pages qui disent comment les afficher",
    size="{name} : {mm} mm, {inches} po",
    side="A5 à l’italienne",
    turns=(
        "Cette page doit être affichée à l’envers.",
        "Cette page doit être affichée tournée d’un quart vers la gauche.",
        "Cette page doit être affichée telle qu’elle a été composée.",
    ),
    tabs=(
        "Avec la touche Tab, on parcourt cette page dans l’ordre de sa structure.",
        "Avec la touche Tab, on parcourt cette page dans l’ordre de ses annotations.",
        (
            "Avec la touche Tab, on parcourt d’abord les champs de cette page, puis le "
            "reste."
        ),
    ),
    banner=(
        "Une banderole de {metres} mètres : chaque unité de cette page vaut "
        "{unit} points."
    ),
    decimal=",",
)

# The pages in Dutch.
DUTCH = Words(
    title="Elk papierformaat, en pagina’s die te horen krijgen hoe ze getoond worden",
    size="{name}: {mm} mm, {inches} in",
    side="A5 liggend",
    turns=(
        "Deze pagina moet ondersteboven getoond worden.",
        "Deze pagina moet een kwartslag naar links gedraaid getoond worden.",
        "Deze pagina moet getoond worden zoals ze getekend is.",
    ),
    tabs=(
        "Een lezer tabt door deze pagina in de volgorde waarin haar structuur haar leest.",
        "Een lezer tabt door deze pagina in de volgorde waarin ze haar annotaties opsomt.",
        "Een lezer tabt eerst door de velden van deze pagina, dan door de rest.",
    ),
    banner=(
        "Een spandoek van {metres} meter: elke eenheid van deze pagina staat voor "
        "{unit} punten."
    ),
    decimal=".",
)

# The pages in Portuguese.
PORTUGUESE = Words(
    title="Todos os formatos de papel, e páginas às quais se diz como devem ser mostradas",
    size="{name}: {mm} mm, {inches} in",
    side="A5 ao baixo",
    turns=(
        "Esta página deve ser mostrada de pernas para o ar.",
        "Esta página deve ser mostrada rodada um quarto de volta para a esquerda.",
        "Esta página deve ser mostrada tal como foi desenhada.",
    ),
    tabs=(
        "Um leitor percorre esta página com o tabulador pela ordem da sua estrutura.",
        "Um leitor percorre esta página com o tabulador pela ordem em que lista as suas anotações.",
        "Um leitor percorre com o tabulador primeiro os campos desta página, depois o resto.",
    ),
    banner=(
        "Uma faixa de {metres} metros: cada unidade desta página vale {unit} pontos."
    ),
    decimal=".",
)

# The pages in Italian.
ITALIAN = Words(
    title="Ogni formato di carta, e pagine a cui si dice come mostrarsi",
    size="{name}: {mm} mm, {inches} in",
    side="A5 orizzontale",
    turns=(
        "Questa pagina va mostrata capovolta.",
        "Questa pagina va mostrata ruotata di un quarto a sinistra.",
        "Questa pagina va mostrata come è stata disegnata.",
    ),
    tabs=(
        "Un lettore percorre questa pagina col tasto Tab nell’ordine della sua struttura.",
        "Un lettore percorre questa pagina col tasto Tab nell’ordine in cui elenca le sue annotazioni.",
        "Un lettore percorre col tasto Tab prima i campi di questa pagina, poi il resto.",
    ),
    banner=(
        "Uno striscione di {metres} metri: ogni unità di questa pagina vale {unit} "
        "punti."
    ),
    decimal=".",
)

# The pages in Spanish.
SPANISH = Words(
    title="Todos los tamaños de papel, y páginas a las que se dice cómo mostrarse",
    size="{name}: {mm} mm, {inches} in",
    side="A5 apaisado",
    turns=(
        "Esta página debe mostrarse cabeza abajo.",
        "Esta página debe mostrarse girada un cuarto a la izquierda.",
        "Esta página debe mostrarse tal como se dibujó.",
    ),
    tabs=(
        "Un lector recorre esta página con el tabulador en el orden en que la lee su estructura.",
        "Un lector recorre esta página con el tabulador en el orden en que enumera sus anotaciones.",
        "Un lector recorre primero los campos de esta página con el tabulador, y luego el resto.",
    ),
    banner=(
        "Una pancarta de {metres} metros: cada unidad de esta página vale {unit} "
        "puntos."
    ),
    decimal=".",
)

# The pages in German.
GERMAN = Words(
    title="Jedes Papierformat und Seiten, denen gesagt wird, wie sie anzuzeigen sind",
    size="{name}: {mm} mm, {inches} Zoll",
    side="A5 quer",
    turns=(
        "Diese Seite ist auf dem Kopf anzuzeigen.",
        "Diese Seite ist um eine Vierteldrehung nach links gedreht anzuzeigen.",
        "Diese Seite ist so anzuzeigen, wie sie gezeichnet wurde.",
    ),
    tabs=(
        "Ein Leser springt mit der Tabulatortaste in der Reihenfolge ihrer Struktur durch diese Seite.",
        "Ein Leser springt mit der Tabulatortaste in der Reihenfolge ihrer Anmerkungen durch diese Seite.",
        "Ein Leser springt mit der Tabulatortaste zuerst durch die Felder dieser Seite, dann durch den Rest.",
    ),
    banner=(
        "Ein Banner von {metres} Metern Länge: Jede Einheit dieser Seite steht für "
        "{unit} Punkt."
    ),
    decimal=".",
)

# 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}


def sizes() -> list[tuple[str, hqf_pdf.Page]]:
    """The sizes the library names, each with what it is called."""
    page = hqf_pdf.Page
    return [
        ("A0", page.a0()),
        ("A1", page.a1()),
        ("A2", page.a2()),
        ("A3", page.a3()),
        ("A4", page.a4()),
        ("A5", page.a5()),
        ("A6", page.a6()),
        ("B4", page.b4()),
        ("B5", page.b5()),
        ("Letter", page.letter()),
        ("Legal", page.legal()),
        ("Tabloid", page.tabloid()),
        ("Ledger", page.ledger()),
    ]


def figure(value: float, places: int, words: Words) -> str:
    """`value` to `places` decimals, its decimal separator the language's own."""
    return f"{value:.{places}f}".replace(".", words.decimal)


def size_line(name: str, page: hqf_pdf.Page, words: Words) -> str:
    """What `page`, called `name`, says of its size."""
    width, height = page.width, page.height
    millimetres = (
        f"{figure(width / POINTS_PER_MM, 0, words)} × "
        f"{figure(height / POINTS_PER_MM, 0, words)}"
    )
    inches = f"{figure(width / 72.0, 1, words)} × {figure(height / 72.0, 1, words)}"
    return (
        words.size.replace("{name}", name)
        .replace("{mm}", millimetres)
        .replace("{inches}", inches)
    )


def framed(page: hqf_pdf.Page, font, lines) -> hqf_pdf.Page:
    """`page` framed a centimetre in from its edges, with `lines` set in its top left
    corner at a size that grows with the sheet, each broken to the frame."""
    width, height = page.width, page.height
    size = max(min(width, height) / 24.0, 10.0)
    content = hqf_pdf.Content()
    content.set_line_width(1.0)
    content.rect(FRAME, FRAME, width - FRAME - FRAME, height - FRAME - FRAME)
    content.stroke()
    flow = hqf_pdf.TextFlow(font, size, leading=size * 1.4)
    room = width - FRAME - FRAME - size - size
    top = height - FRAME - size
    for line in lines:
        broken = flow.break_lines(line, room)
        flow.draw(content, broken, FRAME + size, top, room)
        top -= flow.height(broken)
    page.set_content(content)
    return page


def pages(font, words: Words) -> list[hqf_pdf.Page]:
    """Every page of the document, in order."""
    result = []
    for name, page in sizes():
        result.append(framed(page, font, [size_line(name, page, words)]))

    side = hqf_pdf.Page.a5().on_its_side()
    result.append(framed(side, font, [size_line(words.side, side, words)]))

    turns = (
        hqf_pdf.PageTurn.UpsideDown,
        hqf_pdf.PageTurn.Left,
        hqf_pdf.PageTurn.Upright,
    )
    for turn, said in zip(turns, words.turns):
        page = framed(hqf_pdf.Page.a5(), font, [said])
        page.set_turn(turn)
        result.append(page)

    orders = (
        hqf_pdf.TabOrder.Structure,
        hqf_pdf.TabOrder.Annotations,
        hqf_pdf.TabOrder.Widgets,
    )
    for order, said in zip(orders, words.tabs):
        page = framed(hqf_pdf.Page.a5(), font, [said])
        page.set_tab_order(order)
        result.append(page)

    long = BANNER_MM * POINTS_PER_MM / BANNER_UNIT
    tall = 500.0 * POINTS_PER_MM / BANNER_UNIT
    metres = figure(BANNER_MM / 1000.0, 0, words)
    said = words.banner.replace("{metres}", metres).replace(
        "{unit}", figure(BANNER_UNIT, 0, words)
    )
    banner = framed(hqf_pdf.Page(long, tall), font, [said])
    banner.set_user_unit(BANNER_UNIT)
    result.append(banner)
    return result


def main() -> None:
    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 two
    # languages do not overwrite each other in `tmp/`.
    out = _out.output_path(Path(_language.file_name("paper_sizes.pdf", language)).stem)

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    # The two last tab orders are read from PDF 2.0 on.
    document.set_version(hqf_pdf.Version.V2_0)
    document.set_info("Title", words.title)
    font = document.add_font(hqf_pdf.Font.from_path(DEFAULT_FONT))
    for page in pages(font, words):
        document.add_page(page)

    written = document.write(out)
    print(f"wrote {out} ({written} bytes)")


if __name__ == "__main__":
    main()
