"""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 reading software'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 reading software 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: reading software'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 reading
    # software'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, shown when reading software is set to it",
    seeing=(
        "Enlarge the plan past two hundred per cent: the figures on the walls come in "
        "as the",
        "wide label steps back. Set reading software 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, montré quand le logiciel de lecture y 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 logiciel de lecture 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 reading software 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 reading software falls back on when 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 reading software 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()
