"""Paints meshes of triangles, each corner carrying its own colour.

The Python twin of the `write_mesh` example in Rust. A mesh states its own shape.
Every triangle names three corners, and every corner names a point and a colour; what
lies between them is worked out from the three. So one triangle already runs from red to
green to blue, and a grid of them carries a gradient a straight axis and a circle cannot.

Four panels: a single triangle, a grid of sixty in red, green and blue with its edges
traced over it, the same grid in the four printing inks, and the grid once more named as
a fill and painted inside a disc.

The title, the four labels and the legend are held in `Words`, once per language, and
`HQF_PDF_LANG` picks which set is written.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The width and height of every panel, in points.
BOX = (210.0, 200.0)

# How many cells the grid runs across.
COLUMNS = 6

# How many cells the grid runs up.
ROWS = 5

# How many triangles the page states: the single one, and two per cell in each of the
# three grids.
TRIANGLES = 1 + 3 * COLUMNS * ROWS * 2

# How far the legend runs across, between the margins.
WIDTH = 455.0

# How far a circle's control points stand from its quarter marks, as a share of the
# radius: the number that turns four cubics into a circle.
KAPPA = 0.5522847498307934


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

    # The title across the top of the page.
    title: str
    # The label under the single triangle.
    one: str
    # The label under the grid in red, green and blue.
    grid: str
    # The label under the grid in the four printing inks.
    inks: str
    # The label under the disc the mesh fills.
    fill: str
    # The paragraph at the foot of the page, saying what is above it.
    legend: str


# The page in English.
ENGLISH = Words(
    title="Meshes of triangles, corner by corner",
    one="One triangle, three corners",
    grid="Sixty triangles, edges traced",
    inks="The same sixty, in the four inks",
    fill="The mesh as a fill, inside a disc",
    legend=(
        "Every corner above carries a point and a colour, and the colour "
        "between three corners is worked out from those three. The single "
        "triangle at the top left holds red, green and blue at its corners "
        "and nothing in between; the grid beside it cuts each of its thirty "
        "cells in two, and its edges are stroked over the paint so the "
        "triangles can be counted. The grid at the bottom left states the "
        "same corners in cyan, magenta, yellow and black, which is what a "
        "press is given. The last one is the same mesh again, named as a "
        "fill rather than painted straight onto the page, so the disc it "
        "sits under is what decides where it shows."
    ),
)

# The page in French.
FRENCH = Words(
    title="Maillages de triangles, sommet par sommet",
    one="Un triangle, trois sommets",
    grid="Soixante triangles, arêtes tracées",
    inks="Les mêmes soixante, dans les quatre encres",
    fill="Le maillage en remplissage, dans un disque",
    legend=(
        "Chaque sommet ci-dessus porte un point et une couleur, et la "
        "couleur entre trois sommets se déduit de ces trois-là. Le triangle "
        "seul, en haut à gauche, tient le rouge, le vert et le bleu à ses "
        "sommets et rien entre eux ; la grille à côté coupe en deux "
        "chacune de ses trente cases, et ses arêtes sont tracées par-dessus "
        "la couleur pour qu'on puisse compter les triangles. La grille en "
        "bas à gauche énonce les mêmes sommets en cyan, magenta, jaune et "
        "noir, ce qu'on remet à une presse. La dernière est encore le même "
        "maillage, nommé comme remplissage au lieu d'être peint à même la "
        "page : c'est alors le disque qui décide où il se montre."
    ),
)


# 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 one_triangle(x: float, y: float) -> tuple[
    tuple[tuple[float, float], hqf_pdf.Rgb],
    tuple[tuple[float, float], hqf_pdf.Rgb],
    tuple[tuple[float, float], hqf_pdf.Rgb],
]:
    """The one triangle of the panel at (x, y): green at its foot on the left, blue at
    its foot on the right, red at its apex."""
    return (
        ((x + 10.0, y + 15.0), hqf_pdf.Rgb(0.1, 0.7, 0.2)),
        ((x + BOX[0] - 10.0, y + 15.0), hqf_pdf.Rgb(0.1, 0.2, 0.9)),
        ((x + BOX[0] / 2.0, y + BOX[1] - 15.0), hqf_pdf.Rgb(0.9, 0.1, 0.1)),
    )


def rgb_corner(
    x: float, y: float, u: float, v: float
) -> tuple[tuple[float, float], hqf_pdf.Rgb]:
    """The point and the colour a grid corner carries in red, green and blue, `u` of the
    way across the panel at (x, y) and `v` of the way up it."""
    return (
        (x + u * BOX[0], y + v * BOX[1]),
        hqf_pdf.Rgb(0.15 + 0.8 * u, 0.15 + 0.8 * v, 0.95 - 0.7 * u - 0.2 * v),
    )


def cmyk_corner(
    x: float, y: float, u: float, v: float
) -> tuple[tuple[float, float], hqf_pdf.Cmyk]:
    """The point and the colour a grid corner carries in the four printing inks, `u` of
    the way across the panel at (x, y) and `v` of the way up it."""
    return (
        (x + u * BOX[0], y + v * BOX[1]),
        hqf_pdf.Cmyk(0.8 * u, 0.75 * v, 0.1 + 0.6 * (1.0 - u), 0.05 + 0.15 * u * v),
    )


def grid(x: float, y: float, corner) -> list[tuple[object, object, object]]:
    """The triangles of a grid of COLUMNS by ROWS cells over the panel at (x, y), each
    cell cut in two along the diagonal that rises to the right, every corner carrying
    what `corner` gives it."""
    triangles = []
    for row in range(ROWS):
        foot = row / ROWS
        head = (row + 1) / ROWS
        for column in range(COLUMNS):
            left = column / COLUMNS
            right = (column + 1) / COLUMNS
            triangles.append(
                (
                    corner(x, y, left, foot),
                    corner(x, y, right, foot),
                    corner(x, y, right, head),
                )
            )
            triangles.append(
                (
                    corner(x, y, left, foot),
                    corner(x, y, right, head),
                    corner(x, y, left, head),
                )
            )
    return triangles


def edges(content: hqf_pdf.Content, x: float, y: float) -> None:
    """Strokes the edges of the grid over the panel at (x, y): the lines between the
    rows, those between the columns, and the diagonal of each cell."""
    for row in range(ROWS + 1):
        v = row / ROWS
        content.move_to(x, y + v * BOX[1])
        content.line_to(x + BOX[0], y + v * BOX[1])
    for column in range(COLUMNS + 1):
        u = column / COLUMNS
        content.move_to(x + u * BOX[0], y)
        content.line_to(x + u * BOX[0], y + BOX[1])
    for row in range(ROWS):
        foot = row / ROWS
        head = (row + 1) / ROWS
        for column in range(COLUMNS):
            left = column / COLUMNS
            right = (column + 1) / COLUMNS
            content.move_to(x + left * BOX[0], y + foot * BOX[1])
            content.line_to(x + right * BOX[0], y + head * BOX[1])
    content.stroke()


def disc(content: hqf_pdf.Content, cx: float, cy: float, radius: float) -> None:
    """Fills a disc of radius `radius` about (cx, cy) with whatever the fill is set to,
    out of the four cubics that make a circle."""
    pull = KAPPA * radius
    content.move_to(cx + radius, cy)
    content.curve_to(cx + radius, cy + pull, cx + pull, cy + radius, cx, cy + radius)
    content.curve_to(cx - pull, cy + radius, cx - radius, cy + pull, cx - radius, cy)
    content.curve_to(cx - radius, cy - pull, cx - pull, cy - radius, cx, cy - radius)
    content.curve_to(cx + pull, cy - radius, cx + radius, cy - pull, cx + radius, cy)
    content.close_path()
    content.fill()


def legend(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    top: float,
    color: hqf_pdf.Rgb,
    text: str,
) -> None:
    """Writes the legend in a column the width of the page."""
    flow = hqf_pdf.TextFlow(font, 8.5, leading=12.0, color=color)
    lines = flow.break_lines(text, WIDTH)
    flow.draw(content, lines, 70.0, top, WIDTH)


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

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

    # Left column x, right column x, top row foot, bottom row foot.
    left, right, top, bottom = 70.0, 315.0, 560.0, 300.0

    single = document.add_triangle_mesh([one_triangle(left, top)])
    colours = document.add_triangle_mesh(grid(right, top, rgb_corner))
    inks = document.add_triangle_mesh(grid(left, bottom, cmyk_corner))
    fill = document.add_triangle_mesh_pattern(grid(right, bottom, rgb_corner))

    content = hqf_pdf.Content()
    content.draw_text(font, 18.0, left, 790.0, words.title)

    content.draw_shading(single)
    content.draw_text(font, 10.0, left, top - 15.0, words.one)

    content.draw_shading(colours)
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(1.0))
    content.set_line_width(0.4)
    edges(content, right, top)
    content.restore_state()
    content.draw_text(font, 10.0, right, top - 15.0, words.grid)

    content.draw_shading(inks)
    content.draw_text(font, 10.0, left, bottom - 15.0, words.inks)

    content.save_state()
    content.set_fill_pattern(fill)
    disc(content, right + BOX[0] / 2.0, bottom + BOX[1] / 2.0, BOX[1] / 2.0)
    content.restore_state()
    content.draw_text(font, 10.0, right, bottom - 15.0, words.fill)

    legend(content, font, 265.0, hqf_pdf.Rgb.gray(0.35), words.legend)

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

    written = document.write(out)
    print(f"wrote {out}: {written} bytes, {TRIANGLES} triangles in all")


if __name__ == "__main__":
    main()
