"""One hatch, drawn once, laid down in as many colours as the page asks for.

The Python twin of the `write_stencil_pattern` example in Rust. A tiling pattern
usually states its own colours, so a hatch in five colours is five patterns. A
pattern built with `uncolored` states none: the cell says only what shape is laid
down, and the colour comes from the fill.

The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks which
set the page is written in. What a swatch is painted with is not language: the
numbers under the first row are the ones the stream states, and the ink of the
second row travels under the name a press knows it by.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The left edge of everything.
LEFT = 70.0

# The side of one cell of the hatch.
CELL = 10.0

# Half the height of one band of the hatch, measured up the page.
BAND = 1.6

# How far behind the cell's box a band starts, and how far past it it runs: the box is
# what cuts them, so the ends fall outside it.
BEHIND = -CELL
BEYOND = CELL + CELL

# The five colours the one stencil is laid down in, each under the numbers the fill
# states it as: what is written under a swatch is what the stream says.
COLORS = [
    ([0.12, 0.28, 0.52], "0.12 0.28 0.52"),
    ([0.16, 0.45, 0.35], "0.16 0.45 0.35"),
    ([0.78, 0.30, 0.24], "0.78 0.3 0.24"),
    ([0.55, 0.35, 0.62], "0.55 0.35 0.62"),
    ([0.20, 0.20, 0.20], "0.2 0.2 0.2"),
]

# The five strengths the one stencil is laid down in as an ink.
TINTS = [0.15, 0.35, 0.55, 0.75, 1.0]

# The three colours the ordinary kind of pattern needs a cell of its own for.
OWN = [
    [0.12, 0.28, 0.52],
    [0.78, 0.30, 0.24],
    [0.16, 0.45, 0.35],
]

# The ink the strengths of the second row are laid down in, which is a name a press
# knows the ink by rather than a word a language writes.
INK = "Corporate Blue"


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

    # The title over the page.
    title: str
    # The two lines under the title.
    intro: tuple[str, str]
    # The heading of each of the three panels, and the line under it.
    panels: tuple[tuple[str, str], tuple[str, str], tuple[str, str]]
    # What stands in front of the strength on the label of a tinted swatch.
    tint_label: str
    # What is written under each of the three swatches whose pattern carries its own
    # colour.
    own: tuple[str, str, str]
    # The three lines of the closing note.
    closing: tuple[str, str, str]

    def tint(self, tint: float) -> str:
        """What is written under the swatch laid down at `tint`."""
        return f"{self.tint_label}{tint:g}"


# The page in English.
ENGLISH = Words(
    title="One hatch, laid down in as many colours as the page asks for",
    intro=(
        "The first ten swatches below are one cell. It states no colour: the "
        "fill says what colour to lay",
        "it down in, and the space the fill is stated in says what those "
        "numbers mean.",
    ),
    panels=(
        (
            "Five colours, one cell",
            "The colour is {n} numbers in DeviceRGB, and the pattern is named "
            "beside them.",
        ),
        (
            "The same cell, through one ink",
            "The same pattern, through a space whose one number is how much ink "
            "the press lays down.",
        ),
        (
            "What a cell that carries its own colours costs",
            "Nothing is said at the fill, so each colour of the hatch is a cell of "
            "its own in the file.",
        ),
    ),
    tint_label="tint ",
    own=("one cell", "a second cell", "a third cell"),
    closing=(
        "Thirteen swatches, four cells: one for the ten above, and one "
        "apiece for the three that carry",
        "their own colours. Changing the hatch itself is changing one cell "
        "in the first two rows and",
        "three in the last.",
    ),
)

# The page in French.
FRENCH = Words(
    title="Une hachure, dans autant de couleurs que la page en demande",
    intro=(
        ("Les dix premières pastilles sont une seule case. Elle ne fixe aucune "
         "couleur : le remplissage dit"),
        ("dans quelle couleur la poser, et l’espace où il est exprimé donne le sens de"
         " ces nombres."),
    ),
    panels=(
        (
            "Cinq couleurs, une seule cellule",
            "La couleur tient en {n} nombres DeviceRGB, et le motif est nommé à côté.",
        ),
        (
            "La même cellule, avec une seule encre",
            ("Le même motif, dans un espace dont l’unique nombre est la quantité "
             "d’encre que pose la presse."),
        ),
        (
            "Ce que coûte une cellule qui fixe ses propres couleurs",
            (
                "Le remplissage ne dit rien : chaque couleur de la hachure est donc une "
                "cellule à part dans le fichier."
            ),
        ),
    ),
    tint_label="teinte ",
    own=("une case", "une deuxième case", "une troisième case"),
    closing=(
        ("Treize pastilles, quatre cases : une pour les dix du haut, et une pour "
         "chacune des trois qui"),
        ("fixent leurs propres couleurs. Changer la hachure, c’est changer une case "
         "dans les deux premières"),
        "rangées et trois dans la dernière.",
    ),
)

# The page in Dutch.
DUTCH = Words(
    title="Eén arcering, neergelegd in zoveel kleuren als de pagina vraagt",
    intro=(
        "De eerste tien stalen hieronder zijn één cel. Ze noemt geen kleur: de "
        "vulling zegt in welke kleur ze",
        "neergelegd wordt, en de ruimte waarin de vulling gegeven wordt, zegt wat die "
        "getallen betekenen.",
    ),
    panels=(
        (
            "Vijf kleuren, één cel",
            "De kleur is {n} getallen in DeviceRGB, en het patroon wordt ernaast "
            "genoemd.",
        ),
        (
            "Dezelfde cel, via één inkt",
            "Hetzelfde patroon, via een ruimte waarvan het ene getal is hoeveel inkt "
            "de pers neerlegt.",
        ),
        (
            "Wat een cel kost die haar eigen kleuren bevat",
            "Bij de vulling wordt niets gezegd, dus elke kleur van de arcering is een "
            "eigen cel in het bestand.",
        ),
    ),
    tint_label="tint",
    own=("één cel", "een tweede cel", "een derde cel"),
    closing=(
        "Dertien stalen, vier cellen: één voor de tien hierboven, en één voor elk van "
        "de drie die hun",
        "eigen kleuren bevatten. De arcering zelf veranderen is één cel veranderen in "
        "de eerste twee rijen en",
        "drie in de laatste.",
    ),
)

# The page in Portuguese.
PORTUGUESE = Words(
    title="Um tracejado, posto em tantas cores quantas a página pedir",
    intro=(
        "As primeiras dez amostras abaixo são uma célula. Não indica cor: o "
        "preenchimento diz em que cor",
        "a pôr, e o espaço em que o preenchimento é dado diz o que esses números "
        "significam.",
    ),
    panels=(
        (
            "Cinco cores, uma célula",
            "A cor são {n} números em DeviceRGB, e o padrão é nomeado ao lado.",
        ),
        (
            "A mesma célula, através de uma tinta",
            "O mesmo padrão, num espaço cujo único número é quanta tinta a máquina "
            "põe.",
        ),
        (
            "O que custa uma célula que leva as suas próprias cores",
            "No preenchimento não se diz nada, por isso cada cor do tracejado é uma "
            "célula própria no ficheiro.",
        ),
    ),
    tint_label="tom",
    own=("uma célula", "uma segunda célula", "uma terceira célula"),
    closing=(
        "Treze amostras, quatro células: uma para as dez acima, e uma para cada uma "
        "das três que levam",
        "as suas próprias cores. Mudar o tracejado em si é mudar uma célula nas duas "
        "primeiras filas e",
        "três na última.",
    ),
)

# The page in Italian.
ITALIAN = Words(
    title="Un tratteggio, steso in tanti colori quanti ne chiede la pagina",
    intro=(
        "I primi dieci campioni qui sotto sono una cella. Non indica colore: il "
        "riempimento dice in che colore",
        "stenderla, e lo spazio in cui è dato il riempimento dice cosa significano "
        "quei numeri.",
    ),
    panels=(
        (
            "Cinque colori, una cella",
            "Il colore sono {n} numeri in DeviceRGB, e il motivo è nominato accanto.",
        ),
        (
            "La stessa cella, attraverso un inchiostro",
            "Lo stesso motivo, in uno spazio il cui unico numero è quanto inchiostro "
            "stende la macchina.",
        ),
        (
            "Quanto costa una cella che porta i propri colori",
            "Nel riempimento non si dice nulla, così ogni colore del tratteggio è una "
            "cella propria nel file.",
        ),
    ),
    tint_label="tinta",
    own=("una cella", "una seconda cella", "una terza cella"),
    closing=(
        "Tredici campioni, quattro celle: una per i dieci qui sopra, e una per "
        "ciascuno dei tre che portano",
        "i propri colori. Cambiare il tratteggio stesso è cambiare una cella nelle "
        "prime due righe e",
        "tre nell’ultima.",
    ),
)

# The page in Spanish.
SPANISH = Words(
    title="Un rayado, puesto en tantos colores como pida la página",
    intro=(
        "Las diez primeras muestras de abajo son una celda. No indica color: el "
        "relleno dice en qué color",
        "ponerla, y el espacio en que se da el relleno dice qué significan esos "
        "números.",
    ),
    panels=(
        (
            "Cinco colores, una celda",
            "El color son {n} números en DeviceRGB, y el patrón se nombra a su lado.",
        ),
        (
            "La misma celda, con una tinta",
            "El mismo patrón, en un espacio cuyo único número es cuánta tinta pone la "
            "prensa.",
        ),
        (
            "Lo que cuesta una celda que lleva sus propios colores",
            "En el relleno no se dice nada, así que cada color del rayado es una "
            "celda propia en el archivo.",
        ),
    ),
    tint_label="tono",
    own=("una celda", "una segunda celda", "una tercera celda"),
    closing=(
        "Trece muestras, cuatro celdas: una para las diez de arriba, y una por cada "
        "una de las tres que llevan",
        "sus propios colores. Cambiar el rayado mismo es cambiar una celda en las dos "
        "primeras filas y",
        "tres en la última.",
    ),
)

# The page in German.
GERMAN = Words(
    title="Eine Schraffur, in so vielen Farben, wie die Seite verlangt",
    intro=(
        "Die ersten zehn Farbfelder unten sind eine einzige Zelle. Sie nennt keine "
        "Farbe: Die Füllung",
        "sagt, in welcher Farbe sie aufzutragen ist, und ihr Farbraum sagt, was diese "
        "Zahlen bedeuten.",
    ),
    panels=(
        (
            "Fünf Farben, eine Zelle",
            "Die Farbe sind {n} Zahlen in DeviceRGB, und das Muster ist daneben "
            "genannt.",
        ),
        (
            "Dieselbe Zelle, über eine Farbe",
            "Dasselbe Muster, in einem Farbraum, dessen eine Zahl die aufgetragene "
            "Farbmenge ist.",
        ),
        (
            "Was eine Zelle kostet, die ihre eigenen Farben trägt",
            "Die Füllung sagt nichts, also ist jede Farbe der Schraffur eine eigene "
            "Zelle der Datei.",
        ),
    ),
    tint_label="Tonwert ",
    own=("eine Zelle", "eine zweite Zelle", "eine dritte Zelle"),
    closing=(
        "Dreizehn Farbfelder, vier Zellen: eine für die zehn oben und je eine für die "
        "drei, die ihre",
        "eigenen Farben tragen. Die Schraffur selbst zu ändern heißt, eine Zelle in "
        "den ersten beiden",
        "Reihen zu ändern und drei in der letzten.",
    ),
)

# 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 size of one swatch of the first two rows, and the gap to the next.
SWATCH = (83.0, 105.0)
SWATCH_GAP = 10.0

# The width of one swatch of the last row, which holds three rather than five.
WIDE = 145.0


def hatch(cell):
    """Lay the bands of the hatch into `cell`.

    Three of them, so that the corners carry the halves the middle band leaves out
    and the plane runs unbroken.
    """
    for offset in (-CELL, 0.0, CELL):
        near = BEHIND + offset
        far = BEYOND + offset
        cell.move_to(BEHIND, near - BAND)
        cell.line_to(BEYOND, far - BAND)
        cell.line_to(BEYOND, far + BAND)
        cell.line_to(BEHIND, near + BAND)
        cell.close_path()
        cell.fill()


def stencil():
    """The hatch as a cell that states no colour of its own."""
    cell = hqf_pdf.Content()
    hatch(cell)
    box = hqf_pdf.Rect(0.0, 0.0, CELL, CELL)
    return hqf_pdf.TilingPattern(cell, box, (CELL, CELL)).uncolored()


def carried(color):
    """The same hatch as a cell that carries `color`."""
    cell = hqf_pdf.Content()
    cell.set_fill(hqf_pdf.Rgb(color[0], color[1], color[2]))
    hatch(cell)
    box = hqf_pdf.Rect(0.0, 0.0, CELL, CELL)
    return hqf_pdf.TilingPattern(cell, box, (CELL, CELL))


def paper(content, x, y, width):
    """Lay the paper of a swatch down."""
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.97))
    content.rect(x, y, width, SWATCH[1])
    content.fill()
    content.restore_state()


def frame(content, font, corner, width, label):
    """Frame a swatch and write what is under it."""
    x, y = corner
    content.save_state()
    content.set_stroke(hqf_pdf.Color.gray(0.55))
    content.set_line_width(0.6)
    content.rect(x, y, width, SWATCH[1])
    content.stroke()
    content.set_fill(hqf_pdf.Color.gray(0.45))
    content.draw_text(font, 8.0, x, y - 12.0, label)
    content.restore_state()


def panel_heading(content, font, y, title, under):
    """Draw one panel's heading, with the line under it."""
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.2))
    content.draw_text(font, 11.0, LEFT, y, title)
    content.set_fill(hqf_pdf.Color.gray(0.45))
    content.draw_text(font, 8.0, LEFT, y - 15.0, under)
    content.restore_state()


def in_colors(content, font, space, pattern):
    """Lay the one stencil down five times across, each in the colour beside it."""
    x = LEFT
    for color, label in COLORS:
        paper(content, x, 570.0, SWATCH[0])
        content.save_state()
        content.set_fill_space(space)
        content.set_fill_pattern_components(color, pattern)
        content.set_stroke_space(space)
        content.set_stroke_pattern_components(color, pattern)
        content.set_line_width(4.0)
        content.rect(x, 570.0, SWATCH[0], SWATCH[1])
        content.fill_and_stroke()
        content.restore_state()
        frame(content, font, (x, 570.0), SWATCH[0], label)
        x += SWATCH[0] + SWATCH_GAP


def in_tints(content, font, words, space, pattern):
    """Lay the same stencil down five times, each a strength of the one ink."""
    x = LEFT
    for tint in TINTS:
        paper(content, x, 379.0, SWATCH[0])
        content.save_state()
        content.set_fill_space(space)
        content.set_fill_pattern_components([tint], pattern)
        content.rect(x, 379.0, SWATCH[0], SWATCH[1])
        content.fill()
        content.restore_state()
        frame(content, font, (x, 379.0), SWATCH[0], words.tint(tint))
        x += SWATCH[0] + SWATCH_GAP


def in_their_own(content, font, words, patterns):
    """Lay down three patterns that carry their own colours, one swatch each."""
    x = LEFT
    for pattern, label in zip(patterns, words.own):
        paper(content, x, 188.0, WIDE)
        content.save_state()
        content.set_fill_pattern(pattern)
        content.rect(x, 188.0, WIDE, SWATCH[1])
        content.fill()
        content.restore_state()
        frame(content, font, (x, 188.0), WIDE, label)
        x += WIDE + SWATCH_GAP


def heading(content, font, words):
    """Draw the title and the two lines saying what the page is showing."""
    content.draw_text(font, 15.0, LEFT, 780.0, words.title)
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.35))
    content.draw_text(font, 10.0, LEFT, 762.0, words.intro[0])
    content.draw_text(font, 10.0, LEFT, 748.0, words.intro[1])
    content.restore_state()


def closing(content, font, words):
    """Draw the closing note."""
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.35))
    for y, line in zip((130.0, 116.0, 102.0), words.closing):
        content.draw_text(font, 9.0, LEFT, y, line)
    content.restore_state()


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

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

    pattern = document.add_tiling_pattern(stencil())
    rgb_under = hqf_pdf.PatternSpace(hqf_pdf.DeviceSpace.Rgb)
    under = rgb_under.base.components
    in_rgb = document.add_color_space(rgb_under)
    ink = hqf_pdf.Separation.ink(INK, hqf_pdf.Cmyk(0.9, 0.55, 0.0, 0.05))
    in_ink = document.add_color_space(hqf_pdf.PatternSpace(ink))
    own = [document.add_tiling_pattern(carried(color)) for color in OWN]

    content = hqf_pdf.Content()
    heading(content, font, words)

    colors, tints, their_own = words.panels
    panel_heading(
        content, font, 706.0, colors[0], colors[1].replace("{n}", str(under))
    )
    in_colors(content, font, in_rgb, pattern)

    panel_heading(content, font, 515.0, tints[0], tints[1])
    in_tints(content, font, words, in_ink, pattern)

    panel_heading(content, font, 324.0, their_own[0], their_own[1])
    in_their_own(content, font, words, own)
    closing(content, font, words)

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

    written = document.write(out)
    print(
        f"wrote {out}: {written} bytes, "
        f"{len(COLORS) + len(TINTS) + len(OWN)} swatches from {len(own) + 1} cells"
    )


if __name__ == "__main__":
    main()
