"""Writes a document that opens on its own and asks not to be copied.

The Python twin of the `write_restricted` example in Rust: the same document,
through the binding rather than through the library directly.

Every string and every stream in the file is ciphertext, locked with AES under a
two-hundred-and-fifty-six-bit key. The user password is empty, so a reader opens the
file without asking anybody for anything, which is what most protected files in the
world do.

What the file grants is printing, at the resolution the page was drawn at, and reading
aloud. Copying, changing and taking pages out are withheld — as requests a reader honours, not as locks: a reader that ignores
them copies the page all the same, and the page says so rather than letting the file be
taken for a safe.

The document a reader is shown nothing of until a password is typed is `write_locked`,
and this page names it in words rather than by that name.

The page is written in the language `HQF_PDF_LANG` names. The password is not: it is a
string typed into a reader, and a translated password opens nothing.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# How far in from the left edge of the sheet every line is set, in points.
LEFT = 72.0

# The size the heading is set at, in points.
HEADING_SIZE = 18.0

# Where the baseline of the first line of the heading sits, in points up from the foot
# of the sheet.
HEADING_BASELINE = 760.0

# How far one line of the heading sits below the one before it, in points.
HEADING_LEADING = 22.0

# The size the body is set at, in points.
BODY_SIZE = 11.0

# Where the baseline of the first line of the body sits, in points up from the foot of
# the sheet.
FIRST_BASELINE = 700.0

# How far one line of the body sits below the one before it, in points.
LEADING = 18.0

# The size the heading over each closing section is set at, in points.
SECTION_SIZE = 13.0

# Where the baseline of the heading over the other document sits, in points up from the
# foot of the sheet.
COMPANION_HEADING_BASELINE = 520.0

# Where the baseline of the first line about the other document sits, in points up from
# the foot of the sheet.
COMPANION_BASELINE = 492.0

# Where the baseline of the heading over the closing note sits, in points up from the
# foot of the sheet.
NOTE_HEADING_BASELINE = 420.0

# Where the baseline of the first line of the note sits, in points up from the foot of
# the sheet.
NOTE_BASELINE = 392.0

# The author's password, which the page states so that what the file withholds can be
# lifted. It stands outside the words: a password is typed into a reader, and a
# translated one opens nothing.
OWNER_PASSWORD = "the owner"

# The thirty-two bytes the file key is built from.
#
# They are fixed here, so that the example writes the same file on every run and one
# build can be compared with the last. A program takes them from its operating system —
# `os.urandom(32)`. A file locked under a seed anybody can read is a file anybody opens.
SEED = bytes.fromhex(
    "00112233445566778899aabbccddeeff0f1e2d3c4b5a69788796a5b4c3d2e1f0"
)


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

    The password is not among them: it is typed into a reader, not translated.
    """

    # What the document is called, set over two lines at the head of the page and joined
    # by a space in what the file says of itself.
    heading: tuple[str, ...]
    # The body of the page, one line to a line.
    body: tuple[str, ...]
    # What stands before the author's password.
    password_label: str
    # What stands over the lines about the other document.
    companion_heading: str
    # What the other document is, and how it differs from this one.
    companion: tuple[str, ...]
    # What stands over the closing note.
    note_heading: str
    # The closing note, which says where the key comes from.
    note: tuple[str, ...]

    def title(self) -> str:
        """What the file says of itself: the two lines of the heading, in a row."""
        return " ".join(self.heading)


# The page in English.
ENGLISH = Words(
    heading=("A document that reads freely", "and asks not to be copied"),
    body=(
        "Everything written in this file is scrambled: every word,",
        "every picture, under a key of two hundred and fifty-six bits.",
        "It opens all the same. Nobody is asked for anything, which is",
        "what most protected files in the world do.",
        "What it asks is asked of the reading software: let this be",
        "printed and read out loud, but do not let it be copied or",
        "changed. It is a request and not a lock, and software that",
        "pays no heed to it copies the page all the same.",
    ),
    password_label="The author's password lifts the request:",
    companion_heading="The other document of this pair",
    companion=(
        "A second document was written beside this one. That one shows",
        "nothing at all until its password is typed in, while the page",
        "you are reading opened on its own.",
    ),
    note_heading="Where the key comes from",
    note=(
        "The thirty-two bytes the key is built from are fixed in this",
        "example, so that it writes the same file on every run and one",
        "build can be compared with the last. A program takes them from",
        "its operating system: a seed anybody can read locks nothing.",
    ),
)

# The page in French.
FRENCH = Words(
    heading=(
        "Un document qui se lit librement",
        "et demande à ne pas être copié",
    ),
    body=(
        "Tout ce que ce fichier contient est brouillé : chaque mot,",
        "chaque image, sous une clé de deux cent cinquante-six bits.",
        "Il s'ouvre quand même. On ne demande rien à personne, comme",
        "le font la plupart des fichiers protégés.",
        "Ce qu'il demande, il le demande au logiciel de lecture :",
        "laisse imprimer et lire à voix haute, ne laisse ni copier",
        "ni modifier. C'est une demande, pas un verrou, et un logiciel",
        "qui n'en tient pas compte copie la page quand même.",
    ),
    password_label="Le mot de passe de l'auteur lève la demande :",
    companion_heading="L'autre document de la paire",
    companion=(
        "Un second document a été écrit à côté de celui-ci. Lui ne",
        "montre rien du tout tant qu'on n'a pas tapé son mot de passe,",
        "alors que la page que vous lisez s'est ouverte toute seule.",
    ),
    note_heading="D'où vient la clé",
    note=(
        "Les trente-deux octets dont la clé est tirée sont figés dans cet",
        "exemple, pour qu'il écrive le même fichier à chaque fois et qu'une",
        "version se compare à la précédente. Un programme les prend à son",
        "système : une graine que tout le monde peut lire ne ferme rien.",
    ),
)

# 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 lines(words: Words) -> list[str]:
    """What the body says, the last line of which states the author's password."""
    return [*words.body, f"{words.password_label} {OWNER_PASSWORD}"]


def encryption() -> hqf_pdf.Encryption:
    """What the document is locked with: the author's password, and what is allowed.

    What a reader is asked to allow is printing at the resolution the page was drawn
    at, and reading aloud.
    """
    return (
        hqf_pdf.Encryption(SEED)
        .owner_password(OWNER_PASSWORD)
        .permissions(
            hqf_pdf.Permissions()
            .printing()
            .printing_at_full_resolution()
            .extracting_for_accessibility()
        )
    )


def drawing(words: Words) -> list[tuple[str, float, float]]:
    """Every line the page draws, in the order they are drawn.

    Each is what it says, the size it is set at, and where its baseline sits in points
    up from the foot of the sheet.
    """
    drawn = []

    baseline = HEADING_BASELINE
    for line in words.heading:
        drawn.append((line, HEADING_SIZE, baseline))
        baseline -= HEADING_LEADING

    baseline = FIRST_BASELINE
    for line in lines(words):
        drawn.append((line, BODY_SIZE, baseline))
        baseline -= LEADING

    drawn.append(
        (words.companion_heading, SECTION_SIZE, COMPANION_HEADING_BASELINE)
    )
    baseline = COMPANION_BASELINE
    for line in words.companion:
        drawn.append((line, BODY_SIZE, baseline))
        baseline -= LEADING

    drawn.append((words.note_heading, SECTION_SIZE, NOTE_HEADING_BASELINE))
    baseline = NOTE_BASELINE
    for line in words.note:
        drawn.append((line, BODY_SIZE, baseline))
        baseline -= LEADING
    return drawn


def page(
    handle: hqf_pdf.FontHandle, drawn: list[tuple[str, float, float]]
) -> hqf_pdf.Page:
    """The page: every line set by its own origin, each in an object of its own."""
    content = hqf_pdf.Content()
    for line, size, baseline in drawn:
        content.draw_text(handle, size, LEFT, baseline, line)

    result = hqf_pdf.Page.a4()
    result.set_content(content)
    return result


def document(words: Words, face: Path) -> hqf_pdf.Document:
    """The document and its one page, before it is locked."""
    result = hqf_pdf.Document()
    result.set_license(_licence.licensed())
    result.set_info("Title", words.title())
    handle = result.add_font(hqf_pdf.Font.from_path(face))
    result.add_page(page(handle, drawing(words)))
    return result


def written(path: Path, doc: hqf_pdf.Document) -> None:
    """Writes `doc` to `path`, making the directory it goes in if it is not there."""
    path.parent.mkdir(parents=True, exist_ok=True)
    print(f"wrote {path} ({doc.write(path)} bytes)")


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

    doc = document(words, _out.font_path())
    doc.protect(encryption())
    written(Path(out), doc)


if __name__ == "__main__":
    main()
