"""Stamps a page with what it is: DRAFT, COPY, PAID.

The Python twin of the `write_stamp` example in Rust. A stamp is scaled to the box it
is given rather than set in a size, so the same line stamps a whole page, a table
cell and a form field without a single size being worked out by the caller. It is
turned first and scaled second, so that what stays inside the box is the line as the
reader sees it. The first page draws four of them across boxes of very different
shapes.

The stamp takes whatever colour it is given and nothing else: it is text, and a
caller that wants it under the page's own ink draws it first.

Under each caption, a second line says how far the stamp turned its line.

The three pages after it carry watermarks instead, handed to the document once rather
than drawn page by page. The first runs from page two to the end, even pages only, set
at 96 points whatever the page measures and painted at 35 per cent opacity. The second
runs from page three, odd pages only, under the page's own ink, fitted to half the page
and a shadow behind it. The third lies flat over every page from the fourth on, small
and pale. Each later page says how many of the three it carries, and page three how far
its shadow is offset. The last page carries a stamp of another kind besides: APPROVED
pressed above the page rather than drawn into it, which a reader can hide, delete or
leave off the paper.

The stamped words, the title, the four captions, the watermark and the line heading
each later page are held in `Words`, once per language, and `HQF_PDF_LANG` picks which
set is drawn. Each set keeps its last word the longest, because the last box is the one
that shows a long word coming out smaller rather than cut.

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

from __future__ import annotations

import math
from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

MARGIN = 56.0

# The size the watermark is set in, in points, on every page it covers.
WATERMARK_SIZE = 96.0

# The share of its colour the watermark is painted with.
WATERMARK_ALPHA = 0.35

# The size the first page's title is set in, in points.
TITLE_SIZE = 14.0

# The size the line heading each later page is set in, in points: a sentence rather than
# a title, kept inside the margins in every language.
LATER_SIZE = 11.0

# The colour the stamp drawn above the last page reads in.
APPROVED_GREEN = hqf_pdf.Rgb(0.1, 0.5, 0.2)


@dataclass(frozen=True)
class Stamped:
    """One box to stamp: where it is, how big, and which way the line runs."""

    x: float
    y: float
    width: float
    height: float
    slant: hqf_pdf.Slant
    color: hqf_pdf.Rgb


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

    The stamped words are language: a reader stamps a document in the language they read
    it in. Each set keeps its last word the longest, because the last box is the one
    that shows a long word coming out smaller rather than cut.
    """

    # The line at the head of the page.
    title: str
    # What each box is stamped with, in the order the boxes are laid out.
    stamps: tuple[str, str, str, str]
    # What is written under each box.
    captions: tuple[str, str, str, str]
    # The watermark laid on the even pages after the first.
    watermark: str
    # The watermark laid under the odd pages from the third.
    under: str
    # The watermark laid flat over every page from the fourth.
    flat: str
    # How far a stamp turned its line; `{degrees}` stands for the angle.
    turned: str
    # How many watermarks a page carries; `{count}` stands for them.
    carried: str
    # How far the shadow of the second watermark is offset; `{dx}` and `{dy}` stand for
    # the offsets.
    shadowed: str
    # The line at the head of each page after the first.
    later: tuple[str, str, str]
    # The word the stamp drawn above the last page reads.
    approved: str
    # What the last page says that stamp is, the first of the two lines a reader that
    # cannot see the page is told it says.
    above: str
    # What the last page says a reader can do with that stamp, the second of the two
    # lines.
    offered: str


# The page in English.
ENGLISH = Words(
    title="One stamp, four boxes",
    stamps=(
        "DRAFT",
        "COPY",
        "PAID",
        "CANCELLED",
    ),
    captions=(
        "A wide box: the line runs up its diagonal, which is nearly flat.",
        "A tall box, stamped the other way.",
        "Flat across the middle, and wide enough that the width is what binds.",
        "A longer word in a shorter box: the size follows, nothing is cut.",
    ),
    watermark="SPECIMEN",
    under="INTERNAL",
    flat="ARCHIVED",
    turned="Turned {degrees}°.",
    carried="Watermarks on this page: {count}.",
    shadowed="The shadow is offset by ({dx}, {dy}) pt.",
    later=(
        "Page two is even: the first watermark lies across it, at 96 points.",
        "Page three is odd: the second lies under it, fitted to half the page.",
        "Page four is even again: the first once more, and the third over both.",
    ),
    approved="APPROVED",
    above="The green stamp “Approved” lies above the page.",
    offered="It can be hidden, deleted or left off the paper.",
)

# The page in French.
FRENCH = Words(
    title="Un tampon, quatre boîtes",
    stamps=(
        "BROUILLON",
        "COPIE",
        "PAYÉ",
        "ANNULATION",
    ),
    captions=(
        "Une boîte large : le mot monte le long de sa diagonale, presque à plat.",
        "Une boîte haute, tamponnée dans l’autre sens.",
        "À plat au milieu, et assez large pour que ce soit la largeur qui limite.",
        (
            "Un mot plus long dans une boîte plus basse : la taille suit, rien n’est "
            "coupé."
        ),
    ),
    watermark="SPÉCIMEN",
    under="INTERNE",
    flat="ARCHIVÉ",
    turned="Tourné de {degrees}°.",
    carried="Filigranes sur cette page : {count}.",
    shadowed="L’ombre est décalée de ({dx} ; {dy}) pt.",
    later=(
        "La page deux est paire : le premier filigrane la traverse, en corps 96.",
        "La page trois est impaire : le deuxième passe dessous, à demi-page.",
        "La page quatre est paire : le premier à nouveau, et le troisième par-dessus.",
    ),
    approved="APPROUVÉ",
    above="Le tampon vert « Approuvé » est posé sur la page.",
    offered="On peut le masquer, l’effacer ou ne pas l’imprimer.",
)

# The page in Dutch.
DUTCH = Words(
    title="Eén stempel, vier kaders",
    stamps=(
        "CONCEPT",
        "KOPIE",
        "BETAALD",
        "GEANNULEERD",
    ),
    captions=(
        "Een breed kader: de regel loopt langs de diagonaal, die bijna vlak is.",
        "Een hoog kader, de andere kant op gestempeld.",
        "Vlak door het midden, en breed genoeg dat de breedte beslist.",
        "Een langer woord in een korter kader: de grootte volgt, er wordt niets afgesneden.",
    ),
    watermark="SPECIMEN",
    under="INTERN",
    flat="GEARCHIVEERD",
    turned="Gedraaid {degrees}°.",
    carried="Watermerken op deze pagina: {count}.",
    shadowed="De schaduw is verschoven met ({dx}, {dy}) pt.",
    later=(
        "Pagina twee is even: het eerste watermerk ligt eroverheen, op 96 punten.",
        "Pagina drie is oneven: het tweede ligt eronder, passend op een halve pagina.",
        "Pagina vier is weer even: nog eens het eerste, en het derde over beide.",
    ),
    approved="GOEDGEKEURD",
    above="De groene stempel ‘Goedgekeurd’ ligt over de pagina heen.",
    offered="Je kunt hem verbergen, verwijderen of buiten de afdruk laten.",
)

# The page in Portuguese.
PORTUGUESE = Words(
    title="Um carimbo, quatro caixas",
    stamps=(
        "RASCUNHO",
        "CÓPIA",
        "PAGO",
        "CANCELADO",
    ),
    captions=(
        "Uma caixa larga: a linha sobe pela diagonal, que é quase plana.",
        "Uma caixa alta, carimbada no outro sentido.",
        "Plano pelo meio, e tão largo que é a largura que manda.",
        "Uma palavra mais longa numa caixa mais curta: o tamanho acompanha, nada é cortado.",
    ),
    watermark="ESPÉCIME",
    under="INTERNO",
    flat="ARQUIVADO",
    turned="Rodado {degrees}°.",
    carried="Marcas de água nesta página: {count}.",
    shadowed="A sombra está deslocada ({dx}, {dy}) pt.",
    later=(
        "A página dois é par: a primeira marca de água atravessa-a, a 96 pontos.",
        "A página três é ímpar: a segunda fica por baixo, ajustada a meia página.",
        "A página quatro volta a ser par: de novo a primeira, e a terceira por cima de ambas.",
    ),
    approved="APROVADO",
    above="O carimbo verde «Aprovado» fica por cima da página.",
    offered="Um leitor de PDF pode ocultá-lo, apagá-lo ou deixá-lo fora da impressão.",
)

# The page in Italian.
ITALIAN = Words(
    title="Un timbro, quattro riquadri",
    stamps=(
        "BOZZA",
        "COPIA",
        "PAGATO",
        "ANNULLATO",
    ),
    captions=(
        "Un riquadro largo: la riga sale lungo la diagonale, quasi piatta.",
        "Un riquadro alto, timbrato nell’altro senso.",
        "Piatto al centro, e largo abbastanza da essere vincolato dalla larghezza.",
        "Una parola più lunga in un riquadro più corto: il corpo segue, niente è tagliato.",
    ),
    watermark="CAMPIONE",
    under="INTERNO",
    flat="ARCHIVIATO",
    turned="Ruotato di {degrees}°.",
    carried="Filigrane su questa pagina: {count}.",
    shadowed="L’ombra è spostata di ({dx}, {dy}) pt.",
    later=(
        "La pagina due è pari: la prima filigrana la attraversa, a 96 punti.",
        "La pagina tre è dispari: la seconda sta sotto, adattata a mezza pagina.",
        "La pagina quattro è di nuovo pari: ancora la prima, e la terza sopra entrambe.",
    ),
    approved="APPROVATO",
    above="Il timbro verde «Approvato» è sovrapposto alla pagina.",
    offered="Lo si può nascondere, eliminare o escludere dalla stampa.",
)

# The page in Spanish.
SPANISH = Words(
    title="Un sello, cuatro cajas",
    stamps=(
        "BORRADOR",
        "COPIA",
        "PAGADO",
        "CANCELADO",
    ),
    captions=(
        "Una caja ancha: la línea sube por su diagonal, que es casi plana.",
        "Una caja alta, sellada en el otro sentido.",
        "Plano por el centro, y tan ancho que lo que manda es el ancho.",
        "Una palabra más larga en una caja más corta: el tamaño se adapta, nada se corta.",
    ),
    watermark="MUESTRA",
    under="INTERNO",
    flat="ARCHIVADO",
    turned="Girado {degrees}°.",
    carried="Marcas de agua en esta página: {count}.",
    shadowed="La sombra está desplazada ({dx}, {dy}) pt.",
    later=(
        "La página dos es par: la primera marca de agua la cruza, a 96 puntos.",
        "La página tres es impar: la segunda va debajo, ajustada a media página.",
        "La página cuatro vuelve a ser par: otra vez la primera, y la tercera sobre ambas.",
    ),
    approved="APROBADO",
    above="Sobre la página hay un sello verde «Aprobado».",
    offered="Un lector de PDF puede ocultarlo, eliminarlo o excluirlo de la impresión.",
)

# The page in German.
GERMAN = Words(
    title="Ein Stempel, vier Rahmen",
    stamps=(
        "ENTWURF",
        "KOPIE",
        "BEZAHLT",
        "STORNIERT",
    ),
    captions=(
        "Ein breiter Rahmen: Die Zeile läuft seine fast flache Diagonale hinauf.",
        "Ein hoher Rahmen, andersherum gestempelt.",
        "Flach durch die Mitte und so breit, dass die Breite die Größe bestimmt.",
        "Ein längeres Wort in einem kürzeren Rahmen: Die Größe folgt, nichts wird abgeschnitten.",
    ),
    watermark="MUSTER",
    under="INTERN",
    flat="ARCHIVIERT",
    turned="Um {degrees}° gedreht.",
    carried="Wasserzeichen auf dieser Seite: {count}.",
    shadowed="Der Schatten ist um ({dx}, {dy}) pt versetzt.",
    later=(
        "Seite zwei ist gerade: Das erste Wasserzeichen liegt quer darüber, in 96 Punkt.",
        "Seite drei ist ungerade: Das zweite liegt darunter, auf die halbe Seite eingepasst.",
        "Seite vier ist wieder gerade: noch einmal das erste, und das dritte über beiden.",
    ),
    approved="GENEHMIGT",
    above="Der grüne Stempel „Genehmigt“ liegt über der Seite.",
    offered="Man kann ihn ausblenden, löschen oder beim Drucken weglassen.",
)

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


def boxes() -> list[Stamped]:
    """The boxes, from the top of the page down."""
    return [
        Stamped(
            MARGIN,
            606.0,
            483.0,
            150.0,
            hqf_pdf.Slant.Up,
            hqf_pdf.Rgb(0.75, 0.16, 0.16),
        ),
        Stamped(
            MARGIN,
            300.0,
            150.0,
            260.0,
            hqf_pdf.Slant.Down,
            hqf_pdf.Rgb(0.25, 0.35, 0.7),
        ),
        Stamped(
            268.0,
            300.0,
            271.0,
            260.0,
            hqf_pdf.Slant.Flat,
            hqf_pdf.Rgb(0.1, 0.45, 0.25),
        ),
        Stamped(
            MARGIN,
            130.0,
            483.0,
            110.0,
            hqf_pdf.Slant.Up,
            hqf_pdf.Rgb(0.45, 0.45, 0.45),
        ),
    ]


def outline(content: hqf_pdf.Content, stamped: Stamped) -> None:
    """Draws the outline of a box, so that what the stamp was fitted to can be seen."""
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(0.8))
    content.set_line_width(0.5)
    content.rect(stamped.x, stamped.y, stamped.width, stamped.height)
    content.stroke()
    content.restore_state()


def caption(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    stamped: Stamped,
    row: float,
    text: str,
) -> None:
    """Writes line `row` of the caption under a box, the first one at row zero."""
    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    content.draw_text(font, 8.5, stamped.x, stamped.y - 14.0 - 11.0 * row, text)
    content.restore_state()


def watermarks(font, words, translucent, shadow) -> tuple[hqf_pdf.Watermark, ...]:
    """The three watermarks the pages after the first carry: the first on the even pages
    from page two, the second under the odd pages from page three with `shadow` behind
    it, the third flat over every page from page four. The first and the third are
    drawn under `translucent`."""
    return (
        hqf_pdf.Watermark(
            font,
            words.watermark,
            first=1,
            parity=hqf_pdf.PageParity.Even,
            size=WATERMARK_SIZE,
            color=hqf_pdf.Rgb(0.75, 0.16, 0.16),
            state=translucent,
        ),
        hqf_pdf.Watermark(
            font,
            words.under,
            first=2,
            parity=hqf_pdf.PageParity.Odd,
            stacking=hqf_pdf.Stacking.Under,
            span=0.5,
            slant=hqf_pdf.Slant.Down,
            color=hqf_pdf.Rgb.gray(0.88),
            shadow=shadow,
        ),
        hqf_pdf.Watermark(
            font,
            words.flat,
            first=3,
            parity=hqf_pdf.PageParity.Every,
            stacking=hqf_pdf.Stacking.Over,
            size=28.0,
            slant=hqf_pdf.Slant.Flat,
            color=hqf_pdf.Rgb(0.2, 0.35, 0.65),
            state=translucent,
        ),
    )


def press_approved(page, content, font, words: Words, baseline: float) -> None:
    """Writes the two lines saying what the stamp above the last page is, from `baseline`
    down, and presses that stamp under them, above the page."""
    for text in (words.above, words.offered):
        content.draw_text(font, 10.0, MARGIN, baseline, text)
        baseline -= 14.0
    page.add_annotation(
        hqf_pdf.StampAnnotation(MARGIN, baseline - 56.0, 200.0, 56.0)
        .icon(hqf_pdf.StampIcon.Approved)
        .contents(f"{words.above} {words.offered}")
        .words(
            hqf_pdf.Stamp(
                font,
                words.approved,
                slant=hqf_pdf.Slant.Flat,
                color=APPROVED_GREEN,
            )
        )
    )


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

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

    translucent = document.add_ext_gstate(hqf_pdf.ExtGState(fill_alpha=WATERMARK_ALPHA))
    shadow = hqf_pdf.Shadow(3.0, -3.0, hqf_pdf.Rgb.gray(0.7))
    marks = watermarks(font, words, translucent, shadow)
    carried = [
        sum(1 for mark in marks if mark.covers(index))
        for index in range(1, 1 + len(words.later))
    ]
    for mark in marks:
        document.add_watermark(mark)

    content = hqf_pdf.Content()
    content.draw_text(font, TITLE_SIZE, MARGIN, 780.0, words.title)

    stamped_boxes = boxes()
    for stamped, text, caption_text in zip(stamped_boxes, words.stamps, words.captions):
        outline(content, stamped)
        stamp = hqf_pdf.Stamp(font, text, slant=stamped.slant, color=stamped.color)
        stamp.draw(content, stamped.x, stamped.y, stamped.width, stamped.height)
        caption(content, font, stamped, 0.0, caption_text)
        angle = stamp.angle(stamped.width, stamped.height)
        turned = words.turned.replace("{degrees}", f"{math.degrees(angle):.0f}")
        caption(content, font, stamped, 1.0, turned)

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    document.add_page(page)

    for index, line in enumerate(words.later):
        content = hqf_pdf.Content()
        content.draw_text(font, LATER_SIZE, MARGIN, 780.0, line)
        said = [words.carried.replace("{count}", str(carried[index]))]
        if index == 1:
            said.append(
                words.shadowed.replace("{dx}", f"{shadow.dx:.0f}").replace(
                    "{dy}", f"{shadow.dy:.0f}"
                )
            )
        baseline = 760.0
        for text in said:
            content.draw_text(font, 10.0, MARGIN, baseline, text)
            baseline -= 14.0
        page = hqf_pdf.Page.a4()
        if index == len(words.later) - 1:
            press_approved(page, content, font, words, baseline)
        page.set_content(content)
        document.add_page(page)

    written = document.write(out)
    pages = 1 + len(words.later)
    print(f"wrote {out}: {written} bytes, {len(stamped_boxes)} stamps, {pages} pages")


if __name__ == "__main__":
    main()
