"""A sheet as it goes to the press, and what each box a page states does to it.

The Python twin of the `write_press_sheet` example in Rust. A page has always had
a sheet. It now also has the four boxes a printer reads: what the ink runs to,
where the blade falls, what is worth keeping, and what reading software shows.

What the sheet says is written in the language `HQF_PDF_LANG` names. The prices are
not, and neither are the measurements: the two the note quotes are read off the sheet
the page is drawn on.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# How far past the blade the ink runs.
BLEED_OVER = 9.0
# How much sheet is left outside the ink for the marks.
MARK_ROOM = 27.0
# How long one arm of a corner mark is.
MARK_LENGTH = 18.0

# How much sheet stands outside the finished page on every side.
MARGIN = BLEED_OVER + MARK_ROOM

# The finished page: where the blade falls, in the sheet's own coordinates.
TRIM = (MARGIN, MARGIN, 400.0, 560.0)

# The whole sheet: the finished page, the ink that runs past it, and the room the marks
# stand in.
SHEET = (TRIM[2] + MARGIN + MARGIN, TRIM[3] + MARGIN + MARGIN)

# How far inside the blade the drawing itself keeps.
ART_INSET = 40.0

# The colour the band across the top is laid in.
BAND = (0.11, 0.26, 0.44)

# A4, in points.
A4 = (595.276, 841.890)

# The company whose list is being printed, which no language renames.
COMPANY = "HQF Development"

# The sizes the sheet is set in, in points.
TITLE_SIZE = 22.0
BODY_SIZE = 10.0
FOOT_SIZE = 8.0
NOTE_SIZE = 7.0
CAPTION_SIZE = 8.0
TURNED_TITLE_SIZE = 18.0

# How far one line of body stands under the one above it, in points.
BODY_STEP = 16.0

# Where the label over the arrow between the two sketches begins.
ARROW_LABEL_LEFT = 288.0

# What each row of the list costs, in the order the rows are drawn. A price is not
# language.
PRICES = ("18.40", "6.90", "42.00", "31.50", "12.75", "9.20")

# How far in from the right of what is worth keeping a price stands.
PRICE_INSET = 30.0

# How far in from the right of its box a guide's name stands.
GUIDE_INSET = 30.0

# Where the lowest line of the note in the margin sits, and how far the one above it
# stands.
NOTE_BASELINE = 8.0
NOTE_STEP = 9.0

# The two sketches: where each stands on the turned page.
SKETCHES = ((150.0, 140.0, 110.0, 155.0), (400.0, 165.0, 155.0, 110.0))

# Where the turned page sets every line from.
TURNED_LEFT = 70.0


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

    The prices are not among them, nor the two measurements the last page quotes: those
    are read off the sheet.
    """

    # What the list being printed is called, in the band at the head.
    title: str
    # The line under it, which says what the band does at the blade.
    band: str
    # The paragraph that says why a sheet allows for the blade twice over.
    blade: tuple[str, ...]
    # The paragraph that says what the boxes spare the reader.
    boxes: tuple[str, ...]
    # What each row of the list is, in the order they are drawn.
    rows: tuple[str, ...]
    # What stands after the name of the company at the foot.
    foot: str
    # What names each guide, in the order they are drawn: the ink, the blade, then what
    # is worth keeping.
    guides: tuple[str, ...]
    # The note in the margin of the first sheet, which shows whole.
    first_note: tuple[str, ...]
    # The note in the margin of the second, which is cropped to the blade.
    second_note: tuple[str, ...]
    # What the turned page is called.
    turned_title: str
    # What stands before the width of the sheet in the first line of the turned page.
    across_before: str
    # What stands between the width and the height.
    across_between: str
    # What stands after the height.
    across_after: str
    # The rest of that first paragraph, which says what the page adds.
    quarter: str
    # The paragraph that says what reading software does with the turn.
    reading_software: tuple[str, ...]
    # What the sheet drawn as the file holds it is called.
    in_file: str
    # What the sheet drawn as reading software shows it is called.
    on_screen: str
    # What the arrow between the two sketches says.
    turn: str


# The sheet in English.
ENGLISH = Words(
    title="Autumn list",
    band="The band above runs off the blade: no white edge is left.",
    blade=(
        "The blade never falls exactly where it is",
        "aimed. A sheet allows for that twice over:",
        "the ink runs past the cut, and nothing that",
        "matters stands within a few points of it.",
    ),
    boxes=(
        "The boxes say all three distances, so nobody",
        "has to measure the file to find out where the",
        "page really ends.",
    ),
    rows=(
        "Linen paper, ream",
        "Bookbinder's thread",
        "Corner mark punch",
        "Trim gauge, steel",
        "Bleed guide, acrylic",
        "Press blanket wash",
    ),
    foot=" — one sheet, four boxes",
    guides=("bleed", "trim", "art"),
    first_note=(
        "Page 1 states the bleed, the trim and the art, and no crop: reading software "
        "shows the whole",
        "sheet, marks and all, which is what the press wants to see.",
    ),
    second_note=(
        "Page 2 is the same drawing and the same boxes, plus a crop set to the trim: "
        "reading software",
        "shows the finished page alone. Nothing was removed — what is "
        "outside is still here.",
    ),
    turned_title="A landscape page inside an upright document",
    across_before="This page is the same upright sheet as the two before it: ",
    across_between=" points across, ",
    across_after=" up. What",
    quarter="it adds is one number saying that reading software turns it a quarter "
    "clockwise before showing it.",
    reading_software=(
        "So the words are wider than the sheet they are written on, and nobody "
        "has to be handed a",
        "second page size to say so. Reading software printing the file turns the "
        "paper rather than the",
        "drawing, and reading software showing it hands you the page the way round it "
        "was meant.",
    ),
    in_file="as it sits in the file",
    on_screen="as reading software shows it",
    turn="a quarter clockwise",
)

# The sheet in French.
FRENCH = Words(
    title="Catalogue d'automne",
    band="Le bandeau déborde la coupe : aucun liseré blanc ne reste.",
    blade=(
        "La lame ne tombe jamais exactement où on",
        "la vise. La feuille s'en garde deux fois :",
        "l'encre déborde la coupe, et rien de ce qui",
        "compte ne s'en approche à quelques points.",
    ),
    boxes=(
        "Les boîtes donnent les trois distances, donc",
        "personne n'a à mesurer le fichier pour savoir",
        "où la page finit vraiment.",
    ),
    rows=(
        "Papier de lin, rame",
        "Fil de relieur",
        "Emporte-pièce à repères",
        "Calibre de coupe, acier",
        "Guide de fond perdu, acrylique",
        "Lavant pour blanchet",
    ),
    foot=" — une feuille, quatre boîtes",
    guides=("fond perdu", "coupe", "zone utile"),
    first_note=(
        "La page 1 donne le fond perdu, la coupe et la zone utile, sans recadrage : le "
        "logiciel de lecture montre",
        "toute la feuille, repères compris, ce que l'imprimeur veut voir.",
    ),
    second_note=(
        "La page 2 est le même dessin et les mêmes boîtes, plus un recadrage sur la "
        "coupe : le logiciel de lecture",
        "montre la page finie seule. Rien n'a été retiré — ce qui est dehors "
        "y est toujours.",
    ),
    turned_title="Une page à l'italienne dans un document à la française",
    across_before="Cette page est la même feuille droite que les deux d'avant : ",
    across_between=" points de large, ",
    across_after=" de haut. Ce",
    quarter="qu'elle ajoute est un nombre qui dit qu'un logiciel de lecture la tourne "
    "d'un quart de tour horaire.",
    reading_software=(
        "Les mots sont donc plus larges que la feuille sur laquelle ils sont "
        "écrits, et personne n'a",
        "besoin d'un second format de page pour le dire. Un logiciel de lecture qui "
        "imprime le fichier tourne le",
        "papier plutôt que le dessin, et un logiciel de lecture qui l'affiche vous "
        "rend la page dans le bon sens.",
    ),
    in_file="telle qu'elle est dans le fichier",
    on_screen="telle qu'un logiciel de lecture la montre",
    turn="un quart de tour horaire",
)

# 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 foot(words: Words) -> str:
    """The line of a page's foot, which names the company and what the page shows."""
    return f"{COMPANY}{words.foot}"


def how_wide(words: Words) -> str:
    """The first line of the turned page, which quotes the two measurements of the
    sheet it is drawn on rather than repeating them."""
    return (
        f"{words.across_before}{A4[0]:.0f}{words.across_between}"
        f"{A4[1]:.0f}{words.across_after}"
    )


def band():
    """The colour the band across the top is laid in."""
    return hqf_pdf.Rgb(BAND[0], BAND[1], BAND[2])


def trim():
    """Where the blade falls."""
    return hqf_pdf.Rect(TRIM[0], TRIM[1], TRIM[2], TRIM[3])


def bleed():
    """What the ink runs to."""
    return hqf_pdf.Rect(
        TRIM[0] - BLEED_OVER,
        TRIM[1] - BLEED_OVER,
        TRIM[2] + BLEED_OVER + BLEED_OVER,
        TRIM[3] + BLEED_OVER + BLEED_OVER,
    )


def art():
    """What is worth keeping of the page."""
    return hqf_pdf.Rect(
        TRIM[0] + ART_INSET,
        TRIM[1] + ART_INSET,
        TRIM[2] - ART_INSET - ART_INSET,
        TRIM[3] - ART_INSET - ART_INSET,
    )


def corner_marks(content):
    """Draw the four corner marks.

    Each pair of arms stands outside the ink and points at a corner of the
    finished page.
    """
    left, bottom, width, height = TRIM
    right = left + width
    top = bottom + height
    near = BLEED_OVER
    far = BLEED_OVER + MARK_LENGTH

    content.save_state()
    content.set_stroke(hqf_pdf.Color.gray(0.0))
    content.set_line_width(0.4)
    for x, from_x, to_x in [
        (left, left - near, left - far),
        (right, right + near, right + far),
    ]:
        for y, from_y, to_y in [
            (bottom, bottom - near, bottom - far),
            (top, top + near, top + far),
        ]:
            content.move_to(from_x, y)
            content.line_to(to_x, y)
            content.move_to(x, from_y)
            content.line_to(x, to_y)
    content.stroke()
    content.restore_state()


def listing(content, font, words, top, keep):
    """Draw the list itself, one row a line, each under a hairline."""
    right = keep.x + keep.width
    y = top
    for item, price in zip(words.rows, PRICES):
        content.save_state()
        content.set_fill(hqf_pdf.Color.gray(0.25))
        content.draw_text(font, BODY_SIZE, keep.x, y, item)
        content.draw_text(font, BODY_SIZE, right - PRICE_INSET, y, price)
        content.set_stroke(hqf_pdf.Color.gray(0.8))
        content.set_line_width(0.4)
        content.move_to(keep.x, y - 8.0)
        content.line_to(right, y - 8.0)
        content.stroke()
        content.restore_state()
        y -= 26.0


def artwork(content, font, words):
    """Draw the artwork: a band off the blade, a title, body, a list, a footer."""
    ink = bleed()
    keep = art()

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.96))
    content.rect(ink.x, ink.y, ink.width, ink.height)
    content.fill()
    content.set_fill(band())
    content.rect(ink.x, ink.y + ink.height - 150.0, ink.width, 150.0)
    content.fill()
    content.restore_state()

    content.save_state()
    content.set_fill(hqf_pdf.Rgb(1.0, 1.0, 1.0))
    content.draw_text(
        font, TITLE_SIZE, keep.x, keep.y + keep.height - 30.0, words.title
    )
    content.set_fill(hqf_pdf.Rgb(0.82, 0.86, 0.92))
    content.draw_text(
        font, BODY_SIZE, keep.x, keep.y + keep.height - 50.0, words.band
    )
    content.restore_state()

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.25))
    y = keep.y + keep.height - 130.0
    for paragraph in [words.blade, words.boxes]:
        for line in paragraph:
            content.draw_text(font, BODY_SIZE, keep.x, y, line)
            y -= BODY_STEP
        # The blank between two paragraphs is a step, not a line: an empty line drawn is
        # an empty text object written into the file.
        y -= BODY_STEP
    content.restore_state()

    listing(content, font, words, keep.y + keep.height - 290.0, keep)

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.5))
    content.draw_text(font, FOOT_SIZE, keep.x, keep.y, foot(words))
    content.restore_state()


def guide_boxes():
    """The box each guide is drawn around, and the colour it is drawn in, in the order
    they are named."""
    return (
        (bleed(), hqf_pdf.Rgb(0.85, 0.35, 0.20)),
        (trim(), hqf_pdf.Rgb(0.20, 0.55, 0.35)),
        (art(), hqf_pdf.Rgb(0.45, 0.35, 0.65)),
    )


def guides(content, font, words):
    """Draw the guide showing where each box falls, and name it."""
    content.save_state()
    content.set_line_width(0.4)
    for (box, color), label in zip(guide_boxes(), words.guides):
        content.set_stroke(color)
        content.rect(box.x, box.y, box.width, box.height)
        content.stroke()
        content.set_fill(color)
        content.draw_text(
            font, NOTE_SIZE, box.x + box.width - GUIDE_INSET, box.y - 8.0, label
        )
    content.restore_state()


def sheet(font, words, note):
    """The whole sheet: artwork, marks, guides, and the note in the margin."""
    content = hqf_pdf.Content()
    artwork(content, font, words)
    corner_marks(content)
    guides(content, font, words)

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.35))
    y = NOTE_BASELINE
    for line in reversed(note):
        content.draw_text(font, NOTE_SIZE, TRIM[0], y, line)
        y += NOTE_STEP
    content.restore_state()
    return content


def sketch(content, font, corner, caption):
    """Draw one sheet of the little diagram.

    A sheet taller than it is wide is one reading software has not turned yet, so the
    head of the drawing runs down its left edge; a wide one has been turned, and
    the head is along its top.
    """
    x, y, width, height = corner
    if height > width:
        head = (x + 8.0, y + 8.0, 12.0, height - 16.0)
    else:
        head = (x + 8.0, y + height - 20.0, width - 16.0, 12.0)

    content.save_state()
    content.set_stroke(hqf_pdf.Color.gray(0.55))
    content.set_line_width(0.8)
    content.rect(x, y, width, height)
    content.stroke()
    content.set_fill(band())
    content.rect(head[0], head[1], head[2], head[3])
    content.fill()
    content.set_fill(hqf_pdf.Color.gray(0.45))
    content.draw_text(font, CAPTION_SIZE, x, y - 14.0, caption)
    content.restore_state()


def turned(font, words):
    """A landscape page inside an upright document.

    Everything is laid out in a space as wide as the sheet is tall, and the one
    transform at the head of the stream puts that space onto the upright sheet.
    """
    across, up = A4[1], A4[0]
    content = hqf_pdf.Content()
    content.transform((0.0, 1.0, -1.0, 0.0, A4[0], 0.0))

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.96))
    content.rect(0.0, 0.0, across, up)
    content.fill()
    content.set_fill(band())
    content.rect(0.0, up - 90.0, across, 90.0)
    content.fill()
    content.restore_state()

    content.save_state()
    content.set_fill(hqf_pdf.Rgb(1.0, 1.0, 1.0))
    content.draw_text(
        font, TURNED_TITLE_SIZE, TURNED_LEFT, up - 55.0, words.turned_title
    )
    content.restore_state()

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.3))
    y = up - 130.0
    for paragraph in [(how_wide(words), words.quarter), words.reading_software]:
        for line in paragraph:
            content.draw_text(font, BODY_SIZE, TURNED_LEFT, y, line)
            y -= BODY_STEP
        # The blank between two paragraphs is a step, not a line: an empty line drawn is
        # an empty text object written into the file.
        y -= BODY_STEP
    content.restore_state()

    for corner, caption in zip(SKETCHES, (words.in_file, words.on_screen)):
        sketch(content, font, corner, caption)

    content.save_state()
    content.set_stroke(hqf_pdf.Color.gray(0.55))
    content.set_line_width(1.2)
    content.move_to(295.0, 218.0)
    content.line_to(360.0, 218.0)
    content.line_to(348.0, 226.0)
    content.move_to(360.0, 218.0)
    content.line_to(348.0, 210.0)
    content.stroke()
    content.set_fill(hqf_pdf.Color.gray(0.45))
    content.draw_text(font, CAPTION_SIZE, ARROW_LABEL_LEFT, 234.0, words.turn)
    content.restore_state()
    return content


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

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

    first = hqf_pdf.Page(SHEET[0], SHEET[1])
    first.set_content(sheet(font, words, words.first_note))
    first.set_bleed_box(bleed())
    first.set_trim_box(trim())
    first.set_art_box(art())
    document.add_page(first)

    second = hqf_pdf.Page(SHEET[0], SHEET[1])
    second.set_content(sheet(font, words, words.second_note))
    second.set_crop_box(trim())
    second.set_bleed_box(bleed())
    second.set_trim_box(trim())
    second.set_art_box(art())
    document.add_page(second)

    third = hqf_pdf.Page.a4()
    third.set_content(turned(font, words))
    third.set_turn(hqf_pdf.PageTurn.Right)
    document.add_page(third)

    written = document.write(out)
    print(
        f"wrote {out}: {written} bytes, one drawing on {document.page_count} "
        f"pages, each stating its own boxes"
    )


if __name__ == "__main__":
    main()
