"""Writes a two-story newsletter whose reading order is not its page order.

The Python twin of the `write_articles` example in Rust. Each story begins in a
column of the front page and carries on in a column of the second — and the two
swap sides on the way, so the column below the one being read is the other
story. Nothing drawn on the page says which carries on which. The articles say
it: each names the patches of page it is read through, in order, and a reader
that follows one goes from the foot of a patch to the head of the next wherever
it falls.

Both stories are held in `Words`, once per language, and `HQF_PDF_LANG` picks
which set is set. The grid is not language: the same four columns hold whichever
words are poured into them.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf


@dataclass(frozen=True)
class Story:
    """One story of the newsletter."""

    # What the reader sees over it, and what the article is called in the reader's
    # panel.
    heading: str
    # Who wrote it.
    author: str
    # The passage poured through the story's two columns.
    passage: str


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

    # What stands across the head of the front page.
    masthead: str
    # The line under it, which says what the file is showing.
    standfirst: str
    # The first story: its heading and the passage it is told in.
    first: Story
    # The second story, told beside it.
    second: Story
    # What stands after a heading where a story is taken up again.
    continued: str

    def stories(self) -> tuple[Story, Story]:
        """The two stories, in the order they are laid out."""
        return (self.first, self.second)


# The newsletter in English.
ENGLISH = Words(
    masthead="The Thursday Sheet",
    standfirst="Two stories, four columns, and a reading order of their own.",
    first=Story(
        heading="The bridge reopens",
        author="Claire Vasseur",
        passage=(
            "The bridge reopened on Tuesday, eleven months after the river took the "
            "middle of it. The county surveyor walked the deck at dawn and declared "
            "it sound, which is the only ceremony anyone asked for. Traffic crossed "
            "within the hour. What took the eleven months was not the deck but the "
            "two piers under it, each rebuilt from the bed up while the water ran "
            "round them. The contractor counted nineteen days lost to the weather "
            "and none at all to the design, which is a rarer thing than it sounds. "
            "The old approach road stays shut until the spring, so the lorries still "
            "go the long way round by the mill. The parish council has asked for a "
            "footway on the upstream side and been told it will wait "
            "its turn. Meanwhile the bus takes four minutes off its run and the "
            "school on the far bank has moved its bell back to where it stood before "
            "the flood. Nobody has yet said what becomes of the temporary crossing "
            "downstream, which cost more to keep open than to build and which the "
            "anglers would like left where it is."
        ),
    ),
    second=Story(
        heading="The harvest, county by county",
        author="Martin Aubert",
        passage=(
            "The wheat came in ten days early and heavier than anyone had allowed "
            "for, and the barns of three parishes were full before the second week "
            "of August. The dry June is what did it, say the growers, and the wet "
            "April before that. Not every field agrees: the high ground east of the "
            "ridge lost a third of what it stood to a wind that came through on a "
            "single afternoon. The merchants have held their price so far, which "
            "nobody expects to last past the sales, and the millers are already "
            "asking what the winter sowing will look like on ground this hard. The "
            "straw is the other half of the story: there is a great deal of it, it "
            "is dry, and the men who bale it have been working the fields two "
            "counties away as well as their own. Two of the larger farms have put "
            "their second cut off a fortnight in the hope of rain. The weighbridge "
            "at the co-operative has been open since six every morning and the queue "
            "at seven is longer than it was last year at nine."
        ),
    ),
    continued="(continued)",
)

# The newsletter in French.
FRENCH = Words(
    masthead="La Feuille du jeudi",
    standfirst="Deux récits, quatre colonnes, et un ordre de lecture à eux.",
    first=Story(
        heading="Le pont rouvre",
        author="Claire Vasseur",
        passage=(
            "Le pont a rouvert mardi, onze mois après que la rivière en a emporté le "
            "milieu. L'ingénieur du département a parcouru le tablier à l'aube et "
            "l'a déclaré sûr, seule cérémonie que quiconque avait réclamée. Les "
            "voitures passaient une heure plus tard. Ce qui a pris onze mois, ce "
            "n'est pas le tablier mais les deux piles en dessous, refaites depuis le "
            "lit pendant que l'eau courait autour. L'entreprise a compté dix-neuf "
            "jours perdus au mauvais temps et aucun aux plans, ce qui est plus rare "
            "qu'il n'y paraît. L'ancienne route d'accès reste fermée jusqu'au "
            "printemps, et les camions font encore le tour par le moulin. Le conseil "
            "municipal a demandé un trottoir du côté amont et s'est entendu répondre "
            "qu'il attendrait son tour. En attendant, le car gagne quatre "
            "minutes sur son trajet et l'école de l'autre rive a remis sa sonnerie à "
            "l'heure qu'elle avait avant la crue. Personne n'a encore dit ce que "
            "devient la passerelle provisoire en aval, qui a coûté plus cher à "
            "entretenir qu'à monter et que les pêcheurs aimeraient voir rester là."
        ),
    ),
    second=Story(
        heading="La moisson, canton par canton",
        author="Martin Aubert",
        passage=(
            "Le blé est rentré dix jours en avance et plus lourd que personne ne "
            "l'avait prévu, et les granges de trois communes étaient pleines avant "
            "la deuxième semaine d'août. C'est le mois de juin sec qui a fait cela, "
            "disent les cultivateurs, et l'avril humide qui l'a précédé. Toutes les "
            "parcelles n'en disent pas autant : les hauteurs à l'est de la crête ont "
            "perdu un tiers de ce qu'elles portaient à cause d'un vent passé en un "
            "seul après-midi. Les négociants tiennent leur prix pour l'instant, ce "
            "que personne n'attend au-delà des ventes, et les meuniers demandent "
            "déjà de quoi auront l'air les semis d'hiver sur une terre aussi dure. "
            "La paille est l'autre moitié de l'affaire : il y en a beaucoup, elle "
            "est sèche, et ceux qui la pressent travaillent les champs de deux "
            "départements en plus des leurs. Deux des plus grosses fermes ont "
            "repoussé leur seconde coupe de quinze jours dans l'espoir de la pluie. "
            "Le pont-bascule de la coopérative ouvre à six heures tous les matins et "
            "la file d'attente à sept est plus longue qu'elle ne l'était l'an "
            "dernier à neuf."
        ),
    ),
    continued="(suite)",
)

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

# The left edge of the column on the left of a page.
LEFT = 70.0
# The left edge of the column on the right of a page.
RIGHT = 310.0
# The width of one column.
COLUMN = 215.0
# The top edge of the two columns of the front page, which the masthead stands above.
FRONT_TOP = 700.0
# The top edge of the two columns of the second page.
INSIDE_TOP = 760.0
# How far down from its top edge a column reaches.
HEIGHT = 190.0
# The size the passages are set at, in points.
SIZE = 9.0
# The distance from one baseline to the next, in points.
LEADING = 12.5
# The size a story's heading is set at, in points.
HEADING_SIZE = 12.0
# How much of a column's height the heading and the line under it take.
HEADING_ROOM = 26.0


@dataclass(frozen=True)
class Patch:
    """One patch of page a story is read through: which page, and where on it."""

    # The page it is on, counting from zero.
    page: int
    # The left edge of the column, in points.
    x: float
    # The top edge of the column, in points up from the foot of the sheet.
    top: float

    def text_top(self) -> float:
        """Where the passage starts, under the room the heading takes."""
        return self.top - HEADING_ROOM


# How much of a column the passage has, once the heading has had its room.
TEXT_HEIGHT = HEIGHT - HEADING_ROOM

# The two patches the first story is read through: the left of the front page, then the
# right of the second.
FIRST = (Patch(0, LEFT, FRONT_TOP), Patch(1, RIGHT, INSIDE_TOP))

# The two patches the second story is read through, which cross the first story's on the
# way: the right of the front page, then the left of the second.
SECOND = (Patch(0, RIGHT, FRONT_TOP), Patch(1, LEFT, INSIDE_TOP))

# The patches each story is read through, in the order the stories are laid out.
PATCHES = (FIRST, SECOND)


def heading(words: Words, story: Story, first: bool) -> str:
    """The heading one patch carries: the story's own on the patch it begins in, and the
    story's own with a word after it on every later patch."""
    return story.heading if first else f"{story.heading} {words.continued}"


def masthead(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
    """Draws the masthead across the head of the front page."""
    content.draw_text(font, 22.0, LEFT, 780.0, words.masthead)
    content.draw_text(font, SIZE, LEFT, 762.0, words.standfirst)
    content.set_line_width(1.0)
    content.move_to(LEFT, 752.0)
    content.line_to(RIGHT + COLUMN, 752.0)
    content.stroke()


def draw_patch(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    flow: hqf_pdf.TextFlow,
    patch: Patch,
    title: str,
    lines: hqf_pdf.Lines,
) -> int:
    """Draws one patch: its frame, its heading, and the slice of the passage it holds.
    Returns how many lines it took."""
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(0.75))
    content.set_line_width(0.5)
    content.rect(patch.x, patch.top - HEIGHT, COLUMN, HEIGHT)
    content.stroke()
    content.restore_state()

    content.draw_text(font, HEADING_SIZE, patch.x + 6.0, patch.top - 16.0, title)

    taken = flow.fit(lines, TEXT_HEIGHT, 0)
    flow.draw(
        content,
        lines.sub(0, taken),
        patch.x + 6.0,
        patch.text_top(),
        COLUMN - 12.0,
    )
    return taken


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

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    document.set_info("Title", words.masthead)
    font = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))
    flow = hqf_pdf.TextFlow(font, SIZE, leading=LEADING, align=hqf_pdf.Align.Justify)

    # One content stream per page, drawn into as each story reaches it.
    sheets = [hqf_pdf.Content(), hqf_pdf.Content()]
    masthead(sheets[0], font, words)

    for story, patches in zip(words.stories(), PATCHES):
        broken = flow.break_lines(story.passage, COLUMN - 12.0)
        start = 0
        for index, patch in enumerate(patches):
            taken = draw_patch(
                sheets[patch.page],
                font,
                flow,
                patch,
                heading(words, story, index == 0),
                broken.sub(start, len(broken)),
            )
            start += taken
        article = hqf_pdf.Article(story.heading, author=story.author)
        for patch in patches:
            article = article.on_page(
                patch.page, patch.x, patch.top - HEIGHT, COLUMN, HEIGHT
            )
        document.add_article(article)

    for content in sheets:
        page = hqf_pdf.Page.a4()
        page.set_content(content)
        document.add_page(page)

    written = document.write(out)
    print(f"wrote {out}: {written} bytes")


if __name__ == "__main__":
    main()
