"""Writes a shelf card that marks its lines the three ways a text can be marked.

The Python twin of the `write_text_ornaments` example in Rust. A rule under the
words, a rule through the letters, a rule over the top: the engine takes all
three from the font rather than from a coordinate, so they follow whatever size
the text is set in. The heading and the mark across the foot each carry a shadow
— the same words set once more, offset and paler, under the words themselves.

Every word is held in `Words`, once per language, and `HQF_PDF_LANG` picks which
set is set. The prices are not words: they stand in one table, shared by both
languages.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# Where the card's text begins, in points from the left edge of the page.
LEFT = 56.0
# How wide it runs.
WIDTH = 483.0
# The size the heading is set in.
TITLE_SIZE = 32.0
# The size the standfirst and the closing note are set in.
NOTE_SIZE = 10.5
# The size a line of the shelf is set in.
ROW_SIZE = 13.0
# The top of the heading's box.
TITLE_TOP = 800.0
# The top of the standfirst.
STANDFIRST_TOP = 748.0
# Where the line under the standfirst is drawn.
HEAD_RULE = 730.0
# The top of the first line of the shelf.
FIRST_ROW = 720.0
# How far apart the lines of the shelf sit.
ROW_STEP = 26.0
# The top of the line that adds the three together.
TOTAL_TOP = 626.0
# The top of the first line of the legend.
LEGEND_TOP = 570.0
# How far apart the lines of the legend sit.
LEGEND_STEP = 24.0
# How far into the box the legend's marked words are set.
LEGEND_INDENT = 24.0
# The top of the closing note.
NOTE_TOP = 470.0
# The box the mark is laid across: its lower-left corner and its size.
MARK_BOX = (LEFT, 120.0, WIDTH, 250.0)


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

    # What stands across the head of the card.
    title: str
    # The line under it, which says what the card is showing.
    standfirst: str
    # What each line of the shelf is called, in the order they are set.
    goods: tuple[str, str, str]
    # What stands against the sum of the three.
    total_label: str
    # What opens the legend.
    legend: str
    # The three marks, each named by words set the way the name says.
    under: str
    through: str
    over: str
    # The note at the foot, which says where each mark comes from.
    note: str
    # What the mark laid across the card reads.
    mark: str

    def marks(self) -> tuple[str, str, str]:
        """The three marks, in the order the legend sets them."""
        return (self.under, self.through, self.over)


# The card in English.
ENGLISH = Words(
    title="The half-price shelf",
    standfirst="What a shop marks by hand, set by the engine instead.",
    goods=(
        "Atlas of the northern coast",
        "Field guide to the estuary birds",
        "The lighthouse keepers, a history",
    ),
    total_label="The three together",
    legend="The three marks, each set the way it names:",
    under="a rule under the words",
    through="a rule through the letters",
    over="a rule over the top",
    note=(
        "No rule on this card was placed by hand. The one under the words and the "
        "one through the letters sit where the face itself says they go, and the "
        "one over the top rides the ascender the face states; all three follow the "
        "size the text is set in. The heading carries a copy of itself behind it, "
        "offset and paler, which is what keeps large lettering readable over a "
        "photograph. The mark laid across the foot carries one too, turned with it, "
        "so the copy falls the same way on the sheet as it does under the heading."
    ),
    mark="HALF PRICE",
)

# The card in French.
FRENCH = Words(
    title="L'étagère à moitié prix",
    standfirst="Ce qu'un commerçant marque à la main, posé par le moteur.",
    goods=(
        "Atlas de la côte nord",
        "Guide des oiseaux de l'estuaire",
        "Les gardiens du phare, une histoire",
    ),
    total_label="Les trois ensemble",
    legend="Les trois marques, chacune posée comme elle se nomme :",
    under="un trait sous les mots",
    through="un trait au milieu des lettres",
    over="un trait au-dessus",
    note=(
        "Aucun trait de cette fiche n'a été placé à la main. Celui du dessous et "
        "celui du milieu sont là où le dessin de la lettre les met, et celui du "
        "haut suit l'ascendante que la police annonce ; tous trois suivent la "
        "taille du texte. Le titre porte une copie de lui-même derrière lui, "
        "décalée et plus pâle, ce qui garde un grand lettrage lisible sur une "
        "photographie. La mention posée en travers du pied en porte une aussi, "
        "tournée avec elle, si bien que la copie tombe sur la feuille du même côté "
        "que sous le titre."
    ),
    mark="MOITIÉ PRIX",
)

# Every language the card is written in.
WORDS = {_language.ENGLISH: ENGLISH, _language.FRENCH: FRENCH}

# What each line of the shelf was marked at and what it is marked at now.
#
# Figures are not words: both languages set the same ones, and the engine writes a
# decimal point in every language it is asked for.
PRICES = (
    ("24.00 EUR", "12.00 EUR"),
    ("18.00 EUR", "9.00 EUR"),
    ("30.00 EUR", "15.00 EUR"),
)

# What the three come to together.
TOTAL = "36.00 EUR"


def shadow_ink() -> hqf_pdf.Rgb:
    """The colour every shadow on the card is drawn in."""
    return hqf_pdf.Rgb.gray(0.78)


def heading(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
    """Draws the heading, which sets itself once more behind itself."""
    style = hqf_pdf.Style(font, TITLE_SIZE)
    text = hqf_pdf.RichText(
        shadow=hqf_pdf.Shadow(1.5, -1.5, shadow_ink())
    ).push(words.title, style)
    lines = text.break_lines(WIDTH)
    text.draw(content, lines, LEFT, TITLE_TOP, WIDTH)


def standfirst(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
    """Draws the standfirst and the line under it."""
    flow = hqf_pdf.TextFlow(font, NOTE_SIZE)
    lines = flow.break_lines(words.standfirst, WIDTH)
    flow.draw(content, lines, LEFT, STANDFIRST_TOP, WIDTH)

    content.set_line_width(1.0)
    content.move_to(LEFT, HEAD_RULE)
    content.line_to(LEFT + WIDTH, HEAD_RULE)
    content.stroke()


def shelf_line(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    name: str,
    prices: tuple[str, str],
    top: float,
) -> None:
    """Draws one line of the shelf: what it is called, then what it was marked at
    struck through and what it is marked at now."""
    plain = hqf_pdf.Style(font, ROW_SIZE)
    struck = hqf_pdf.Style(font, ROW_SIZE, strikethrough=True)

    left = hqf_pdf.RichText().push(name, plain)
    left.draw(content, left.break_lines(WIDTH), LEFT, top, WIDTH)

    right = (
        hqf_pdf.RichText(align=hqf_pdf.Align.Right)
        .push(prices[0], struck)
        .push("   ", plain)
        .push(prices[1], plain)
    )
    right.draw(content, right.break_lines(WIDTH), LEFT, top, WIDTH)


def total(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the line that adds the three together, the sum ruled over the top the
    way a column of figures is ruled before it is totalled."""
    plain = hqf_pdf.Style(font, ROW_SIZE)
    ruled = hqf_pdf.Style(font, ROW_SIZE, overline=True)

    left = hqf_pdf.RichText().push(words.total_label, plain)
    left.draw(content, left.break_lines(WIDTH), LEFT, TOTAL_TOP, WIDTH)

    right = hqf_pdf.RichText(align=hqf_pdf.Align.Right).push(TOTAL, ruled)
    right.draw(content, right.break_lines(WIDTH), LEFT, TOTAL_TOP, WIDTH)


def legend(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the legend: a line that says what follows, then the three marks, each
    named by words set the way the name says."""
    plain = hqf_pdf.Style(font, ROW_SIZE)
    opening = hqf_pdf.RichText().push(words.legend, plain)
    opening.draw(content, opening.break_lines(WIDTH), LEFT, LEGEND_TOP, WIDTH)

    marked = (
        hqf_pdf.Style(font, ROW_SIZE, underline=True),
        hqf_pdf.Style(font, ROW_SIZE, strikethrough=True),
        hqf_pdf.Style(font, ROW_SIZE, overline=True),
    )

    # The tops step down one line at a time rather than being multiplied out, so the
    # Rust twin adds the same numbers in the same order.
    top = LEGEND_TOP
    for name, style in zip(words.marks(), marked):
        top -= LEGEND_STEP
        line = hqf_pdf.RichText().push(name, style)
        room = WIDTH - LEGEND_INDENT
        line.draw(content, line.break_lines(room), LEFT + LEGEND_INDENT, top, room)


def note(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the closing note, which says where each mark comes from."""
    flow = hqf_pdf.TextFlow(font, NOTE_SIZE, align=hqf_pdf.Align.Justify)
    lines = flow.break_lines(words.note, WIDTH)
    flow.draw(content, lines, LEFT, NOTE_TOP, WIDTH)


def mark(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the mark laid across the foot of the card, which carries a shadow of
    its own."""
    x, y, width, height = MARK_BOX
    stamp = hqf_pdf.Stamp(
        font,
        words.mark,
        slant=hqf_pdf.Slant.Up,
        color=hqf_pdf.Rgb(0.72, 0.16, 0.16),
        shadow=hqf_pdf.Shadow(2.5, -2.5, shadow_ink()),
    )
    stamp.draw(content, x, y, width, height)


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("text_ornaments.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()))

    content = hqf_pdf.Content()
    heading(content, font, words)
    standfirst(content, font, words)

    # The tops step down one line at a time rather than being multiplied out, so the
    # Rust twin adds the same numbers in the same order.
    top = FIRST_ROW
    for name, prices in zip(words.goods, PRICES):
        shelf_line(content, font, name, prices, top)
        top -= ROW_STEP

    total(content, font, words)
    legend(content, font, words)
    note(content, font, words)
    mark(content, font, words)

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