"""Fits one picture into boxes of one size eight ways, and labels each.

The Python twin of the `write_fitted` example in Rust. A `FitBox` says how a picture
is sized to a box, where it sits in it, how far the box is inset first, and how far
the picture is turned and mirrored on its way in. Every panel hands the same picture
and the same box to a different policy, and draws what comes back the same way:
concatenate the matrix the policy gives, then draw the picture at the origin at its
natural size. Nothing measures anything by hand.

The panel that covers its box is the one case that overflows on purpose, so it is
clipped to the box it covers; every other panel stays inside.

The title and the eight captions are held in `Words`, once per language, and
`HQF_PDF_LANG` picks which set is drawn. Each caption opens with the name the library
gives the policy it describes, which is the same in every language.

Usage: python examples/write_fitted.py [out.pdf]
       HQF_PDF_LANG=fr python examples/write_fitted.py
"""

from __future__ import annotations

import math
from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The picture every panel fits: the JPEG committed for the tests.
IMAGES = Path(__file__).resolve().parents[2] / "hqf-pdf" / "tests" / "images"
PHOTO = IMAGES / "photo.jpg"

PAGE_WIDTH = 595.276
MARGIN = 56.0
# The gap between the two columns of panels.
GAP = 24.0
# The box every panel fits its picture into.
BOX_WIDTH = (PAGE_WIDTH - 2.0 * MARGIN - GAP) / 2.0
BOX_HEIGHT = 130.0
# The top of the first row of boxes, and the drop from one row to the next.
FIRST_ROW = 750.0
ROW_PITCH = 176.0
# How far under a box its caption's baseline sits.
CAPTION_DROP = 14.0


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

    Each caption opens with the name the library gives its policy — ``contain``,
    ``cover``, ``stretch`` — which is not language.
    """

    # The line at the head of the page.
    title: str
    # What is written under each box, in the order the panels are laid out.
    captions: tuple[str, str, str, str, str, str, str, str]


# The page in English.
ENGLISH = Words(
    title="One picture, one box, eight fitting policies",
    captions=(
        "contain: as large as fits, its shape kept",
        "cover: no gap left, the overflow clipped",
        "stretch: the box filled, its shape lost",
        "contain, top left, inset 12 points",
        "a quarter turn anticlockwise",
        "a quarter turn clockwise",
        "turned 20 degrees, still inside its box",
        "mirrored left to right",
    ),
)

# The page in French.
FRENCH = Words(
    title="Une image, une boîte, huit façons de l'ajuster",
    captions=(
        "contain : la plus grande qui tient, sans déformer",
        "cover : aucun vide laissé, le débordement rogné",
        "stretch : la boîte remplie, les proportions perdues",
        "contain, en haut à gauche, 12 points de retrait",
        "un quart de tour vers la gauche",
        "un quart de tour vers la droite",
        "tournée de 20 degrés, toujours dans sa boîte",
        "retournée de gauche à droite",
    ),
)


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


@dataclass(frozen=True)
class Panel:
    """What a panel says it does, the policy that does it, and whether what it
    draws has to be held to the box."""

    caption: str
    fitted: hqf_pdf.FitBox
    clipped: bool = False


def panels(words: Words) -> list[Panel]:
    """The eight policies, in the order they are laid out: the five ways of sizing
    first, then the turns and the mirror, each under the caption `words` gives it."""
    fitted = [
        (hqf_pdf.FitBox(hqf_pdf.FitMode.Contain), False),
        (hqf_pdf.FitBox(hqf_pdf.FitMode.Cover), True),
        (hqf_pdf.FitBox(hqf_pdf.FitMode.Stretch), False),
        (
            hqf_pdf.FitBox(
                hqf_pdf.FitMode.Contain,
                anchor=hqf_pdf.Anchor.top_left(),
                margin=12.0,
            ),
            False,
        ),
        (hqf_pdf.FitBox(hqf_pdf.FitMode.Contain, turn=hqf_pdf.Turn.left()), False),
        (hqf_pdf.FitBox(hqf_pdf.FitMode.Contain, turn=hqf_pdf.Turn.right()), False),
        (
            hqf_pdf.FitBox(
                hqf_pdf.FitMode.Contain,
                turn=hqf_pdf.Turn.angle(math.radians(20.0)),
            ),
            False,
        ),
        (
            hqf_pdf.FitBox(hqf_pdf.FitMode.Contain, mirror=hqf_pdf.Mirror.LeftToRight),
            False,
        ),
    ]
    return [
        Panel(caption, box, clipped=clipped)
        for (box, clipped), caption in zip(fitted, words.captions)
    ]


def panel_box(index: int) -> tuple[float, float, float, float]:
    """The box the panel at `index` fits into: two to a row, filled left to right."""
    column = index % 2
    row = index // 2
    x = MARGIN + column * (BOX_WIDTH + GAP)
    top = FIRST_ROW - row * ROW_PITCH
    return (x, top - BOX_HEIGHT, BOX_WIDTH, BOX_HEIGHT)


def draw_panel(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    image: hqf_pdf.ImageHandle,
    panel: Panel,
    box: tuple[float, float, float, float],
) -> None:
    """Draws one panel: its box's outline, the picture fitted into it, and its
    caption underneath."""
    x, y, width, height = box

    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(0.75))
    content.set_line_width(0.5)
    content.rect(x, y, width, height)
    content.stroke()
    content.restore_state()

    size = image.size
    content.save_state()
    if panel.clipped:
        content.rect(x, y, width, height)
        content.clip()
        content.end_path()
    content.transform(panel.fitted.matrix(size, box))
    content.draw_image(image, 0.0, 0.0, size[0], size[1])
    content.restore_state()

    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    content.draw_text(font, 8.5, x, y - CAPTION_DROP, panel.caption)
    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("fitted.pdf", language)).stem)

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    font = document.add_font(hqf_pdf.Font.from_path(_out.DEFAULT_FONT))
    image = document.add_image(hqf_pdf.Image.from_path(PHOTO))

    content = hqf_pdf.Content()
    content.draw_text(font, 16.0, MARGIN, 790.0, words.title)

    drawn = panels(words)
    for index, panel in enumerate(drawn):
        draw_panel(content, font, image, panel, panel_box(index))

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

    written = document.write(out)
    print(
        f"wrote {out}: {written} bytes, {len(drawn)} panels of one "
        f"{image.width}x{image.height} picture"
    )


if __name__ == "__main__":
    main()
