"""Draws a checking sheet for a pack somebody else sent: one row per page of it,
each showing that page and the stamp it was given.

The Python twin of the `write_form_cells` example in Rust. Two kinds of form stand
in the cells, and they behave alike. The first column holds a page taken whole out
of another document; the second holds a stamp drawn here, once, and placed in every
row that was given it. Neither asks its column for a width nor its row for a height:
each is fitted whole and undistorted into the rectangle the table leaves it, which
is the bargain a picture cell makes. Both are given a rectangle taller than their
own proportions need, so the width runs out first and each stands in the middle of
the height it does not fill.

A page brought in belongs to the sheets it is put on rather than to the document, so
every page the table lands on is handed the pages it shows.

The pack is written by the example a moment before it is read, so this runs on any
machine with nothing to fetch.

What the sheet says is held in `Words`, once per language, and `HQF_PDF_LANG` picks
which set is printed. What a page is called is also what a reader hears in its place:
the alternative text of a form cell reads the same field as the line beside it, so
the two cannot disagree.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# A4's width, which every page here is drawn across.
PAGE_WIDTH = 595.276

# The margin the checking sheet and the pack both keep.
MARGIN = 56.0

# The width left between the margins, which the table is fitted across.
TABLE_WIDTH = PAGE_WIDTH - 2 * MARGIN

# The top of the table on the page, and the lowest it may reach.
TOP = 736.0
BOTTOM = 64.0

# How many pages the pack holds, which is how many rows the table takes.
SHEETS = 4

# How many stamps are drawn. A row names one of them; two rows may name the same one.
MARKS = 3

# The width of the column the pages are shown in, and of the column the stamps stand in.
SHEET_COLUMN = 96.0
MARK_COLUMN = 76.0

# The height every row of the checking sheet takes.
ROW_HEIGHT = 140.0

# The colour the heading is painted on.
HEAD_FILL = hqf_pdf.Rgb.gray(0.86)

# The ink the sheet is written in, and the grey of its second lines.
INK = hqf_pdf.Rgb(0.1, 0.1, 0.12)
MUTED = hqf_pdf.Rgb(0.42, 0.42, 0.45)

# The colour the sender's stationery is printed in.
SENDER_INK = hqf_pdf.Rgb(0.11, 0.33, 0.55)

# The ink of each stamp, in the order the stamps are drawn.
MARK_INKS = [
    hqf_pdf.Rgb(0.09, 0.42, 0.24),
    hqf_pdf.Rgb(0.65, 0.38, 0.04),
    hqf_pdf.Rgb(0.36, 0.36, 0.42),
]

# The size of a stamp, in points, and the size its word is set at.
STAMP = (62.0, 24.0)
STAMP_TEXT = 9.0

# Who sent the pack, and where they are. A firm writes its name one way, whatever
# language reads it.
SENDER_NAME = "Meridian Optics SAS"
SENDER_TOWN = "18 avenue des Peupliers, 69100 Villeurbanne"

# What each page of the pack is filed as. A reference is not language.
REFERENCES = ["DN-4471", "PL-4471", "CC-4471", "WB-4471"]

# The stamp each page was given, in the order the rows are printed. The first page and
# the third carry the same one, which is drawn once.
MARK_OF = [0, 1, 0, 2]

# How many lines of the sender's own writing each page of the pack carries.
LINES_OF = [24, 19, 26, 15]

# The share of the measure each line of the sender's writing runs to, taken in turn: a
# page of writing is not a block, and the short line ends a paragraph.
LINE_SHARES = [1.0, 0.94, 0.98, 0.9, 0.96, 0.52]


@dataclass(frozen=True)
class Words:
    """Every word the checking sheet prints, in one language."""

    # The line at the head of the sheet.
    title: str
    # The two lines under it, saying what each row shows.
    intro: tuple[str, str]
    # The heading of each column, in the order the columns are set.
    headings: tuple[str, str, str, str]
    # What each page of the pack is called, in the order they arrived. A form cell
    # carries the same words as the words a reader hears in its place.
    sheets: tuple[str, str, str, str]
    # What was found on each of them.
    notes: tuple[str, str, str, str]
    # What each stamp says, which is also what it is called.
    marks: tuple[str, str, str]
    # The line under the table, saying what a stamp placed twice costs.
    foot: str


# The checking sheet in English.
ENGLISH = Words(
    title="What arrived, page by page",
    intro=(
        "Each row shows a page of the pack as it arrived, and the stamp that page "
        "was given.",
        "Both are forms, and both are fitted to the cell the table leaves them, "
        "whole and undistorted.",
    ),
    headings=("Sheet", "Stamp", "What was found", "Reference"),
    sheets=(
        "Delivery note",
        "Packing list",
        "Certificate of conformity",
        "Weighbridge ticket",
    ),
    notes=(
        "The whole page, at a sixth of its size: nothing was redrawn, and nothing "
        "was left out.",
        "Two lines disagree with the delivery note, so the sheet waits on the "
        "sender.",
        "Signed and dated, and filed with the batch it covers.",
        "The weight reads, the time does not, so the sheet is held until a clean "
        "copy arrives.",
    ),
    marks=("Accepted", "Query", "On hold"),
    foot=(
        "The first row and the third carry one stamp: a drawing costs its "
        "operators once, wherever it lands."
    ),
)

# The checking sheet in French.
FRENCH = Words(
    title="Ce qui est arrivé, feuille par feuille",
    intro=(
        "Chaque ligne montre une feuille du lot telle qu'elle est arrivée, et le "
        "tampon qu'elle a reçu.",
        "Les deux sont des formulaires, ajustés à la cellule que le tableau leur "
        "laisse, entiers et sans déformation.",
    ),
    headings=("Feuille", "Tampon", "Ce qui a été relevé", "Référence"),
    sheets=(
        "Bon de livraison",
        "Liste de colisage",
        "Certificat de conformité",
        "Ticket de pesée",
    ),
    notes=(
        "La page entière, au sixième de sa taille : rien n'a été redessiné, rien "
        "n'a été laissé de côté.",
        "Deux lignes ne concordent pas avec le bon de livraison, la feuille attend "
        "donc l'expéditeur.",
        "Signé et daté, classé avec le lot qu'il couvre.",
        "Le poids se lit, l'heure non : la feuille est retenue jusqu'à l'arrivée "
        "d'une copie nette.",
    ),
    marks=("Accepté", "À vérifier", "En attente"),
    foot=(
        "Le même tampon est en première et en troisième ligne : un dessin ne "
        "coûte ses instructions qu'une fois."
    ),
)


# 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 text(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    size: float,
    x: float,
    y: float,
    ink: hqf_pdf.Rgb,
    line: str,
) -> None:
    """Draws one line of text in `ink`, with its left edge at `x` and its baseline
    at `y`."""
    content.set_fill(ink)
    content.draw_text(font, size, x, y, line)


def wash(ink: hqf_pdf.Rgb) -> hqf_pdf.Rgb:
    """The pale wash a stamp is printed on, which is its own ink most of the way to
    white."""
    return hqf_pdf.Rgb(
        ink.r * 0.16 + 0.84,
        ink.g * 0.16 + 0.84,
        ink.b * 0.16 + 0.84,
    )


def stamp(font: hqf_pdf.FontHandle, word: str, ink: hqf_pdf.Rgb) -> hqf_pdf.Drawing:
    """One stamp: a washed box, a rule around it, and the word it carries in the
    middle."""
    width, height = STAMP
    content = hqf_pdf.Content()

    content.set_fill(wash(ink))
    content.rect(0.0, 0.0, width, height)
    content.fill()

    content.set_stroke(ink)
    content.set_line_width(1.2)
    content.rect(0.6, 0.6, width - 1.2, height - 1.2)
    content.stroke()

    measured = font.measure(word, STAMP_TEXT)
    text(content, font, STAMP_TEXT, (width - measured) / 2.0, 8.6, ink, word)

    return hqf_pdf.Drawing(content, hqf_pdf.Rect(0.0, 0.0, width, height))


def sheet_page(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    title: str,
    reference: str,
    lines: int,
) -> None:
    """One page of the pack: the sender's letterhead, what the page is, its
    reference, and the sender's own writing under it.

    The writing is a block of rules rather than words. What this example needs of the
    pack is that its pages arrive whole, at whatever they were made.
    """
    content.set_fill(wash(SENDER_INK))
    content.rect(0.0, 742.0, PAGE_WIDTH, 100.0)
    content.fill()
    content.set_fill(SENDER_INK)
    content.rect(0.0, 738.0, PAGE_WIDTH, 4.0)
    content.fill()

    text(content, font, 20.0, MARGIN, 788.0, INK, SENDER_NAME)
    text(content, font, 9.0, MARGIN, 766.0, MUTED, SENDER_TOWN)

    text(content, font, 26.0, MARGIN, 690.0, SENDER_INK, title)
    text(content, font, 11.0, MARGIN, 666.0, MUTED, reference)

    content.set_stroke(hqf_pdf.Rgb.gray(0.74))
    content.set_line_width(3.4)
    y = 630.0
    for line in range(lines):
        share = LINE_SHARES[line % len(LINE_SHARES)]
        content.move_to(MARGIN, y)
        content.line_to(share * TABLE_WIDTH + MARGIN, y)
        content.stroke()
        y -= 22.0

    text(content, font, 8.0, MARGIN, 60.0, MUTED, SENDER_NAME)


def a_pack(font_path: Path, words: Words) -> bytes:
    """The pack the checking sheet reads: one page per sheet, as its sender made
    them."""
    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    font = document.add_font(hqf_pdf.Font.from_path(font_path))

    for index, title in enumerate(words.sheets):
        content = hqf_pdf.Content()
        sheet_page(content, font, title, REFERENCES[index], LINES_OF[index])

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

    return document.to_bytes()


def checking_sheet(
    font: hqf_pdf.FontHandle,
    words: Words,
    sheets: list[hqf_pdf.ImportedPage],
    stamps: list[hqf_pdf.DrawingHandle],
) -> hqf_pdf.Table:
    """The checking sheet: a heading, then one row per page of the pack, holding that
    page and its stamp."""
    # The two form columns are fixed widths, the note takes what is left, and the
    # reference is as wide as its own widest cell.
    columns = hqf_pdf.Columns(
        [
            hqf_pdf.ColumnWidth.points(SHEET_COLUMN),
            hqf_pdf.ColumnWidth.points(MARK_COLUMN),
            hqf_pdf.ColumnWidth.fraction(1.0),
            hqf_pdf.ColumnWidth.content(),
        ],
        TABLE_WIDTH,
    )

    table = hqf_pdf.Table(columns)
    table.header(1)
    table.rule(hqf_pdf.Rule.frame(), hqf_pdf.Stroke(0.8))
    hairline = hqf_pdf.Stroke(0.25, hqf_pdf.Rgb.gray(0.75))
    table.rule(hqf_pdf.Rule.horizontal_other(), hairline)
    table.rule(hqf_pdf.Rule.vertical_other(), hairline)
    table.rule(hqf_pdf.Rule.horizontal(1), hqf_pdf.Stroke(0.8))

    pad = hqf_pdf.Padding.symmetric(6.0, 5.0)

    table.push(
        hqf_pdf.Row(
            [
                hqf_pdf.Cell(font, 9.0, label, padding=pad, fill=HEAD_FILL)
                for label in words.headings
            ],
            min_height=20.0,
        )
    )

    for index, name in enumerate(words.sheets):
        mark = MARK_OF[index]
        table.push(
            hqf_pdf.Row(
                [
                    hqf_pdf.Cell.imported_page(
                        sheets[index],
                        alt=name,
                        valign=hqf_pdf.VAlign.Middle,
                        padding=pad,
                    ),
                    hqf_pdf.Cell.drawing(
                        stamps[mark],
                        alt=words.marks[mark],
                        valign=hqf_pdf.VAlign.Middle,
                        padding=pad,
                    ),
                    hqf_pdf.Cell(
                        font, 9.0, f"{name}\n{words.notes[index]}", padding=pad
                    ),
                    hqf_pdf.Cell(font, 9.0, REFERENCES[index], padding=pad),
                ],
                min_height=ROW_HEIGHT,
            )
        )

    return table


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("form_cells.pdf", language)).stem)
    font_path = _out.font_path()

    pack = hqf_pdf.Reader(a_pack(font_path, words))

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

    # Each page of the pack comes in as a form, and each stamp is drawn once.
    sheets = [document.import_page(pack, index) for index in range(SHEETS)]
    stamps = [
        document.add_drawing(stamp(font, word, ink))
        for word, ink in zip(words.marks, MARK_INKS)
    ]

    content = hqf_pdf.Content()
    text(content, font, 15.0, MARGIN, 786.0, INK, words.title)
    y = 768.0
    for line in words.intro:
        text(content, font, 9.0, MARGIN, y, MUTED, line)
        y -= 12.0

    table = checking_sheet(font, words, sheets, stamps)
    placed = table.fit(MARGIN, TOP, TOP - BOTTOM, 0)
    placed.draw(content)

    text(content, font, 9.0, MARGIN, placed.bottom - 18.0, MUTED, words.foot)

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    # A page may only draw what its resources name, and a page brought in from elsewhere
    # belongs to the sheets it is put on.
    for sheet in sheets:
        sheet.add_to(page)
    document.add_page(page)

    written = document.write(out)
    print(
        f"wrote {out}: {written} bytes, {len(sheets)} pages brought in, "
        f"{MARKS} stamps drawn"
    )


if __name__ == "__main__":
    main()
