"""Sets the same tabulated text under each of the three tab methods, then a
table of contents whose dots run out to a right-hand stop, so what each method
does to a column can be read off the page.

The Python twin of the `write_tab_stops` example in Rust. A grey rule is drawn
at every stop the block was told to use, so the runs can be seen landing on
them. The relative method steps the pen on from wherever it stands, so its
columns never line up; the typewriter method steps to the next multiple of the
tab size, so they do; a ruler sends each tabulation to a stop of its own and
sets the run on it, centred over it, or lined up on its decimal point.

The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks which
set the page is labelled in. The step the typewriter panel works in is not one
of them: its label is set around the very number the panel is given, so no
language can announce a grid the page does not land on.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Sequence

import _language
import _licence
import _out

import hqf_pdf

# The left edge every panel is set from.
X = 80.0

# The room every panel has to fill.
COLUMN = 430.0

# The distance from a panel's heading down to the block below it.
DROP = 20.0

# The reference, the quantity and the unit price of each row the first three panels set.
# What each row is a category of is a word, and stands in `Words` under the same order.
ROWS = (
    ("A-1180", "12", "18.50"),
    ("B-2050", "4", "7.05"),
    ("C-9004", "120", "1 250.00"),
)

# The page each entry of the table of contents is on.
PAGES = ("7", "18", "34", "61", "112")

# The step the typewriter panel works in, which is the grid its columns land on and the
# number its label spells out.
GRID = 96.0

# How much of a line one tabulation opens when a flow is told nothing, as the first
# panel's label spells it out.
SHARE = "7.5%"

# The stops the ruler panel sends its three tabulations to.
RULER = [130.0, 260.0, 380.0]


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

    # The label over the relative panel, set around the share of a line one tabulation
    # opens under the default.
    relative: tuple[str, str]
    # The label over the typewriter panel, set around the step it works in.
    typewriter: tuple[str, str]
    # The label over the ruler panel.
    ruler: str
    # The label over the table of contents.
    listing: str
    # What each of the four columns is called.
    columns: tuple[str, str, str, str]
    # What each row of `ROWS` is a category of.
    categories: tuple[str, str, str]
    # The entries of the table of contents, each set on the page `PAGES` gives it.
    contents: tuple[str, str, str, str, str]

    def relative_label(self) -> str:
        """The label over the relative panel, around the share it opens."""
        return f"{self.relative[0]}{SHARE}{self.relative[1]}"

    def typewriter_label(self) -> str:
        """The label over the typewriter panel, around the step it works in."""
        return f"{self.typewriter[0]}{GRID:g}{self.typewriter[1]}"

    def rows(self) -> str:
        """The four columns the first three panels set, a tabulation between each pair
        and a heading over them."""
        lines = ["\t".join(self.columns)]
        lines.extend(
            f"{row[0]}\t{category}\t{row[1]}\t{row[2]}"
            for row, category in zip(ROWS, self.categories)
        )
        return "\n".join(lines)

    def listed(self) -> str:
        """The table of contents, each entry with its page number behind a
        tabulation."""
        return "\n".join(
            f"{entry}\t{page}" for entry, page in zip(self.contents, PAGES)
        )


# The page in English.
ENGLISH = Words(
    relative=(
        "Relative, the default: each tabulation steps the pen on by ",
        " of the column from where it stands, so the rows do not line up.",
    ),
    typewriter=(
        "Typewriter: each tabulation lands on the next multiple of ",
        " points, so the rows line up on that grid.",
    ),
    ruler="Ruler: a stop of its own for each tabulation, the second centred on it "
    "and the third lined up on its decimal point, the heading, which carries "
    "none, ending on the stop.",
    listing="A ruler stop at the far edge, the run ending on it and the leader "
    "filling the room the tabulation opened.",
    columns=("Reference", "Category", "Quantity", "Unit price"),
    categories=("Binding", "Cover", "Paper"),
    contents=(
        "Getting started",
        "The page model",
        "Fonts and the text engine",
        "Tables and their columns",
        "Digital signatures",
    ),
)

# The page in French.
FRENCH = Words(
    relative=(
        "Relative, par défaut : chaque tabulation avance la plume de ",
        " de la colonne depuis où elle est, donc les lignes ne s'alignent pas.",
    ),
    typewriter=(
        "Machine à écrire : chaque tabulation tombe sur le multiple suivant de ",
        " points, donc les lignes s'alignent sur cette grille.",
    ),
    ruler="Règle : un taquet par tabulation, le deuxième centré dessus et le "
    "troisième aligné sur son point décimal, l'en-tête, qui n'en porte pas, "
    "finissant sur le taquet.",
    listing="Un taquet de règle au bord, la course finissant dessus et les points "
    "de conduite remplissant la place que la tabulation a ouverte.",
    columns=("Référence", "Catégorie", "Quantité", "Prix unitaire"),
    categories=("Reliure", "Couverture", "Papier"),
    contents=(
        "Pour commencer",
        "Le modèle de page",
        "Les polices et le moteur de texte",
        "Les tableaux et leurs colonnes",
        "Les signatures numériques",
    ),
)

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


def panel(
    content: hqf_pdf.Content,
    handle: hqf_pdf.FontHandle,
    top: float,
    label: str,
    flow: hqf_pdf.TextFlow,
    text: str,
    stops: Sequence[float],
) -> None:
    """Draws one panel: its heading, the tabulated block below it, and a grey rule
    at every stop the block was told to use."""
    heading = hqf_pdf.TextFlow(handle, 8.0)
    heading_lines = heading.break_lines(label, COLUMN)
    heading.draw(content, heading_lines, X, top, COLUMN)

    text_top = top - DROP
    lines = flow.break_lines(text, COLUMN)
    height = flow.height(lines)

    # The stops first, so the text is read over them rather than under them.
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb(0.78, 0.78, 0.82))
    content.set_line_width(0.5)
    for stop in stops:
        content.move_to(X + stop, text_top)
        content.line_to(X + stop, text_top - height)
        content.stroke()
    content.restore_state()

    flow.draw(content, lines, X, text_top, COLUMN)


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

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

    content = hqf_pdf.Content()
    rows = words.rows()

    # Relative, which is what a flow does when it is told nothing: every tabulation
    # steps the pen on by the tab size from wherever it stands, so a longer entry pushes
    # the whole row along with it.
    relative = hqf_pdf.TextFlow(handle, 11.0, leading=16.0)
    panel(content, handle, 780.0, words.relative_label(), relative, rows, [])

    # A typewriter's tab bar: every tabulation lands on the next multiple of the tab
    # size, so the rows line up on a grid however wide their entries are.
    typewriter = hqf_pdf.TextFlow(
        handle,
        11.0,
        leading=16.0,
        tab_method=hqf_pdf.TabMethod.Typewriter,
        tab_size=hqf_pdf.TabSize.points(GRID),
    )
    panel(
        content,
        handle,
        660.0,
        words.typewriter_label(),
        typewriter,
        rows,
        [GRID, GRID * 2.0, GRID * 3.0, GRID * 4.0],
    )

    # A ruler names its stops, and each one says how the run it opens sits about it: the
    # quantities are centred, the prices line up on their decimal point.
    ruler = hqf_pdf.TextFlow(
        handle,
        11.0,
        leading=16.0,
        tab_method=hqf_pdf.TabMethod.Ruler,
        tab_ruler=RULER,
        tab_alignments=[
            hqf_pdf.TabAlign.Left,
            hqf_pdf.TabAlign.Center,
            hqf_pdf.TabAlign.Decimal,
        ],
    )
    panel(content, handle, 540.0, words.ruler, ruler, rows, RULER)

    # The stop sits at the far edge and the run ends on it, so the numbers are flush
    # right whatever they are worth, and the leader fills the room the tabulation
    # opened.
    contents = hqf_pdf.TextFlow(
        handle,
        11.0,
        leading=16.0,
        tab_method=hqf_pdf.TabMethod.Ruler,
        tab_ruler=[COLUMN],
        tab_alignments=[hqf_pdf.TabAlign.Right],
        leader=".",
    )
    panel(content, handle, 420.0, words.listing, contents, words.listed(), [COLUMN])

    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()
