"""Writes a catalogue whose first column is a picture rather than text.

The Python twin of the `write_picture_cells` example in Rust. The four pictures
are wildly different sizes — one is nine hundred pixels across, another sixteen
— and no two share the same proportions, yet the column they sit in is the width
the table gave it and every row is as tall as its own text. A picture asks its
column for no width and its row for no height: it is fitted, whole and
undistorted, into whatever rectangle it ends up with, and where it sits in that
rectangle is the alignment the cell already had.

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

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The margins, and the box the table is fitted into.
MARGIN = 56.0
TOP = 760.0
BOTTOM = 64.0
PAGE_WIDTH = 595.276
TABLE_WIDTH = PAGE_WIDTH - 2 * MARGIN

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

# The height every row of the catalogue takes, whatever its text needs.
ROW_HEIGHT = 54.0

# The pictures the catalogue shows, committed with the tests.
PICTURES = Path(__file__).resolve().parents[2] / "hqf-pdf" / "tests" / "images"


@dataclass(frozen=True)
class Item:
    """One line of the catalogue, save what it is called and what is said about it.

    A picture, a corner and a price are the same in every language; the line reads its
    words from `Words` instead.
    """

    picture: str
    across: hqf_pdf.Align
    down: hqf_pdf.VAlign
    reference: str
    price: str


# The catalogue's lines, in the order they are printed. The pictures are shown against
# each edge of their cells in turn, so that the room a picture does not fill can be seen
# going to one side or the other.
ITEMS = [
    Item(
        "hqf_development.png",
        hqf_pdf.Align.Left,
        hqf_pdf.VAlign.Top,
        "BN-900",
        "148.00",
    ),
    Item(
        "photo.jpg",
        hqf_pdf.Align.Center,
        hqf_pdf.VAlign.Middle,
        "PH-160",
        "24.50",
    ),
    Item(
        "logo.png",
        hqf_pdf.Align.Right,
        hqf_pdf.VAlign.Bottom,
        "SQ-96",
        "9.90",
    ),
    Item(
        "bilevel.png",
        hqf_pdf.Align.Center,
        hqf_pdf.VAlign.Middle,
        "RL-16",
        "3.75",
    ),
]


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

    # The heading of each column, in the order the columns are set.
    headings: tuple[str, str, str, str]
    # What each item is called, in the order the lines are printed. A picture cell
    # carries the same words as the words a reader hears in its place.
    names: tuple[str, str, str, str]
    # What is said about each item.
    notes: tuple[str, str, str, str]


# The catalogue in English.
ENGLISH = Words(
    headings=("Picture", "Item", "Reference", "Price, €"),
    names=("Wide banner", "Photograph", "Square mark", "Two-colour rule"),
    notes=(
        "Nine hundred pixels across and half as tall, drawn against the top "
        "left of its cell.",
        "Four to three, in the middle of its cell — the room it does not fill "
        "is shared either side.",
        "As tall as it is wide, against the bottom right. The height runs out "
        "first, so nothing is left below it.",
        "Sixteen pixels by eight, drawn as large as the cell allows: a picture "
        "is fitted to its cell, not to its own pixels.",
    ),
)

# The catalogue in French.
FRENCH = Words(
    headings=("Image", "Article", "Référence", "Prix, €"),
    names=("Bandeau large", "Photographie", "Marque carrée", "Filet bicolore"),
    notes=(
        "Neuf cents pixels de large et deux fois moins haut, calé en haut "
        "à gauche de sa cellule.",
        ("Au format quatre tiers, au milieu de sa cellule : la place qu’elle laisse se"
         " répartit des deux côtés."),
        ("Aussi haute que large, calée en bas à droite. C’est la hauteur qui limite en"
         " premier : rien ne reste en dessous."),
        ("Seize pixels sur huit, dessiné aussi grand que la cellule le permet : une "
         "image s’ajuste à sa cellule, pas à ses propres pixels."),
    ),
)

# The catalogue in Dutch.
DUTCH = Words(
    headings=("Afbeelding", "Artikel", "Referentie", "Prijs, €"),
    names=("Brede banner", "Foto", "Vierkant merk", "Tweekleurige lijn"),
    notes=(
        "Negenhonderd pixels breed en half zo hoog, tegen de linkerbovenhoek van de "
        "cel getekend.",
        "Vier bij drie, midden in de cel: de ruimte die ze niet vult, wordt aan beide "
        "kanten verdeeld.",
        "Even hoog als breed, tegen de rechteronderhoek. De hoogte raakt eerst op, "
        "dus blijft er eronder niets over.",
        "Zestien pixels bij acht, zo groot getekend als de cel toelaat: een "
        "afbeelding wordt aan haar cel aangepast, niet aan haar pixels.",
    ),
)

# The catalogue in Portuguese.
PORTUGUESE = Words(
    headings=("Imagem", "Artigo", "Referência", "Preço, €"),
    names=("Faixa larga", "Fotografia", "Marca quadrada", "Filete bicolor"),
    notes=(
        "Novecentos píxeis de largura e metade de altura, encostada ao canto superior "
        "esquerdo da célula.",
        "Quatro por três, no meio da célula: o espaço que não preenche reparte-se "
        "pelos dois lados.",
        "Tão alta como larga, encostada ao canto inferior direito. A altura esgota-se "
        "primeiro, por isso nada fica por baixo.",
        "Dezasseis píxeis por oito, desenhada tão grande quanto a célula permite: uma "
        "imagem ajusta-se à célula, não aos seus píxeis.",
    ),
)

# The catalogue in Italian.
ITALIAN = Words(
    headings=("Immagine", "Articolo", "Riferimento", "Prezzo, €"),
    names=("Banner largo", "Fotografia", "Marchio quadrato", "Filetto bicolore"),
    notes=(
        "Novecento pixel di larghezza e metà di altezza, contro l’angolo in alto a "
        "sinistra della sua cella.",
        "Quattro per tre, al centro della sua cella: lo spazio che non riempie è "
        "diviso ai due lati.",
        "Alta quanto larga, contro l’angolo in basso a destra. L’altezza finisce "
        "prima, così sotto non resta nulla.",
        "Sedici pixel per otto, disegnata grande quanto la cella consente: "
        "un’immagine si adatta alla cella, non ai suoi pixel.",
    ),
)

# The catalogue in Spanish.
SPANISH = Words(
    headings=("Imagen", "Artículo", "Referencia", "Precio, €"),
    names=("Banda ancha", "Fotografía", "Marca cuadrada", "Filete bicolor"),
    notes=(
        "Novecientos píxeles de ancho y la mitad de alto, contra la esquina superior "
        "izquierda de su celda.",
        "Cuatro por tres, en el centro de su celda: el sitio que no llena se reparte "
        "a cada lado.",
        "Tan alta como ancha, contra la esquina inferior derecha. La altura se agota "
        "antes, así que no queda nada debajo.",
        "Dieciséis píxeles por ocho, dibujada tan grande como deja la celda: una "
        "imagen se ajusta a su celda, no a sus píxeles.",
    ),
)

# The catalogue in German.
GERMAN = Words(
    headings=("Bild", "Artikel", "Referenz", "Preis (€)"),
    names=("Breites Banner", "Foto", "Quadratisches Zeichen", "Zweifarbige Linie"),
    notes=(
        "Neunhundert Pixel breit und halb so hoch, oben links in seiner Zelle "
        "gezeichnet.",
        "Vier zu drei, in der Mitte seiner Zelle — der Raum, den es nicht füllt, "
        "verteilt sich auf beide Seiten.",
        "So hoch wie breit, unten rechts. Die Höhe reicht zuerst nicht mehr, also "
        "bleibt darunter nichts übrig.",
        "Sechzehn mal acht Pixel, so groß gezeichnet, wie die Zelle erlaubt: Ein Bild "
        "wird in seine Zelle eingepasst, nicht nach seinen Pixeln.",
    ),
)


# 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 catalogue(
    document: hqf_pdf.Document, font: hqf_pdf.FontHandle, words: Words
) -> hqf_pdf.Table:
    """The catalogue: a heading, then one row per item, the first cell of each a
    picture."""
    # The picture column is a fixed width, the note takes what is left, and the
    # reference and the price are each as wide as their own widest cell.
    columns = hqf_pdf.Columns(
        [
            hqf_pdf.ColumnWidth.points(96.0),
            hqf_pdf.ColumnWidth.fraction(1.0),
            hqf_pdf.ColumnWidth.content(),
            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, color=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(
            cells=[
                hqf_pdf.Cell(font, 9.0, words.headings[0], padding=pad, fill=HEAD_FILL),
                hqf_pdf.Cell(font, 9.0, words.headings[1], padding=pad, fill=HEAD_FILL),
                hqf_pdf.Cell(font, 9.0, words.headings[2], padding=pad, fill=HEAD_FILL),
                hqf_pdf.Cell(
                    font,
                    9.0,
                    words.headings[3],
                    align=hqf_pdf.Align.Right,
                    padding=pad,
                    fill=HEAD_FILL,
                ),
            ],
            min_height=20.0,
        )
    )

    for item, name, note in zip(ITEMS, words.names, words.notes):
        image = document.add_image(hqf_pdf.Image.from_path(PICTURES / item.picture))
        table.push(
            hqf_pdf.Row(
                cells=[
                    hqf_pdf.Cell.image(
                        image,
                        alt=name,
                        align=item.across,
                        valign=item.down,
                        padding=pad,
                    ),
                    hqf_pdf.Cell(font, 9.0, f"{name}\n{note}", padding=pad),
                    hqf_pdf.Cell(font, 9.0, item.reference, padding=pad),
                    hqf_pdf.Cell(
                        font,
                        9.0,
                        item.price,
                        align=hqf_pdf.Align.Right,
                        align_on=".",
                        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("picture_cells.pdf", language)).stem
    )

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

    table = catalogue(document, font, words)
    placed = table.fit(MARGIN, TOP, TOP - BOTTOM, 0)

    content = hqf_pdf.Content()
    placed.draw(content)

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

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


if __name__ == "__main__":
    main()
