"""Sets one justified paragraph in a narrow column four times: first with no
break points inside its words, then breaking after a slash or a hyphen, then
breaking where its punctuation opens a break, so the paths and compounds that ran
past the edge can be seen wrapping in it, and last breaking where its punctuation
opens a break once each hyphen has given way to a non-breaking one, so the paths
still wrap and the compounds stay whole.

The Python twin of the `write_break_after` example in Rust. The text carries
file paths and hyphenated compounds whose tokens are wider than the column. The
first setting breaks only at spaces, so those tokens run past the column's right
edge; the second breaks after "/" and "-", keeping the character on the line, so
the tokens wrap inside the column; the third names no character and reads the
punctuation itself, breaking after a slash or a hyphen but never after one that
opens a path; the fourth reads the punctuation too, its text given with each
hyphen replaced by a non-breaking hyphen before the lines are broken.

The sentence and the four headings are held in `Words`, once per language, and
`HQF_PDF_LANG` picks which set is drawn. The two paths are addresses, and stand the same
in every language.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The two addresses the sentence carries, each wider than the column so that a break
# after a slash can be seen falling inside them.
RENDER_PATH = "/api/v2/organizations/documents/render"
PREVIEW_PATH = "/api/v2/organizations/documents/preview"


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

    What is not language stays out of it: the two paths are addresses a caller types,
    and each set of words carries a hyphenated compound of its own so that every setting
    has something to break in.
    """

    # The heading over the setting that breaks at spaces only.
    plain: str
    # The heading over the setting that breaks after a slash and a hyphen.
    breaking: str
    # The heading over the setting that breaks where its punctuation opens a break.
    punctuation: str
    # The heading over the setting that breaks where its punctuation opens a break, each
    # hyphen replaced by a non-breaking one.
    whole: str
    # The sentence, cut where the two paths go into it.
    text_before: str
    text_between: str
    text_after: str

    def text(self) -> str:
        """Return the sentence every setting is broken to, with the paths in it."""
        return (
            f"{self.text_before}{RENDER_PATH}"
            f"{self.text_between}{PREVIEW_PATH}{self.text_after}"
        )


# The page in English.
ENGLISH = Words(
    plain="Without break points: the paths run past the column's edge.",
    breaking="Breaking after slash and hyphen: the paths and compounds wrap.",
    punctuation="Breaking where punctuation allows: paths and compounds wrap, no character named.",
    whole="Breaking where punctuation allows, no-break hyphens: paths wrap, compounds stay whole.",
    text_before="Send each request to ",
    text_between=", then read the read-only copy from ",
    text_after=" on the rate-limited tier.",
)

# The page in French.
FRENCH = Words(
    plain="Sans point de coupure : les chemins débordent de la colonne.",
    breaking="Coupure après la barre oblique et le trait d’union : chemins et mots composés se coupent.",
    punctuation="Coupure à la ponctuation, sans nommer de caractère : chemins et mots composés se coupent.",
    whole="Coupure à la ponctuation, trait d’union insécable : les chemins se coupent, pas les mots composés.",
    text_before="Envoyez chaque requête à ",
    text_between=", puis consultez le fac-similé en lecture seule à l’adresse ",
    text_after=" avec l’offre à débit limité.",
)

# The page in Dutch.
DUTCH = Words(
    plain="Zonder breekpunten: paden lopen over de rand van de kolom.",
    breaking="Breken na schuine streep en koppelteken: paden en samenstellingen worden afgebroken.",
    punctuation="Breken bij leestekens, geen teken opgegeven: paden en samenstellingen worden afgebroken.",
    whole="Breken bij leestekens, vaste koppeltekens: paden worden afgebroken, samenstellingen niet.",
    text_before="Stuur elk verzoek naar ",
    text_between=" en lees daarna de alleen-lezenkopie van ",
    text_after=" op het niveau met verzoeklimiet.",
)

# The page in Portuguese.
PORTUGUESE = Words(
    plain="Sem pontos de quebra: os caminhos ultrapassam a margem da coluna.",
    breaking="Quebra após a barra e o hífen: os caminhos e a palavra composta partem-se.",
    punctuation="Quebra pela pontuação, sem indicar caracteres: os caminhos e a palavra composta partem-se.",
    whole="Quebra pela pontuação, hífen inseparável: partem-se os caminhos, não a palavra composta.",
    text_before="Envie cada pedido para ",
    text_between=" e, depois, leia a cópia só de leitura em ",
    text_after=", no escalão com taxa-limite de pedidos.",
)

# The page in Italian.
ITALIAN = Words(
    plain="Senza punti di interruzione: i percorsi escono dal bordo della colonna.",
    breaking="Interruzione dopo barra e trattino: percorsi e composti vanno a capo.",
    punctuation="Interruzione sulla punteggiatura, senza indicare segni: percorsi e composti vanno a capo.",
    whole="Interruzione sulla punteggiatura, trattini indivisibili: a capo i percorsi, non i composti.",
    text_before="Inviare ogni richiesta a ",
    text_between=", poi leggere da ",
    text_after=" la copia in sola lettura, sul livello con soglia-limite di richieste.",
)

# The page in Spanish.
SPANISH = Words(
    plain="Sin puntos de corte: las rutas se salen del borde de la columna.",
    breaking="Corte tras la barra y el guion: las rutas y los compuestos se parten.",
    punctuation="Corte según la puntuación, sin nombrar caracteres: las rutas y los compuestos se parten.",
    whole="Corte según la puntuación con guiones inseparables: las rutas se parten, los compuestos no.",
    text_before="Envíe cada petición a ",
    text_between=" como pares clave-valor y después lea la copia de solo lectura desde ",
    text_after=", en el nivel con límite de peticiones.",
)

# The page in German.
GERMAN = Words(
    plain="Ohne Umbruchstellen: Pfade laufen über den Spaltenrand hinaus.",
    breaking="Umbruch nach Schrägstrich und Bindestrich: Pfade und Komposita brechen um.",
    punctuation="Umbruch nach Satzzeichen, ohne Zeichenvorgabe: Pfade und Komposita brechen um.",
    whole="Umbruch nach Satzzeichen, Bindestriche geschützt: Pfade brechen um, Komposita bleiben ganz.",
    text_before="Senden Sie jede Anfrage an ",
    text_between=" und lesen Sie anschließend die Nur-Lese-Kopie unter ",
    text_after=" im Tarif mit Ratenbegrenzung.",
)

# 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 column every setting is broken to, narrow enough that the paths reach its edge.
COLUMN = 125.0

# What the last setting puts in place of each hyphen: a hyphen no line breaks after.
NON_BREAKING_HYPHEN = "\u2011"

# The width a heading is set in, on one line.
HEADING_WIDTH = 400.0

# The left edge of every setting, stacked one above the other.
X = 80.0

# Where the heading of the first setting of a page stands.
TOP = 760.0

# The room left between the bottom of one setting and the heading of the next, where the
# next one's place follows from the one above it.
GAP = 30.0


def panel(
    content: hqf_pdf.Content,
    handle: hqf_pdf.FontHandle,
    label: str,
    flow: hqf_pdf.TextFlow,
    text: str,
    top: float,
) -> float:
    """Set `label` at `top` and `text` below it in the column, broken by `flow`, with
    the column's edge around it, and return where the setting ends."""
    heading = hqf_pdf.TextFlow(handle, 8.0)
    heading_lines = heading.break_lines(label, HEADING_WIDTH)
    heading.draw(content, heading_lines, X, top, HEADING_WIDTH)

    text_top = top - 20.0
    lines = flow.break_lines(text, COLUMN)
    flow.draw(content, lines, X, text_top, COLUMN)

    # The column's edge, so the lines can be read against it.
    height = flow.height(lines)
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(0.8))
    content.set_line_width(0.5)
    content.rect(X, text_top - height, COLUMN, height)
    content.stroke()
    content.restore_state()
    return text_top - height


def setting(
    handle: hqf_pdf.FontHandle,
    break_after: str = "",
    punctuation: bool = False,
    replacing: dict[str, hqf_pdf.Replacement] | None = None,
) -> hqf_pdf.TextFlow:
    """Return the flow every panel sets its text in, breaking as asked and making
    the replacements `replacing` names first."""
    return hqf_pdf.TextFlow(
        handle,
        11.0,
        leading=15.0,
        align=hqf_pdf.Align.Justify,
        break_after=break_after,
        break_at_punctuation=punctuation,
        replacing=replacing or {},
    )


def pages(
    handle: hqf_pdf.FontHandle, words: Words
) -> list[list[tuple[str, hqf_pdf.TextFlow]]]:
    """Return the settings the page sets its text in, each under its heading, two to
    a page: the last two share theirs, so that the compound the one breaks can be
    seen whole in the other.

    Every setting sets the same text; only where a word may break inside itself
    differs between them.
    """
    return [
        [
            (words.plain, setting(handle)),
            (words.breaking, setting(handle, "/-")),
        ],
        [
            (words.punctuation, setting(handle, punctuation=True)),
            (
                words.whole,
                setting(
                    handle,
                    punctuation=True,
                    replacing={"-": hqf_pdf.Replacement.each(NON_BREAKING_HYPHEN)},
                ),
            ),
        ],
    ]


def set_page(
    content: hqf_pdf.Content,
    handle: hqf_pdf.FontHandle,
    panels: list[tuple[str, hqf_pdf.TextFlow]],
    text: str,
) -> float:
    """Set each of `panels` one under the other from the top of a page, and return
    where the last one ends."""
    top = TOP
    bottom = TOP
    for label, flow in panels:
        bottom = panel(content, handle, label, flow, text, top)
        top = bottom - GAP
    return bottom


def main() -> None:
    language = _language.from_environment()
    words = _language.words_of(WORDS, language)
    text = words.text()

    # 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("break_after.pdf", language)).stem)

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    handle = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))

    for panels in pages(handle, words):
        content = hqf_pdf.Content()
        set_page(content, handle, panels, text)
        page = hqf_pdf.Page.a4()
        page.set_content(content)
        document.add_page(page)

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


if __name__ == "__main__":
    main()
