"""Sets one line of statuses twice: above in a house font alone, below with a
second font behind it, so the characters the house font has no glyph for can be
seen appearing.

The Python twin of the `write_font_fallback` example in Rust. The house font
here is IBM Plex Sans, which draws a tick but neither a star, an umbrella nor a
cross. Set alone, those three come out as the empty box every font carries for a
character it does not have. Set through a chain, each is handed to DejaVu Sans,
which does have it, and the line is drawn in as many fonts as it takes.

The closing note counts the stretches the chain cuts the line into, and names the
two fonts as the files themselves name them.

The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks which set
is drawn. The four symbols are not language: they stand the same in every one.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# Where the committed fonts sit.
FONT_DIR = _out.ROOT / "crates" / "hqf-pdf" / "tests" / "fonts"

# The four symbols the statuses are marked with, in the order they are drawn.
#
# The tick is in both fonts; the star, the umbrella and the cross are in the second
# alone, which is what the two settings show.
MARKS = ("✓", "★", "☂", "✗")

# What stands between two statuses on the line. A run of spaces would be set as one, so
# the statuses are held apart by a character.
GAP = " · "


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

    What is not language stays out of it: the four symbols are marks, and the two font
    names are what the files call themselves.
    """

    # The line across the top of the page.
    title: str
    # Why a document needs a font behind its own, in two sentences.
    why: str
    # The heading over the setting in the house font alone.
    alone: str
    # The heading over the setting that falls back.
    chained: str
    # The four statuses, each drawn after its mark.
    statuses: tuple[str, str, str, str]
    # The closing note, cut where the count and the two font names go into it.
    note_before: str
    note_between: str
    note_joiner: str
    note_after: str
    # The three headings over the list of marks.
    head_mark: str
    head_status: str
    head_font: str

    def line(self) -> str:
        """Return the line of statuses, each after its mark."""
        return GAP.join(f"{mark} {status}" for mark, status in zip(MARKS, self.statuses))

    def note(self, stretches: int, house: str, behind: str) -> str:
        """Return the closing note, with the count and the two font names in it."""
        return (
            f"{self.note_before}{stretches}{self.note_between}"
            f"{house}{self.note_joiner}{behind}{self.note_after}"
        )


# The page in English.
ENGLISH = Words(
    title="A font behind the font",
    why=(
        "A house font rarely covers every character a document has to draw. "
        "Without a second font behind it, a character it has no glyph for comes "
        "out as the empty box below, and a reader is left guessing.\n"
        "A chain hands that character to the next font instead, so the line is "
        "set in as many fonts as it takes and every mark is drawn."
    ),
    alone="The house font alone: what it has no glyph for, it cannot draw.",
    chained="The house font, with a second one behind it.",
    statuses=("Paid", "Priority", "Weather hold", "Refused"),
    note_before="The line above is cut into ",
    note_between=" stretches, drawn by two fonts: ",
    note_joiner=", then ",
    note_after=(
        ". The words are in the first; the marks it lacks are in the second."
    ),
    head_mark="Mark",
    head_status="Status",
    head_font="Drawn by",
)

# The page in French.
FRENCH = Words(
    title="Une police derrière la police",
    why=(
        "Une police de marque couvre rarement tous les caractères qu'un document "
        "doit dessiner. Sans une seconde police derrière elle, un caractère "
        "qu'elle ne connaît pas sort en case vide, comme ci-dessous, et le "
        "lecteur reste devant une devinette.\n"
        "Une chaîne confie plutôt ce caractère à la police suivante : la ligne "
        "est composée dans autant de polices qu'il le faut, et chaque marque est "
        "dessinée."
    ),
    alone="La police de marque seule : ce qu'elle ne connaît pas, elle ne le dessine pas.",
    chained="La police de marque, avec une seconde police derrière elle.",
    statuses=("Payé", "Prioritaire", "Attente météo", "Refusé"),
    note_before="La ligne du dessus est découpée en ",
    note_between=" morceaux, dessinés par deux polices : ",
    note_joiner=", puis ",
    note_after=(
        ". Les mots viennent de la première, les marques qui lui manquent "
        "de la seconde."
    ),
    head_mark="Marque",
    head_status="État",
    head_font="Dessinée par",
)


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

# The left edge of everything on the page.
X = 60.0

# The width every block is broken to.
ROOM = 475.0

# The baseline the title sits on.
TITLE_TOP = 780.0

# The top of the block that says why the page matters.
WHY_TOP = 750.0

# The top of the heading over the setting in the house font alone.
ALONE_TOP = 650.0

# The top of the heading over the setting that falls back.
CHAINED_TOP = 560.0

# The top of the closing note.
NOTE_TOP = 470.0

# How far a setting sits below the heading over it.
UNDER_HEADING = 34.0

# The size the statuses are set in.
LINE_SIZE = 15.0

# The top of the list that names the font behind each mark.
LIST_TOP = 400.0

# How far apart two rows of that list sit.
ROW_DROP = 18.0

# Where the three columns of that list begin.
COLUMNS = (X, X + 46.0, X + 176.0)


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

    out = _out.output_path(Path(_language.file_name("font_fallback.pdf", language)).stem)

    house = hqf_pdf.Font.from_path(FONT_DIR / "IBMPlexSans-Regular.otf")
    behind = hqf_pdf.Font.from_path(FONT_DIR / "DejaVuSans.ttf")
    house_name = house.postscript_name
    behind_name = behind.postscript_name

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    plain = document.add_font(house)
    spare = document.add_font(behind)
    chain = plain.falling_back_to(spare)

    content = hqf_pdf.Content()

    title = hqf_pdf.TextFlow(plain, 18.0)
    title.draw(content, title.break_lines(words.title, ROOM), X, TITLE_TOP, ROOM)

    # The block that says why the page matters is set through the chain: it is ordinary
    # prose, and the chain draws ordinary prose exactly as the house font alone would.
    why = hqf_pdf.TextFlow(chain, 10.0, leading=14.0)
    why.draw(content, why.break_lines(words.why, ROOM), X, WHY_TOP, ROOM)

    # The same line twice: once in the house font on its own, once through the chain.
    # Only the font differs.
    for top, heading, handle in (
        (ALONE_TOP, words.alone, plain),
        (CHAINED_TOP, words.chained, chain),
    ):
        label = hqf_pdf.TextFlow(plain, 9.0)
        label.draw(content, label.break_lines(heading, ROOM), X, top, ROOM)

        statuses = hqf_pdf.TextFlow(handle, LINE_SIZE)
        statuses.draw(
            content,
            statuses.break_lines(line, ROOM),
            X,
            top - UNDER_HEADING,
            ROOM,
        )

    # What the chain made of the line, counted off the chain itself rather than written
    # down: the note says what the page above it holds.
    note = words.note(len(chain.runs(line)), house_name, behind_name)
    closing = hqf_pdf.TextFlow(chain, 10.0, leading=14.0)
    closing.draw(content, closing.break_lines(note, ROOM), X, NOTE_TOP, ROOM)

    # Which font drew which mark, read off the chain: a mark the house font has is drawn
    # by it, and the rest by the font behind it.
    head_row = hqf_pdf.TextFlow(plain, 9.0)
    for column, head in zip(COLUMNS, (words.head_mark, words.head_status, words.head_font)):
        head_row.draw(content, head_row.break_lines(head, ROOM), column, LIST_TOP, ROOM)

    for row, (mark, status) in enumerate(zip(MARKS, words.statuses)):
        runs = chain.runs(mark)
        drawn_by = house_name if runs and runs[0].font == plain.name else behind_name
        top = LIST_TOP - ROW_DROP * float(row + 1)
        for column, cell in zip(COLUMNS, (mark, status, drawn_by)):
            flow = hqf_pdf.TextFlow(chain, 10.0)
            flow.draw(content, flow.break_lines(cell, ROOM), column, top, ROOM)

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