"""Lays an invoice on a letterhead somebody else made.

The Python twin of the `write_overlay` example in Rust. The letterhead is a PDF:
it is read, brought in as a form, drawn under the invoice, and nothing about it
is rewritten. Under the invoice, the page lists what the letterhead says, read back
off it: each run of text with where it begins and the size it is set at.

The invoice's labels, and the one label the stand-in letterhead carries, are held in
`Words`, once per language, and `HQF_PDF_LANG` picks which set is drawn. The leader
dots are counted rather than typed, so a longer label in another language still ends
its amount on the same character.

Usage: python examples/write_overlay.py [out.pdf] [letterhead.pdf]
       HQF_PDF_LANG=fr python examples/write_overlay.py
"""

from __future__ import annotations

import sys
from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# How many characters a billed line runs to: its label, a space, the leader dots, a
# space, then its amount. The dots take whatever is left, so the amounts end on the same
# character however long the label before them is.
LINE_CHARS = 56

# The invoice's number, and what each billed line comes to. Neither is language.
NUMBER = "1964413"
AMOUNTS = ("4 250.00 EUR", "850.00 EUR", "5 100.00 EUR")

# The letterhead's own name, address and registration number. None of them is language:
# they belong to whoever the letterhead is for.
COMPANY = "ACME Ltd"
COMPANY_ADDRESS = "12 Fleet Street — London EC4Y 1AA"
COMPANY_NUMBER = "123 456 789"

# The size each of the five invoice lines is set at, in the order they are laid down.
SIZES = (16.0, 11.0, 11.0, 11.0, 12.0)


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

    What is not language stays out of it: the invoice's number, the three amounts, and
    the letterhead's name, address and number.
    """

    # What stands on the letterhead before its registration number.
    company_number_label: str
    # What stands before the invoice's number.
    number_label: str
    # The line that gives the day the invoice was issued.
    issued: str
    # What each billed line is for, in the order they are set.
    items: tuple[str, str, str]
    # What stands above the runs read back off the letterhead.
    read_back: str
    # What stands for a run whose characters the letterhead's file does not state.
    unread: str

    def lines(self) -> list[str]:
        """The five lines the invoice sets, in order.

        Each billed line is padded out with leader dots until it runs to `LINE_CHARS`.
        """
        lines = [f"{self.number_label} {NUMBER}", self.issued]
        for item, amount in zip(self.items, AMOUNTS):
            dots = max(0, LINE_CHARS - len(item) - len(amount) - 2)
            lines.append(f"{item} {'.' * dots} {amount}")
        return lines

    def letterhead_line(self) -> str:
        """The second line of the stand-in letterhead: an address, then the number the
        company registers under, under whatever this language calls it."""
        return f"{COMPANY_ADDRESS} — {self.company_number_label} {COMPANY_NUMBER}"


# The invoice in English.
ENGLISH = Words(
    company_number_label="Company No.",
    number_label="Invoice No.",
    issued="Issued 14 July 2026",
    items=("Rendering engine development", "VAT at 20 %", "Total due"),
    read_back="What the letterhead says, read back off its page:",
    unread="(a run whose characters the file does not state)",
)

# The invoice in French.
FRENCH = Words(
    company_number_label="N° d’entreprise",
    number_label="Facture n°",
    issued="Émise le 14 juillet 2026",
    items=("Développement du moteur de rendu", "TVA 20 %", "Total à payer"),
    read_back="Ce que dit le papier à en-tête, relu sur sa page\u00a0:",
    unread="(un passage dont le fichier ne donne pas les caractères)",
)

# The invoice in Dutch.
DUTCH = Words(
    company_number_label="KvK-nr.",
    number_label="Factuur nr.",
    issued="Uitgegeven op 14 juli 2026",
    items=("Ontwikkeling van de opmaakmotor", "Btw 20 %", "Te betalen"),
    read_back="Wat het briefpapier zegt, teruggelezen van de pagina:",
    unread="(een stuk tekst waarvan het bestand de tekens niet vermeldt)",
)

# The invoice in Portuguese.
PORTUGUESE = Words(
    company_number_label="NIPC",
    number_label="Fatura n.º",
    issued="Emitida a 14 de julho de 2026",
    items=("Desenvolvimento do motor de paginação", "IVA a 20 %", "Total a pagar"),
    read_back="O que diz o papel timbrado, relido na sua página:",
    unread="(um trecho cujos caracteres o ficheiro não indica)",
)

# The invoice in Italian.
ITALIAN = Words(
    company_number_label="N. REA",
    number_label="Fattura n.",
    issued="Emessa il 14 luglio 2026",
    items=("Sviluppo del motore di impaginazione", "IVA al 20 %", "Totale dovuto"),
    read_back="Ciò che dice la carta intestata, riletto sulla sua pagina:",
    unread="(un tratto di cui il file non indica i caratteri)",
)

# The invoice in Spanish.
SPANISH = Words(
    company_number_label="N.º de empresa",
    number_label="Factura n.º",
    issued="Emitida el 14 de julio de 2026",
    items=("Desarrollo del motor de maquetación", "IVA al 20 %", "Total a pagar"),
    read_back="Lo que dice el membrete, leído de nuevo en su página:",
    unread="(un fragmento cuyos caracteres el archivo no indica)",
)

# The invoice in German.
GERMAN = Words(
    company_number_label="Handelsregister Nr.",
    number_label="Rechnung Nr.",
    issued="Ausgestellt am 14. Juli 2026",
    items=("Entwicklung der Rendering-Engine", "MwSt. 20 %", "Zu zahlen"),
    read_back="Was der Briefkopf sagt, von seiner Seite zurückgelesen:",
    unread="(ein Abschnitt, dessen Zeichen die Datei nicht angibt)",
)


# 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, _language.GERMAN: GERMAN, _language.SPANISH: SPANISH, _language.ITALIAN: ITALIAN, _language.PORTUGUESE: PORTUGUESE, _language.DUTCH: DUTCH}


def a_letterhead(words: Words) -> bytes:
    """A letterhead, for when the caller has none to hand."""
    doc = hqf_pdf.Document()
    doc.set_license(_licence.licensed())
    font = doc.add_font(hqf_pdf.Font.from_path(_out.DEFAULT_FONT))

    content = hqf_pdf.Content()
    content.save_state()
    content.set_fill(hqf_pdf.Rgb(0.91, 0.94, 0.98))
    content.rect(0.0, 742.0, 595.276, 100.0)
    content.fill()
    content.set_fill(hqf_pdf.Rgb(0.2, 0.35, 0.6))
    content.rect(0.0, 738.0, 595.276, 4.0)
    content.fill()
    content.restore_state()

    content.draw_text(font, 22.0, 56.0, 786.0, COMPANY)
    content.draw_text(font, 9.0, 56.0, 764.0, words.letterhead_line())

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


def run_line(run: hqf_pdf.TextRun, words: Words) -> str:
    """One run read back off the letterhead, as the page lists it: its text, then where
    it begins and the size it is set at, where the file says so."""
    text = run.text if run.text is not None else words.unread
    if run.origin is None or run.size is None:
        return text
    x, y = run.origin
    return f"{text} · {x:.0f}, {y:.0f} · {run.size:.0f} pt"


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

    # The letterhead: the caller's, or one made here to stand in for it.
    if len(sys.argv) > 2:
        letterhead = open(sys.argv[2], "rb").read()
    else:
        letterhead = a_letterhead(words)

    reader = hqf_pdf.Reader(letterhead)
    print(f"the letterhead has {reader.page_count} page(s)")

    doc = hqf_pdf.Document()
    doc.set_license(_licence.licensed())

    template = doc.import_page(reader, 0)
    print(f"imported it: {template.width:.0f} × {template.height:.0f} points")

    font = doc.add_font(hqf_pdf.Font.from_path(_out.DEFAULT_FONT))

    content = hqf_pdf.Content()
    content.draw_form(template)

    top = 680.0
    for line, size in zip(words.lines(), SIZES):
        content.draw_text(font, size, 56.0, top, line)
        top -= size * 2.0

    # What the letterhead says, read back off its page, under a line of its own: each
    # run with where it begins and the size it is set at.
    said = reader.page_text(0)
    read_back = [(11.0, words.read_back)]
    read_back.extend((9.0, run_line(run, words)) for run in said.runs)
    top -= 12.0
    for size, line in read_back:
        content.draw_text(font, size, 56.0, top, line)
        print(line)
        top -= size * 1.6

    page = hqf_pdf.Page(template.width, template.height)
    page.set_content(content)
    template.add_to(page)
    doc.add_page(page)

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


if __name__ == "__main__":
    main()
