"""Sets text down the page, in columns: three signs painted on narrow boards, the
letters of each word stacked one under the next.

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

Each board is a box of its own, and each sets its columns a different way: from the top
and right to left, the order Japanese and Chinese are set in, broken between any two
letters, a hyphen ending each column a cut inside a word closes; from the bottom and
left to right, broken only at a space; and centred down the
board, in a colour of its own and a wider leading. Only the columns that fit the board's
width are drawn.

Under each board, lines say how many of its columns fit and how many it broke into, how
tall the tallest runs, how many end a paragraph, what the first one holds, and how tall
the whole text would stand in one column. A grey outline marks the box every drawn
column covers.

The font is added to run down the page, which is what a flow down the page is refused
without. The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks
which set the page is written in.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# How far in from the left edge of the sheet the heading starts, in points.
MARGIN = 72.0

# Where the baseline of the heading sits, in points up from the foot of the sheet.
HEAD_TOP = 790.0

# The size the heading is set at, in points.
HEADING = 16.0

# The size the lines under the heading and under the boards are set at, in points.
BODY = 9.0

# How far below one of those lines the next one sits, in points.
LINE_STEP = 12.0

# The left edges of the three boards, in points.
BOARDS = (72.0, 247.0, 422.0)

# How wide a board is, in points.
BOARD_WIDTH = 110.0

# How wide the label over a board and the lines under it may run, in points.
LABEL_WIDTH = 150.0

# Where the top of every board sits, in points up from the foot of the sheet.
BOARD_TOP = 720.0

# How tall a board is, in points.
BOARD_HEIGHT = 280.0

# The room kept between a board's edge and its outermost column, in points.
INSET = 6.0

# The size the signs are set at, in points.
SIGN = 18.0


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

    # The heading, and what the file says of itself.
    title: str
    # What the page says under its heading.
    intro: tuple[str, str]
    # What each board is labelled, left to right.
    labels: tuple[str, str, str]
    # What each board says, left to right; a newline starts a paragraph.
    signs: tuple[str, str, str]
    # How many columns fit and how many there are; `{shown}` and `{count}` stand for
    # them.
    shown: str
    # How tall the tallest column runs; `{tall}` stands for it.
    tallest: str
    # How many columns end a paragraph; `{ends}` stands for it.
    ends: str
    # What the first column holds; `{first}` stands for it.
    first: str
    # How tall the whole text would stand in one column; `{long}` stands for it.
    long: str
    # How far across the drawn columns reach; `{across}` stands for it.
    across: str


# The page in English.
ENGLISH = Words(
    title="Text set down the page",
    intro=(
        (
            "Three signs on narrow boards, each word's letters stacked one under the "
            "next,"
        ),
        (
            "each board setting its columns another way. Under each, what its columns "
            "measure."
        ),
    ),
    labels=(
        "From the top, right to left, a hyphen at each cut",
        "From the bottom, left to right",
        "Centred, in blue, wider apart",
    ),
    signs=(
        "HARBOUR VIEW HOTEL\nROOMS FREE\nBREAKFAST SERVED\nPARKING\nWIFI",
        "OPEN\nALL YEAR",
        "FERRY\nTO THE\nISLANDS",
    ),
    shown="{shown} of {count} columns fit",
    tallest="tallest: {tall} pt",
    ends="{ends} end a paragraph",
    first="first: {first}",
    long="in one column: {long} pt",
    across="across: {across} pt",
)

# The page in French.
FRENCH = Words(
    title="Du texte composé de haut en bas",
    intro=(
        (
            "Trois enseignes sur des planches étroites, les lettres de chaque mot "
            "empilées,"
        ),
        (
            "chaque planche posant ses colonnes autrement. Dessous, ce que mesurent "
            "ses colonnes."
        ),
    ),
    labels=(
        "Du haut, de droite à gauche, un trait d’union à chaque coupure",
        "Du bas, de gauche à droite",
        "Centré, en bleu, plus espacé",
    ),
    signs=(
        "HÔTEL DE LA MARINE\nCHAMBRES LIBRES\nPETIT DÉJEUNER\nPARKING\nWIFI",
        "OUVERT\nTOUTE L’ANNÉE",
        "BAC\nPOUR\nLES ÎLES",
    ),
    shown="{shown} colonnes sur {count} tiennent",
    tallest="la plus haute : {tall} pt",
    ends="{ends} finissent un paragraphe",
    first="la première : {first}",
    long="en une colonne : {long} pt",
    across="en largeur : {across} pt",
)

# The page in Dutch.
DUTCH = Words(
    title="Tekst gezet langs de pagina omlaag",
    intro=(
        (
            "Drie borden op smalle planken, de letters van elk woord onder elkaar "
            "gestapeld;"
        ),
        (
            "elk bord zet zijn kolommen op een andere manier. Eronder wat de kolommen "
            "meten."
        ),
    ),
    labels=(
        "Van boven, van rechts naar links, een streepje bij elke breuk",
        "Van onder, van links naar rechts",
        "Gecentreerd, in blauw, verder uit elkaar",
    ),
    signs=(
        "HOTEL HAVENZICHT\nKAMERS VRIJ\nONTBIJT\nPARKEREN\nWIFI",
        "OPEN\nHET HELE JAAR",
        "VEERBOOT\nNAAR DE\nEILANDEN",
    ),
    shown="{shown} van {count} kolommen passen",
    tallest="hoogste: {tall} pt",
    ends="{ends} sluiten een alinea af",
    first="eerste: {first}",
    long="in één kolom: {long} pt",
    across="in de breedte: {across} pt",
)

# The page in Portuguese.
PORTUGUESE = Words(
    title="Texto composto para baixo",
    intro=(
        (
            "Três letreiros em tábuas estreitas, as letras de cada palavra empilhadas "
            "umas sob as outras,"
        ),
        (
            "e cada tábua compõe as suas colunas de outra forma. Por baixo de cada "
            "uma, o que medem as colunas."
        ),
    ),
    labels=(
        "Da direita para a esquerda, um hífen em cada corte",
        "De baixo, da esquerda para a direita",
        "Centrado, a azul, mais espaçado",
    ),
    signs=(
        "HOTEL VISTA DO PORTO\nQUARTOS LIVRES\nPEQUENO-ALMOÇO\nESTACIONAMENTO\nWIFI",
        "ABERTO\nO ANO TODO",
        "FERRY\nPARA AS\nILHAS",
    ),
    shown="cabem {shown} de {count} colunas",
    tallest="a mais alta: {tall} pt",
    ends="{ends} acabam um parágrafo",
    first="primeira: {first}",
    long="numa coluna: {long} pt",
    across="na largura: {across} pt",
)

# The page in Italian.
ITALIAN = Words(
    title="Testo composto verso il basso",
    intro=(
        (
            "Tre insegne su assi strette, le lettere di ogni parola impilate l’una "
            "sotto l’altra,"
        ),
        (
            "e ogni asse compone le sue colonne in un altro modo. Sotto ciascuna, "
            "cosa misurano le sue colonne."
        ),
    ),
    labels=(
        "Dall’alto, da destra a sinistra, un trattino a ogni taglio",
        "Dal basso, da sinistra a destra",
        "Centrato, in blu, più spaziato",
    ),
    signs=(
        "HOTEL VISTA PORTO\nCAMERE LIBERE\nCOLAZIONE\nPARCHEGGIO\nWIFI",
        "APERTO\nTUTTO L’ANNO",
        "TRAGHETTO\nPER LE\nISOLE",
    ),
    shown="ci stanno {shown} colonne su {count}",
    tallest="la più alta: {tall} pt",
    ends="{ends} chiudono un paragrafo",
    first="prima: {first}",
    long="in una colonna: {long} pt",
    across="in larghezza: {across} pt",
)

# The page in Spanish.
SPANISH = Words(
    title="Texto compuesto hacia abajo",
    intro=(
        (
            "Tres carteles en tablas estrechas, las letras de cada palabra apiladas "
            "una bajo otra,"
        ),
        (
            "y cada tabla compone sus columnas de otra forma. Debajo de cada una, lo "
            "que miden sus columnas."
        ),
    ),
    labels=(
        "De derecha a izquierda, un guion en cada corte",
        "Desde abajo, de izquierda a derecha",
        "Centrado, en azul, más espaciado",
    ),
    signs=(
        "HOTEL VISTA AL PUERTO\nHABITACIONES LIBRES\nDESAYUNO\nAPARCAMIENTO\nWIFI",
        "ABIERTO\nTODO EL AÑO",
        "FERRY\nA LAS\nISLAS",
    ),
    shown="caben {shown} de {count} columnas",
    tallest="la más alta: {tall} pt",
    ends="{ends} acaban un párrafo",
    first="primera: {first}",
    long="en una columna: {long} pt",
    across="a lo ancho: {across} pt",
)

# The page in German.
GERMAN = Words(
    title="Senkrecht gesetzter Text",
    intro=(
        (
            "Drei Schilder auf schmalen Brettern, die Buchstaben jedes Worts "
            "untereinander gestapelt,"
        ),
        (
            "jedes Brett setzt seine Spalten anders. Darunter, was seine Spalten "
            "messen."
        ),
    ),
    labels=(
        "Von oben, rechts nach links, Bindestrich bei Umbruch",
        "Von unten, von links nach rechts",
        "Zentriert, in Blau, weiter auseinander",
    ),
    signs=(
        "HOTEL HAFENBLICK\nZIMMER FREI\nFRÜHSTÜCK\nPARKPLATZ\nWLAN",
        "GANZJÄHRIG\nGEÖFFNET",
        "FÄHRE\nZU DEN\nINSELN",
    ),
    shown="{shown} von {count} Spalten passen",
    tallest="am höchsten: {tall} pt",
    ends="{ends} beenden einen Absatz",
    first="erste: {first}",
    long="in einer Spalte: {long} pt",
    across="quer: {across} pt",
)

# 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 flows(upright) -> tuple[hqf_pdf.VerticalFlow, ...]:
    """The three ways the boards set their columns, left to right."""
    return (
        hqf_pdf.VerticalFlow(
            upright,
            SIGN,
            align=hqf_pdf.ColumnAlign.Top,
            order=hqf_pdf.ColumnOrder.RightToLeft,
            overlong=hqf_pdf.Overlong.Break,
            cut_mark="-",
        ),
        hqf_pdf.VerticalFlow(
            upright,
            SIGN,
            align=hqf_pdf.ColumnAlign.Bottom,
            order=hqf_pdf.ColumnOrder.LeftToRight,
            overlong=hqf_pdf.Overlong.Overflow,
        ),
        hqf_pdf.VerticalFlow(
            upright,
            SIGN,
            align=hqf_pdf.ColumnAlign.Center,
            leading=SIGN * 1.6,
            color=hqf_pdf.Rgb(0.0, 0.3, 0.65),
        ),
    )


def said(flow, columns, shown, text, words) -> list[str]:
    """What the board holding `columns` says of them under it, `shown` of them drawn
    and `text` their whole text."""
    count = len(columns)
    tall = max((columns.height(index) for index in range(count)), default=0.0)
    ends = sum(1 for index in range(count) if columns.last_of_paragraph(index))
    first = next(
        (columns.text(index) for index in range(count) if not columns.is_empty(index)),
        "",
    )
    whole = text.replace("\n", " ")
    return [
        words.shown.replace("{shown}", str(shown)).replace("{count}", str(count)),
        words.tallest.replace("{tall}", f"{tall:.0f}"),
        words.ends.replace("{ends}", str(ends)),
        words.first.replace("{first}", first),
        words.long.replace("{long}", f"{flow.measure(whole):.0f}"),
        words.across.replace("{across}", f"{flow.width(columns.sub(0, shown)):.0f}"),
    ]


def page(font, upright, words) -> hqf_pdf.Page:
    """The page: the heading, then each board with its label over it, its columns that
    fit, the outline of the box each covers, and what it says of them under it."""
    content = hqf_pdf.Content()
    content.set_fill(hqf_pdf.Rgb(0.0, 0.0, 0.0))
    content.draw_text(font, HEADING, MARGIN, HEAD_TOP, words.title)
    y = HEAD_TOP - 22.0
    for line in words.intro:
        content.draw_text(font, BODY, MARGIN, y, line)
        y -= LINE_STEP

    label = hqf_pdf.TextFlow(font, BODY, leading=LINE_STEP)
    outlines = hqf_pdf.Guides.hairline(hqf_pdf.Rgb.gray(0.6))
    for index, flow in enumerate(flows(upright)):
        left = BOARDS[index]
        labelled = label.break_lines(words.labels[index], LABEL_WIDTH)
        label.draw(content, labelled, left, BOARD_TOP + 30.0, LABEL_WIDTH)

        content.save_state()
        content.set_stroke(hqf_pdf.Rgb(0.2, 0.2, 0.2))
        content.set_line_width(0.8)
        content.rect(left, BOARD_TOP - BOARD_HEIGHT, BOARD_WIDTH, BOARD_HEIGHT)
        content.stroke()
        content.restore_state()

        text = words.signs[index]
        room = BOARD_HEIGHT - 2.0 * INSET
        columns = flow.break_columns(text, room)
        shown = flow.fit(columns, BOARD_WIDTH - 2.0 * INSET, 0)
        drawn = columns.sub(0, shown)
        # A column hangs from its middle: the first sits half an em in from the edge the
        # order starts from.
        if index == 1:
            x = left + INSET + SIGN / 2.0
        else:
            x = left + BOARD_WIDTH - INSET - SIGN / 2.0
        top = BOARD_TOP - INSET
        flow.draw(content, drawn, x, top, room)
        outlines.outline_all(content, flow.column_boxes(drawn, x, top, room))

        baseline = BOARD_TOP - BOARD_HEIGHT - 14.0
        content.set_fill(hqf_pdf.Rgb(0.0, 0.0, 0.0))
        for line in said(flow, columns, shown, text, words):
            content.draw_text(font, BODY, left, baseline, line)
            baseline -= LINE_STEP

    result = hqf_pdf.Page.a4()
    result.set_content(content)
    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("vertical_text.pdf", language)).stem
    )

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    document.set_info("Title", words.title)
    font = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))
    upright = document.add_vertical_font(hqf_pdf.Font.from_path(_out.font_path()))
    document.add_page(page(font, upright, words))

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


if __name__ == "__main__":
    main()
