"""Draws a land plan whose layers decide for themselves when to be shown.

The Python twin of the `write_self_showing_layers` example in Rust. The plot and
the house it holds are the page's own ink and are always there. Three kinds of layer
sit on top and nobody has to touch the reader's panel for any of them: one wide label
stands over the plot while the plan is looked at whole, the fine figures come in once
the plan is enlarged twice over, and one layer per language carries the sentence under
the plan, so a reader set to French reads French.

The page is held in `Words`, once per language, and `HQF_PDF_LANG` picks which set is
drawn. The table of words is also what the language layers are built from: writing one
more set of words gives the document one more layer, without a line that draws being
touched. The distances are not words — a metre is a metre.

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

from __future__ import annotations

import math
from dataclasses import dataclass
from pathlib import Path
from typing import Callable

import _language
import _licence
import _out

import hqf_pdf

# Where the page's text begins, in points from the left edge.
LEFT = 80.0

# The plot: where its lower left corner sits on the page, and how big it is.
PLOT = (LEFT, 380.0, 435.0, 360.0)

# The house inside the plot, as an inset from the plot's lower left corner and a size of
# its own.
HOUSE = (60.0, 90.0, 200.0, 170.0)

# The magnification the wide label gives way to the fine figures at. The label is shown
# below it and the figures from it, so exactly one of the two stands at every
# magnification.
CROSSOVER = 2.0

# How many layers watch the magnification.
SCALED_COUNT = 2

# The distances the plan is measured at, each written inside the line it stands for.
# None of them is language: a metre is a metre.
PLOT_ALONG = "24.30"
PLOT_ACROSS = "17.60"
HOUSE_ALONG = "11.20"
HOUSE_ACROSS = "8.40"
SIDE_SETBACK = "3.35"
FOOT_SETBACK = "5.10"

# The two sides of the plot, written the way the wide label states them.
OVERALL = "24.30 m × 17.60 m"


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

    The name of the language and the sentence under the plan are the language layer's
    own: the reader's panel shows the one and the page shows the other, and both stay
    in their language whatever language the rest of the page is in. Everything else
    follows the page.
    """

    # The line at the head of the page.
    title: str
    # What stands under it, saying what the page is about.
    intro: str
    # What this language is called, which is the name its layer carries in the reader's
    # panel.
    tongue: str
    # The sentence this language's layer carries under the plan.
    caption: str
    # What the two layers that watch the magnification are called.
    scaled: tuple[str, str]
    # What the legend says each of them holds.
    holdings: tuple[str, str]
    # The wide label the plot carries while the plan is looked at whole.
    whole: str
    # What the legend says about the layers that carry the wording.
    wording: str
    # The lines under the legend saying how to see all of it.
    seeing: tuple[str, str]


# The page in English.
ENGLISH = Words(
    title="A plan that shows what can be read",
    intro=(
        "Every layer here decides for itself, by the magnification and by the "
        "language."
    ),
    tongue="English",
    caption="Plot 12, Mill Lane — sold with the barn and the well.",
    scaled=("Whole plot", "Fine figures"),
    holdings=(
        "stands over the plan while it is looked at whole",
        "comes in once the plan is enlarged twice over",
    ),
    whole="PLOT 12",
    wording="one layer a language, and a reader shows the one it is set to",
    seeing=(
        "Enlarge the plan past two hundred per cent: the figures on the walls come "
        "in as the",
        "wide label steps back. Set the reader to another language and the sentence "
        "follows.",
    ),
)

# The page in French.
FRENCH = Words(
    title="Un plan qui montre ce qui se lit",
    intro="Chaque calque décide ici tout seul, par l'agrandissement et par la langue.",
    tongue="Français",
    caption="Parcelle 12, chemin du Moulin — vendue avec la grange et le puits.",
    scaled=("Parcelle entière", "Cotes fines"),
    holdings=(
        "se tient sur le plan tant qu'on le regarde en entier",
        "arrivent dès que le plan est agrandi deux fois",
    ),
    whole="PARCELLE 12",
    wording="un calque par langue, et le lecteur montre celle sur laquelle il est réglé",
    seeing=(
        "Agrandissez le plan au-delà de deux cents pour cent : les cotes des murs "
        "arrivent",
        "et le grand titre s'efface. Réglez le lecteur sur une autre langue, la "
        "phrase suit.",
    ),
)


# Every language the example is written in. A language is added by writing its own set
# of words and naming it here, and the document gains its layer.
WORDS = {_language.ENGLISH: ENGLISH, _language.FRENCH: FRENCH}


def house() -> tuple[float, float, float, float]:
    """The house, as a place and a size on the page."""
    x, y, _, _ = PLOT
    inset_x, inset_y, width, height = HOUSE
    return (x + inset_x, y + inset_y, width, height)


def ground(content: hqf_pdf.Content) -> None:
    """Draws the ink the plan always carries: the plot's boundary and the house
    standing in it."""
    x, y, width, height = PLOT
    hx, hy, hw, hh = house()
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(0.2))
    content.set_line_width(1.6)
    content.rect(x, y, width, height)
    content.stroke()
    content.set_fill(hqf_pdf.Rgb(0.9, 0.91, 0.94))
    content.set_line_width(1.2)
    content.rect(hx, hy, hw, hh)
    content.fill()
    content.rect(hx, hy, hw, hh)
    content.stroke()
    content.restore_state()


def wide_label(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
    """Draws what the plot is called and how big it is, in type that reads from across
    a desk."""
    x, y, width, height = PLOT
    middle = x + width / 2.0
    content.save_state()
    content.set_fill(hqf_pdf.Rgb(0.35, 0.42, 0.55))
    name = font.measure(words.whole, 26.0)
    content.draw_text(
        font, 26.0, middle - name / 2.0, y + height - 54.0, words.whole
    )
    sizes = font.measure(OVERALL, 12.0)
    content.draw_text(font, 12.0, middle - sizes / 2.0, y + height - 76.0, OVERALL)
    content.restore_state()


def fine_figures(content: hqf_pdf.Content, font: hqf_pdf.FontHandle) -> None:
    """Draws the distance each wall and each setback stands for, in type too small to
    read until the plan is enlarged."""
    x, y, width, height = PLOT
    hx, hy, hw, hh = house()
    figures = [
        (x + width / 2.0, y + 5.0, PLOT_ALONG),
        (x + 14.0, y + height / 2.0, PLOT_ACROSS),
        (hx + hw / 2.0, hy + 5.0, HOUSE_ALONG),
        (hx + 14.0, hy + hh / 2.0, HOUSE_ACROSS),
        ((x + hx) / 2.0, hy + hh / 2.0, SIDE_SETBACK),
        (hx + hw / 2.0, (y + hy) / 2.0, FOOT_SETBACK),
    ]
    content.save_state()
    content.set_fill(hqf_pdf.Rgb(0.2, 0.45, 0.35))
    for at, up, figure in figures:
        content.draw_text(
            font, 4.5, at - font.measure(figure, 4.5) / 2.0, up, figure
        )
    content.restore_state()


def legend(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the legend under the plan: what each layer holds and when it stands."""
    baseline = 300.0
    for name, what in zip(words.scaled, words.holdings):
        content.draw_text(font, 10.0, LEFT, baseline, name)
        content.save_state()
        content.set_fill(hqf_pdf.Rgb.gray(0.35))
        content.draw_text(font, 10.0, LEFT + 110.0, baseline, what)
        content.restore_state()
        baseline -= 18.0

    tongues = " · ".join(said.tongue for said in WORDS.values())
    content.draw_text(font, 10.0, LEFT, baseline, tongues)
    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    content.draw_text(font, 10.0, LEFT + 110.0, baseline, words.wording)
    for rank, line in enumerate(words.seeing):
        content.draw_text(font, 10.0, LEFT, baseline - 32.0 - rank * 14.0, line)
    content.restore_state()


def on_layer(
    content: hqf_pdf.Content,
    layer: hqf_pdf.LayerHandle,
    body: Callable[[hqf_pdf.Content], None],
) -> None:
    """Draws `body` inside `layer`, so a reader shows and hides all of it at once."""
    content.begin_layer(layer)
    body(content)
    content.end_marked()


def build(language: str, words: Words) -> hqf_pdf.Document:
    """The document the example writes, in `words`."""
    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    font = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))

    wide = document.add_layer(hqf_pdf.Layer(words.scaled[0]).zoom(0.0, CROSSOVER))
    fine = document.add_layer(
        hqf_pdf.Layer(words.scaled[1]).zoom(CROSSOVER, math.inf)
    )
    # One layer a language, built from the same table the page is written from. The one
    # the page itself is in is the one a reader falls back on when it is set to a
    # language no layer states.
    spoken = []
    for named, said in WORDS.items():
        layer = hqf_pdf.Layer(said.tongue)
        if named == language:
            layer = layer.preferred_language(named)
        else:
            layer = layer.language(named)
        spoken.append((document.add_layer(layer), said))

    content = hqf_pdf.Content()
    content.draw_text(font, 15.0, LEFT, 790.0, words.title)
    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    content.draw_text(font, 10.0, LEFT, 770.0, words.intro)
    content.restore_state()

    ground(content)
    on_layer(content, wide, lambda target: wide_label(target, font, words))
    on_layer(content, fine, lambda target: fine_figures(target, font))
    # Every language's sentence stands on a line of its own, so a reader that states no
    # language of its own reads them one under the other rather than one over the other.
    for rank, (layer, said) in enumerate(spoken):

        def sentence(
            target: hqf_pdf.Content, rank: int = rank, said: Words = said
        ) -> None:
            target.draw_text(font, 10.0, LEFT, 350.0 - rank * 15.0, said.caption)

        on_layer(content, layer, sentence)
    legend(content, font, words)

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


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

    document = build(language, words)
    written = document.write(out)
    print(f"wrote {out}: {written} bytes, {SCALED_COUNT + len(WORDS)} layers")


if __name__ == "__main__":
    main()
