"""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 three 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 dit aucune "
        "couleur : le remplissage dit",
        "dans quelle couleur la poser, et l'espace où il est dit donne le sens "
        "de ces nombres.",
    ),
    panels=(
        (
            "Cinq couleurs, une seule case",
            "La couleur est trois nombres en DeviceRGB, et le motif est nommé à "
            "côté d'eux.",
        ),
        (
            "La même case, à travers une 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 case qui porte ses propres couleurs",
            "Rien n'est dit au remplissage, donc chaque couleur de la hachure est "
            "une case à 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 "
        "chacune pour les trois qui",
        "portent leurs propres couleurs. Changer la hachure, c'est changer une "
        "case dans les deux premières",
        "rangées et trois dans la dernière.",
    ),
)

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

# 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.rect(x, 570.0, SWATCH[0], SWATCH[1])
        content.fill()
        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())
    in_rgb = document.add_color_space(hqf_pdf.PatternSpace(hqf_pdf.DeviceSpace.Rgb))
    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])
    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()
