"""Writes a specification sheet whose cells are merged down the rows.

The Python twin of the `write_merged_cells` example in Rust. Each group of
properties names itself once, in a cell covering all the rows of the group, and
carries one note beside them in another: the rows beneath hand their cells to
the columns those two leave free, and the hairlines between the rows stop at
their edges. The last group shows what a merged cell does when it needs more
room than its rows give it: every row it covers grows by the same share of what
is missing.

Every word the sheet draws is held in `Words`, once per language, and `HQF_PDF_LANG`
picks which one is drawn. The model's number and the figures are the same in both.

Usage: python examples/write_merged_cells.py [out.pdf] [font.ttf]
       HQF_PDF_LANG=fr python examples/write_merged_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 a group's name and its note are painted on.
GROUP_FILL = hqf_pdf.Rgb(0.90, 0.91, 0.94)


# How many properties each group holds, in the order they are printed.
GROUP_ROWS = (4, 3, 2, 2)


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

    What is not language stays out of it: the model's number, the figures and the units
    they are given in read the same in every language.
    """

    # The heading across the four columns.
    title: str
    # What each group is called, written once beside all of its rows.
    groups: tuple[str, ...]
    # What is said about each group as a whole, once beside all of its rows. The last is
    # far longer than the two rows it covers, which is what makes the sheet's last row
    # grow.
    notes: tuple[str, ...]
    # What each property is called, group after group.
    properties: tuple[str, ...]
    # What each of them is worth, in the same order.
    values: tuple[str, ...]


# The sheet in English.
ENGLISH = Words(
    title="Model HQF-220 — technical data",
    groups=("Body", "Optics", "Mounting", "Power"),
    notes=(
        "Measured without the mounting plate.",
        "Figures hold across the whole of the zoom range.",
        "Plate sold with the instrument.",
        "Twelve volts, from the adapter supplied or from any regulated bench "
        "supply. The instrument draws its full current for the first two seconds "
        "after it is switched on, so a supply rated at the working figure alone "
        "will trip.",
    ),
    properties=(
        "Material",
        "Mass",
        "Finish",
        "Sealing",
        "Focal length",
        "Widest aperture",
        "Coating",
        "Thread",
        "Plate",
        "Supply",
        "Draw",
    ),
    values=(
        "Anodised aluminium",
        "1.84 kg",
        "Matt black, bead blasted",
        "IP65",
        "24 to 70 mm",
        "f/2.8",
        "Multi-layer, both faces",
        "M6, four points",
        "Quick release",
        "12 V DC",
        "9 W at full output",
    ),
)

# The sheet in French.
FRENCH = Words(
    title="Modèle HQF-220 — caractéristiques techniques",
    groups=("Boîtier", "Optique", "Fixation", "Alimentation"),
    notes=(
        "Mesuré sans la platine de fixation.",
        "Valeurs valables sur toute la plage de zoom.",
        "Platine livrée avec l’appareil.",
        ("Douze volts, par l’adaptateur fourni ou par toute alimentation de "
         "laboratoire régulée. L’appareil appelle son courant maximal pendant les deux"
         " premières secondes après la mise sous tension : une alimentation calibrée "
         "sur la seule valeur nominale se mettra en sécurité."),
    ),
    properties=(
        "Matériau",
        "Masse",
        "Finition",
        "Étanchéité",
        "Focale",
        "Ouverture maximale",
        "Traitement",
        "Filetage",
        "Platine",
        "Source",
        "Consommation",
    ),
    values=(
        "Aluminium anodisé",
        "1.84 kg",
        "Noir mat, microbillé",
        "IP65",
        "24 à 70 mm",
        "f/2.8",
        "Multicouche, deux faces",
        "M6, quatre points",
        "Attache rapide",
        "12 V continu",
        "9 W à pleine puissance",
    ),
)

# The sheet in Dutch.
DUTCH = Words(
    title="Model HQF-220 — technische gegevens",
    groups=("Behuizing", "Optiek", "Montage", "Voeding"),
    notes=(
        "Gemeten zonder de montageplaat.",
        "De waarden gelden over het hele zoombereik.",
        "Plaat meegeleverd met het instrument.",
        "Twaalf volt, uit de meegeleverde adapter of uit elke gestabiliseerde "
        "labvoeding. Het instrument trekt zijn volle stroom in de eerste twee "
        "seconden na het inschakelen, dus een voeding die alleen op de werkwaarde is "
        "berekend, schakelt af.",
    ),
    properties=(
        "Materiaal",
        "Massa",
        "Afwerking",
        "Afdichting",
        "Brandpuntsafstand",
        "Grootste diafragma",
        "Coating",
        "Schroefdraad",
        "Plaat",
        "Voeding",
        "Opgenomen vermogen",
    ),
    values=(
        "Geanodiseerd aluminium",
        "1,84 kg",
        "Matzwart, gestraald",
        "IP65",
        "24 tot 70 mm",
        "f/2,8",
        "Meerlaags, beide zijden",
        "M6, vier punten",
        "Snelkoppeling",
        "12 V DC",
        "9 W bij vol vermogen",
    ),
)

# The sheet in Portuguese.
PORTUGUESE = Words(
    title="Modelo HQF-220 — dados técnicos",
    groups=("Corpo", "Ótica", "Montagem", "Alimentação"),
    notes=(
        "Medido sem a placa de montagem.",
        "Os valores mantêm-se em toda a gama do zoom.",
        "Placa vendida com o instrumento.",
        "Doze volts, do adaptador fornecido ou de qualquer fonte de bancada regulada. "
        "O instrumento consome toda a corrente nos dois primeiros segundos após ser "
        "ligado, por isso uma fonte dimensionada só para o valor de trabalho vai "
        "disparar.",
    ),
    properties=(
        "Material",
        "Massa",
        "Acabamento",
        "Estanquidade",
        "Distância focal",
        "Abertura máxima",
        "Tratamento",
        "Rosca",
        "Placa",
        "Alimentação",
        "Consumo",
    ),
    values=(
        "Alumínio anodizado",
        "1,84 kg",
        "Preto mate, decapado a esferas",
        "IP65",
        "24 a 70 mm",
        "f/2,8",
        "Multicamada, ambas as faces",
        "M6, quatro pontos",
        "Desengate rápido",
        "12 V CC",
        "9 W à potência máxima",
    ),
)

# The sheet in Italian.
ITALIAN = Words(
    title="Modello HQF-220 — dati tecnici",
    groups=("Corpo", "Ottica", "Montaggio", "Alimentazione"),
    notes=(
        "Misurato senza la piastra di montaggio.",
        "Le cifre valgono su tutta l’escursione dello zoom.",
        "Piastra venduta con lo strumento.",
        "Dodici volt, dall’adattatore fornito o da qualsiasi alimentatore da banco "
        "stabilizzato. Lo strumento assorbe tutta la corrente nei primi due secondi "
        "dopo l’accensione, così un alimentatore dimensionato solo sul valore di "
        "lavoro scatterà.",
    ),
    properties=(
        "Materiale",
        "Massa",
        "Finitura",
        "Tenuta",
        "Lunghezza focale",
        "Apertura massima",
        "Trattamento",
        "Filettatura",
        "Piastra",
        "Alimentazione",
        "Assorbimento",
    ),
    values=(
        "Alluminio anodizzato",
        "1,84 kg",
        "Nero opaco, sabbiato",
        "IP65",
        "Da 24 a 70 mm",
        "f/2,8",
        "Multistrato, su entrambe le facce",
        "M6, quattro punti",
        "Sgancio rapido",
        "12 V CC",
        "9 W alla massima potenza",
    ),
)

# The sheet in Spanish.
SPANISH = Words(
    title="Modelo HQF-220 — datos técnicos",
    groups=("Cuerpo", "Óptica", "Montaje", "Alimentación"),
    notes=(
        "Medido sin la placa de montaje.",
        "Las cifras valen en todo el rango del zoom.",
        "Placa vendida con el instrumento.",
        "Doce voltios, del adaptador incluido o de cualquier fuente de banco "
        "regulada. El instrumento consume toda su corriente los dos primeros segundos "
        "tras encenderse, así que una fuente calculada solo para el valor de trabajo "
        "saltará.",
    ),
    properties=(
        "Material",
        "Masa",
        "Acabado",
        "Estanqueidad",
        "Distancia focal",
        "Apertura máxima",
        "Tratamiento",
        "Rosca",
        "Placa",
        "Alimentación",
        "Consumo",
    ),
    values=(
        "Aluminio anodizado",
        "1,84 kg",
        "Negro mate, microgranallado",
        "IP65",
        "24 a 70 mm",
        "f/2,8",
        "Multicapa, ambas caras",
        "M6, cuatro puntos",
        "Liberación rápida",
        "12 V CC",
        "9 W a plena potencia",
    ),
)

# The sheet in German.
GERMAN = Words(
    title="Modell HQF-220 — technische Daten",
    groups=("Gehäuse", "Optik", "Befestigung", "Stromversorgung"),
    notes=(
        "Ohne die Montageplatte gemessen.",
        "Die Werte gelten über den ganzen Zoombereich.",
        "Die Platte wird mit dem Gerät geliefert.",
        "Zwölf Volt, aus dem mitgelieferten Adapter oder einem beliebigen geregelten "
        "Labornetzteil. Das Gerät zieht in den ersten zwei Sekunden nach dem "
        "Einschalten seinen vollen Strom, also schaltet ein nur für den Betriebswert "
        "ausgelegtes Netzteil ab.",
    ),
    properties=(
        "Werkstoff",
        "Masse",
        "Oberfläche",
        "Abdichtung",
        "Brennweite",
        "Größte Blende",
        "Vergütung",
        "Gewinde",
        "Platte",
        "Versorgung",
        "Aufnahme",
    ),
    values=(
        "Eloxiertes Aluminium",
        "1.84 kg",
        "Mattschwarz, glasperlgestrahlt",
        "IP65",
        "24 bis 70 mm",
        "f/2.8",
        "Mehrschichtig, beidseitig",
        "M6, vier Punkte",
        "Schnellwechsel",
        "12 V DC",
        "9 W bei voller Leistung",
    ),
)


# 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 sheet(font: hqf_pdf.FontHandle, words: Words) -> hqf_pdf.Table:
    """The specification sheet: a heading across the table, then two merged
    cells per group of properties."""
    # The property column takes whatever the other three leave it.
    columns = hqf_pdf.Columns(
        [
            hqf_pdf.ColumnWidth.points(76.0),
            hqf_pdf.ColumnWidth.fraction(1.0),
            hqf_pdf.ColumnWidth.points(120.0),
            hqf_pdf.ColumnWidth.points(150.0),
        ],
        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)

    # The heading covers the four columns, the way a cell covers four rows further down:
    # one span across, one down.
    table.push(
        hqf_pdf.Row(
            cells=[
                hqf_pdf.Cell(
                    font,
                    11.0,
                    words.title,
                    span=4,
                    align=hqf_pdf.Align.Center,
                    valign=hqf_pdf.VAlign.Middle,
                    padding=pad,
                    fill=hqf_pdf.Rgb.gray(0.86),
                )
            ],
            min_height=24.0,
        )
    )

    first = 0
    for group, rows in enumerate(GROUP_ROWS):
        for index in range(rows):
            prop = words.properties[first + index]
            value = words.values[first + index]
            cells = []
            # The group names itself on its first row only, and so does its note. The
            # rows under them hold two cells, not four: the first column and the last
            # are taken.
            if index == 0:
                cells.append(
                    hqf_pdf.Cell(
                        font,
                        10.0,
                        words.groups[group],
                        span_rows=rows,
                        align=hqf_pdf.Align.Center,
                        valign=hqf_pdf.VAlign.Middle,
                        padding=pad,
                        fill=GROUP_FILL,
                    )
                )
            cells.append(hqf_pdf.Cell(font, 9.0, prop, padding=pad))
            cells.append(hqf_pdf.Cell(font, 9.0, value, padding=pad))
            if index == 0:
                cells.append(
                    hqf_pdf.Cell(
                        font,
                        8.0,
                        words.notes[group],
                        span_rows=rows,
                        align=hqf_pdf.Align.Justify,
                        padding=pad,
                        fill=GROUP_FILL,
                    )
                )
            table.push(hqf_pdf.Row(cells=cells, min_height=18.0))
        first += rows

    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("merged_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 = sheet(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()
