"""Lays a stack out by hand, one box at a time, and says under each box what became
of the stack in it.

The Python twin of the `write_stack_by_hand` example in Rust. A delivery slip holding
a heading, a picture, a paragraph, a rule and a closing line is fitted into a first
box too short for it, which says where the slip resumes; the rest goes into a second
box from the cursor the first hands back, and that one says it is done. A poster whose
picture is taller than its box says so and places nothing. Each box is outlined, each
block it placed is outlined inside it, and under each a line says what became of the
stack and how much of the box it took.

Every word is held in `Words`, once per language, and `HQF_PDF_LANG` picks which set is
drawn. The heights and the ordinates are figures, and stand the same in every language.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The picture the delivery slip and the poster hold.
PICTURE = _out.ROOT / "crates" / "hqf-pdf" / "tests" / "images" / "logo.png"


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

    # The heading of the page.
    heading: str
    # The delivery slip's heading, its paragraph and its closing line.
    slip: tuple[str, str, str]
    # What became of a stack in a box: every block placed; the box full, the rest
    # resuming at block `{block}`; a block taller than the box.
    outcomes: tuple[str, str, str]
    # How much of a box its blocks took; `{height}`, `{top}` and `{bottom}` stand for
    # the figures.
    took: str
    # What a box that placed nothing says.
    nothing: str


# The page in English.
ENGLISH = Words(
    heading="A stack laid out by hand, one box at a time",
    slip=(
        "Delivery slip",
        (
            "Three parcels left the depot this morning and are due at the site before "
            "noon. The driver will ring the bell at the gate; if nobody answers, the "
            "parcels go back to the depot and wait there for two days."
        ),
        "Keep this slip until the parcels are signed for.",
    ),
    outcomes=(
        "Every block placed.",
        "The box is full; the rest resumes at block {block}.",
        "A block is taller than the box: nothing more can be placed.",
    ),
    took="{height} pt taken, from {top} down to {bottom}.",
    nothing="Nothing placed in this box.",
)

# The page in French.
FRENCH = Words(
    heading="Une pile mise en page à la main, cadre par cadre",
    slip=(
        "Bon de livraison",
        (
            "Trois colis ont quitté le dépôt ce matin et doivent arriver au chantier "
            "avant midi. Le chauffeur sonnera au portail ; si personne ne répond, les "
            "colis repartent au dépôt et y attendent deux jours."
        ),
        "Gardez ce bon jusqu’à la signature des colis.",
    ),
    outcomes=(
        "Tous les blocs sont placés.",
        "Le cadre est plein ; la suite reprend au bloc {block}.",
        "Un bloc est plus haut que le cadre : plus rien ne peut être placé.",
    ),
    took="{height} pt occupés, de {top} jusqu’à {bottom}.",
    nothing="Rien n’est placé dans ce cadre.",
)

# The page in Dutch.
DUTCH = Words(
    heading="Een stapel met de hand opgemaakt, één kader tegelijk",
    slip=(
        "Afleverbon",
        (
            "Drie colli verlieten vanochtend het depot en worden vóór twaalf uur op "
            "de bouwplaats verwacht. De chauffeur belt aan bij het hek; als niemand "
            "opendoet, gaan de colli terug naar het depot en wachten daar twee dagen."
        ),
        "Bewaar deze bon tot de ontvangst van de colli getekend is.",
    ),
    outcomes=(
        "Elk blok geplaatst.",
        "Het kader is vol; de rest gaat verder bij blok {block}.",
        "Een blok is hoger dan het kader: er kan niets meer geplaatst worden.",
    ),
    took="{height} pt gebruikt, van {top} tot {bottom}.",
    nothing="Niets geplaatst in dit kader.",
)

# The page in Portuguese.
PORTUGUESE = Words(
    heading="Uma pilha disposta à mão, uma caixa de cada vez",
    slip=(
        "Talão de entrega",
        (
            "Três volumes saíram do armazém esta manhã e devem chegar à obra antes do "
            "meio-dia. O motorista toca à campainha do portão; se ninguém responder, "
            "os volumes voltam ao armazém e esperam lá dois dias."
        ),
        "Guarde este talão até a receção dos volumes ser assinada.",
    ),
    outcomes=(
        "Todos os blocos colocados.",
        "A caixa está cheia; o resto continua no bloco {block}.",
        "Um bloco é mais alto do que a caixa: não se pode colocar mais nada.",
    ),
    took="{height} pt ocupados, de {top} a {bottom}.",
    nothing="Nada colocado nesta caixa.",
)

# The page in Italian.
ITALIAN = Words(
    heading="Una pila disposta a mano, un riquadro alla volta",
    slip=(
        "Documento di trasporto",
        (
            "Tre colli sono partiti dal deposito stamattina e devono arrivare in "
            "cantiere prima di mezzogiorno. L’autista suonerà al cancello; se nessuno "
            "risponde, i colli tornano al deposito e ci restano due giorni."
        ),
        "Conservate questo documento finché la consegna non è firmata.",
    ),
    outcomes=(
        "Tutti i blocchi collocati.",
        "Il riquadro è pieno; il resto riprende dal blocco {block}.",
        "Un blocco è più alto del riquadro: non si può collocare altro.",
    ),
    took="{height} pt occupati, da {top} a {bottom}.",
    nothing="Nulla collocato in questo riquadro.",
)

# The page in Spanish.
SPANISH = Words(
    heading="Una pila colocada a mano, caja a caja",
    slip=(
        "Volante de entrega",
        (
            "Tres paquetes salieron del almacén esta mañana y deben llegar a la obra "
            "antes de mediodía. El conductor llamará al timbre de la verja; si nadie "
            "responde, los paquetes vuelven al almacén y esperan allí dos días."
        ),
        "Guarde este volante hasta que se firme la recepción de los paquetes.",
    ),
    outcomes=(
        "Todos los bloques colocados.",
        "La caja está llena; el resto sigue en el bloque {block}.",
        "Un bloque es más alto que la caja: no puede colocarse nada más.",
    ),
    took="{height} pt ocupados, de {top} a {bottom}.",
    nothing="Nada colocado en esta caja.",
)

# The page in German.
GERMAN = Words(
    heading="Ein Stapel, von Hand aufgebaut, ein Rahmen nach dem anderen",
    slip=(
        "Lieferschein",
        (
            "Drei Pakete haben heute Morgen das Depot verlassen und sollen vor zwölf "
            "Uhr auf der Baustelle sein. Der Fahrer klingelt am Tor; öffnet niemand, "
            "gehen die Pakete ins Depot zurück und warten dort zwei Tage."
        ),
        "Diesen Schein aufbewahren, bis der Empfang der Pakete unterschrieben ist.",
    ),
    outcomes=(
        "Jeder Block platziert.",
        "Der Rahmen ist voll; der Rest geht bei Block {block} weiter.",
        "Ein Block ist höher als der Rahmen: Nichts weiter kann platziert werden.",
    ),
    took="{height} pt belegt, von {top} hinunter bis {bottom}.",
    nothing="Nichts in diesem Rahmen platziert.",
)

# 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 colour the captions and the slip's rule are set in.
MUTED = hqf_pdf.Rgb(0.45, 0.45, 0.45)

# How wide the heading may run, in points: the sheet less a margin either side.
WIDTH = 483.0

# The left edge of everything the page draws.
LEFT = 56.0


def slip(font, logo, words) -> hqf_pdf.Stack:
    """The delivery slip: a heading, the picture, a paragraph, a rule and a closing
    line."""
    stack = hqf_pdf.Stack()
    stack.push(
        hqf_pdf.Block.text(
            hqf_pdf.TextFlow(font, 13.0), words.slip[0], keep_together=True
        )
    )
    stack.push(hqf_pdf.Block.space(6.0))
    stack.push(hqf_pdf.Block.image(logo, 60.0, 30.0))
    stack.push(hqf_pdf.Block.space(6.0))
    stack.push(
        hqf_pdf.Block.text(hqf_pdf.TextFlow(font, 9.5, leading=13.0), words.slip[1])
    )
    stack.push(hqf_pdf.Block.space(6.0))
    stack.push(hqf_pdf.Block.rule(hqf_pdf.Stroke(0.6, color=MUTED)))
    stack.push(hqf_pdf.Block.space(6.0))
    stack.push(
        hqf_pdf.Block.text(hqf_pdf.TextFlow(font, 8.5, color=MUTED), words.slip[2])
    )
    return stack


def poster(logo) -> hqf_pdf.Stack:
    """A poster holding nothing but a picture taller than any box it is given."""
    stack = hqf_pdf.Stack()
    stack.push(hqf_pdf.Block.image(logo, 200.0, 300.0))
    return stack


# The three boxes the page lays out by hand: the first too short for the slip, the
# second for the rest of it, the third for the poster.
HAND_BOXES = (
    hqf_pdf.StackFrame(LEFT, 730.0, 220.0, 95.0),
    hqf_pdf.StackFrame(LEFT + 263.0, 730.0, 220.0, 260.0),
    hqf_pdf.StackFrame(LEFT, 420.0, 220.0, 150.0),
)


def caption(content, font, frame, lines) -> None:
    """Set `lines` under `frame`, one after the other."""
    flow = hqf_pdf.TextFlow(font, 8.0, color=MUTED)
    y = frame.top - frame.height - 6.0
    for line in lines:
        broken = flow.break_lines(line, frame.width)
        flow.draw(content, broken, frame.x, y, frame.width)
        y -= flow.height(broken)


def outline_frame(content, frame) -> None:
    """Outline `frame` in grey."""
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb(0.6, 0.6, 0.6))
    content.set_line_width(0.5)
    content.rect(frame.x, frame.top - frame.height, frame.width, frame.height)
    content.stroke()
    content.restore_state()


def laid(content, font, placed, words) -> None:
    """Outline `placed`'s box and each block it placed, draw the blocks, and set under
    the box what became of the stack there."""
    frame = placed.frame
    outline_frame(content, frame)
    placed.draw(content)
    boxes = [at for __, at in placed.block_boxes()]
    hqf_pdf.Guides.hairline(hqf_pdf.Rgb(0.85, 0.0, 0.6)).outline_all(content, boxes)

    if placed.done:
        outcome = words.outcomes[0]
    else:
        outcome = words.outcomes[1].replace("{block}", str(placed.next.block + 1))
    if placed.is_empty:
        took = words.nothing
    else:
        took = (
            words.took.replace("{height}", f"{placed.height:.0f}")
            .replace("{top}", f"{placed.top:.0f}")
            .replace("{bottom}", f"{placed.bottom:.0f}")
        )
    caption(content, font, frame, [outcome, took])


def by_hand(font, logo, words) -> hqf_pdf.Content:
    """The page: the delivery slip laid down two boxes by hand, the second taking up
    where the first stopped, and the poster that fits no box."""
    content = hqf_pdf.Content()
    heading = hqf_pdf.TextFlow(font, 14.0)
    heading.draw(content, heading.break_lines(words.heading, WIDTH), LEFT, 780.0, WIDTH)

    laid_out = slip(font, logo, words)
    if len(laid_out):
        first = laid_out.fit(HAND_BOXES[0], hqf_pdf.StackCursor())
        laid(content, font, first, words)
        if not first.done:
            rest = laid_out.fit(HAND_BOXES[1], first.next)
            laid(content, font, rest, words)
    # A picture taller than the box is refused rather than placed: the box says so, and
    # holds nothing.
    try:
        placed = poster(logo).fit(HAND_BOXES[2], hqf_pdf.StackCursor())
        laid(content, font, placed, words)
    except hqf_pdf.BlockTooTall:
        outline_frame(content, HAND_BOXES[2])
        caption(content, font, HAND_BOXES[2], [words.outcomes[2], words.nothing])
    return content


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

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

    logo = document.add_image(hqf_pdf.Image.from_path(PICTURE))

    page = hqf_pdf.Page.a4()
    page.set_content(by_hand(font, logo, words))
    document.add_page(page)

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


if __name__ == "__main__":
    main()
