"""A chart of colours, and a drawing that names places in it.

The Python twin of the `write_color_table` example in Rust. A colour in such a
space is one number, and that number is not a colour: it is a row of the table
the document carries. The same sixteen numbers are drawn twice below, through
two tables, and what changes is the table.

The page is held in `Words`, once per language, and `HQF_PDF_LANG` picks which set is
drawn. The two tables are not language: eight rows of red, green and blue are the same
eight rows whoever reads the page.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The left edge of everything.
LEFT = 70.0

# The house colours, eight entries of red, green and blue.
HOUSE = bytes(
    [
        0x1B, 0x36, 0x5D,  # deep blue
        0x2E, 0x86, 0x86,  # teal
        0xE8, 0xB3, 0x3B,  # gold
        0xD9, 0x5F, 0x43,  # coral
        0x7A, 0x4B, 0x7E,  # plum
        0xE3, 0xD5, 0xB8,  # sand
        0x5C, 0x67, 0x70,  # slate
        0x18, 0x1C, 0x22,  # ink
    ]
)

# The same eight places, filled in for a different season.
SEASON = bytes(
    [
        0x0B, 0x4F, 0x3A,  # pine
        0x8C, 0xC0, 0x63,  # moss
        0xF2, 0xE8, 0xC9,  # linen
        0xB0, 0x2E, 0x4A,  # berry
        0x3D, 0x2B, 0x1F,  # bark
        0xE7, 0xA3, 0x6B,  # clay
        0x9A, 0xA5, 0x9E,  # lichen
        0x14, 0x18, 0x14,  # peat
    ]
)

# The drawing itself: sixteen places in whichever table is being read, laid out four to
# a row from the top down.
PLACES = [
    0.0, 1.0, 2.0, 3.0,
    1.0, 4.0, 5.0, 2.0,
    2.0, 5.0, 6.0, 1.0,
    3.0, 2.0, 1.0, 7.0,
]

# How many places are drawn across.
ACROSS = 4

# The size of one swatch of a chart, the gap to the next, and the height.
CHART_CELL = 50.0
CHART_GAP = 6.0
CHART_HEIGHT = 34.0

# The size of one square of the drawing, and the gap to the next.
SQUARE = 44.0
SQUARE_GAP = 5.0


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

    The two tables are not here: eight rows of red, green and blue are the same eight
    rows in every language, and so are the numbers under the swatches.
    """

    # The line at the head of the page.
    title: str
    # The two lines under it, saying what the drawings below are.
    intro: tuple[str, str]
    # The first panel's heading and the line under it.
    house: tuple[str, str]
    # The second panel's, once the table has been swapped.
    season: tuple[str, str]
    # The two lines at the foot of the page.
    closing: tuple[str, str]
    # The line that sizes a table, read from the table itself: `{bytes}` and `{numbers}`
    # stand for the two figures.
    size: str


# The page in English.
ENGLISH = Words(
    title="A chart of colours, and a drawing that names places in it",
    intro=(
        (
            "The two drawings below are the same sixteen numbers. Neither of them says "
            "a colour:"
        ),
        (
            "each says which row of the table to look in, and the document carries the "
            "table."
        ),
    ),
    house=(
        "The house colours",
        "Eight rows of red, green and blue, carried in the file and named 0 to 7.",
    ),
    season=(
        "The same drawing, one table later",
        (
            "The same eight places, filled in for another season. The drawing was not "
            "touched."
        ),
    ),
    closing=(
        (
            "Repainting the house is one table, not one drawing: nothing above was "
            "drawn twice, and nothing"
        ),
        "in either drawing had to be found and changed.",
    ),
    size=(
        "Each table is {bytes} bytes: {numbers} numbers a row, one for each component "
        "of its space."
    ),
)

# The page in French.
FRENCH = Words(
    title="Une table de couleurs, et un dessin qui en désigne les lignes",
    intro=(
        (
            "Les deux dessins ci-dessous reposent sur les seize mêmes nombres. Aucun ne "
            "donne"
        ),
        (
            "de couleur : chacun renvoie à une ligne de la table, et c’est le document "
            "qui contient la table."
        ),
    ),
    house=(
        "Les couleurs de la maison",
        (
            "Huit lignes de rouge, de vert et de bleu, contenues dans le fichier et "
            "numérotées de 0 à 7."
        ),
    ),
    season=(
        "Le même dessin, une table plus tard",
        (
            "Les huit mêmes lignes, remplies pour une autre saison. Le dessin n’a pas "
            "été touché."
        ),
    ),
    closing=(
        (
            "Repeindre la maison, c’est changer une table, pas un dessin : rien n’a "
            "été dessiné deux fois,"
        ),
        "et rien, dans aucun des deux dessins, n’a dû être retrouvé ni modifié.",
    ),
    size=(
        "Chaque table fait {bytes} octets : {numbers} nombres par ligne, un par "
        "composante de couleur."
    ),
)

# The page in Dutch.
DUTCH = Words(
    title="Een kleurenkaart, en een tekening die naar haar vakjes verwijst",
    intro=(
        (
            "De tekeningen hieronder bestaan uit dezelfde zestien getallen. Geen "
            "ervan noemt een kleur:"
        ),
        (
            "elk zegt in welke rij van de tabel te kijken, en de tabel zit in het "
            "document."
        ),
    ),
    house=(
        "De huiskleuren",
        "Acht rijen rood, groen en blauw, opgeslagen in het bestand en genummerd van 0 tot 7.",
    ),
    season=(
        "Dezelfde tekening, één tabel later",
        (
            "Dezelfde acht vakjes, ingevuld voor een ander seizoen. De tekening is "
            "niet aangeraakt."
        ),
    ),
    closing=(
        (
            "Het huis overschilderen is één tabel, niet één tekening: niets hierboven "
            "is twee keer getekend, en in"
        ),
        "geen van beide tekeningen hoefde iets gezocht en veranderd te worden.",
    ),
    size=(
        "Elke tabel is {bytes} bytes: {numbers} getallen per rij, één per component "
        "van zijn ruimte."
    ),
)

# The page in Portuguese.
PORTUGUESE = Words(
    title="Uma tabela de cores, e um desenho que nomeia as suas casas",
    intro=(
        (
            "Os dois desenhos abaixo são os mesmos dezasseis números. Nenhum diz uma "
            "cor:"
        ),
        (
            "cada um diz em que linha da tabela procurar, e o documento leva a tabela."
        ),
    ),
    house=(
        "As cores da casa",
        "Oito linhas de vermelho, verde e azul, levadas no ficheiro e numeradas de 0 a 7.",
    ),
    season=(
        "O mesmo desenho, uma tabela depois",
        (
            "As mesmas oito casas, preenchidas para outra estação. O desenho não foi "
            "tocado."
        ),
    ),
    closing=(
        (
            "Repintar a casa é uma tabela, não um desenho: nada acima foi desenhado "
            "duas vezes, e nada"
        ),
        "em nenhum dos desenhos teve de ser procurado e mudado.",
    ),
    size=(
        "Cada tabela ocupa {bytes} bytes: {numbers} números por linha, um por "
        "componente do seu espaço."
    ),
)

# The page in Italian.
ITALIAN = Words(
    title="Una tabella di colori, e un disegno che ne nomina le caselle",
    intro=(
        (
            "I due disegni qui sotto sono gli stessi sedici numeri. Nessuno dei due "
            "dice un colore:"
        ),
        (
            "ciascuno dice in quale riga della tabella guardare, e il documento porta "
            "la tabella."
        ),
    ),
    house=(
        "I colori della casa",
        "Otto righe di rosso, verde e blu, portate nel file e numerate da 0 a 7.",
    ),
    season=(
        "Lo stesso disegno, una tabella dopo",
        (
            "Le stesse otto caselle, riempite per un’altra stagione. Il disegno non è "
            "stato toccato."
        ),
    ),
    closing=(
        (
            "Ridipingere la casa è cambiare una tabella, non un disegno: niente sopra "
            "è stato disegnato due volte, e"
        ),
        "in nessuno dei due disegni c’è stato qualcosa da cercare e cambiare.",
    ),
    size=(
        "Ogni tabella occupa {bytes} byte: {numbers} numeri per riga, uno per "
        "componente del suo spazio."
    ),
)

# The page in Spanish.
SPANISH = Words(
    title="Una carta de colores y un dibujo que nombra sus casillas",
    intro=(
        (
            "Los dos dibujos de abajo son los mismos dieciséis números. Ninguno dice "
            "un color:"
        ),
        (
            "cada uno dice en qué fila de la tabla mirar, y el documento lleva la "
            "tabla."
        ),
    ),
    house=(
        "Los colores de la casa",
        "Ocho filas de rojo, verde y azul, guardadas en el archivo y numeradas de 0 a 7.",
    ),
    season=(
        "El mismo dibujo, una tabla después",
        (
            "Las mismas ocho casillas, rellenadas para otra temporada. El dibujo no "
            "se tocó."
        ),
    ),
    closing=(
        (
            "Repintar la casa es una tabla, no un dibujo: nada de arriba se dibujó "
            "dos veces, y en"
        ),
        "ninguno de los dibujos hubo que buscar nada para cambiarlo.",
    ),
    size=(
        "Cada tabla ocupa {bytes} bytes: {numbers} números por fila, uno por "
        "componente de su espacio."
    ),
)

# The page in German.
GERMAN = Words(
    title="Eine Farbtafel und eine Zeichnung, die Plätze darin benennt",
    intro=(
        (
            "Die zwei Zeichnungen unten sind dieselben sechzehn Zahlen. Keine nennt "
            "eine Farbe:"
        ),
        (
            "Jede sagt, in welcher Zeile der Tafel nachzusehen ist, und das Dokument "
            "trägt die Tafel."
        ),
    ),
    house=(
        "Die Hausfarben",
        "Acht Zeilen Rot, Grün und Blau, in der Datei mitgeführt und von 0 bis 7 nummeriert.",
    ),
    season=(
        "Dieselbe Zeichnung, eine Tafel später",
        (
            "Dieselben acht Plätze, für eine andere Saison gefüllt. Die Zeichnung "
            "blieb unberührt."
        ),
    ),
    closing=(
        (
            "Umfärben heißt: die Tafel tauschen, nicht die Zeichnung. Nichts oben "
            "wurde zweimal gezeichnet, und"
        ),
        "in keiner der Zeichnungen musste etwas gesucht und geändert werden.",
    ),
    size=(
        "Jede Tafel hat {bytes} Bytes: {numbers} Zahlen je Zeile, eine je Komponente "
        "ihres Farbraums."
    ),
)

# 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 heading(content, font, words):
    """Draw the title and the two lines saying what the page is showing."""
    content.draw_text(font, 15.0, LEFT, 780.0, words.title)
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.35))
    content.draw_text(font, 10.0, LEFT, 762.0, words.intro[0])
    content.draw_text(font, 10.0, LEFT, 748.0, words.intro[1])
    content.restore_state()


def panel_heading(content, font, y, title, under):
    """Draw one panel's heading, with the line under it."""
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.2))
    content.draw_text(font, 11.0, LEFT, y, title)
    content.set_fill(hqf_pdf.Color.gray(0.45))
    content.draw_text(font, 8.0, LEFT, y - 15.0, under)
    content.restore_state()


def chart(content, font, space, entries, bottom):
    """Draw the table itself, one swatch per row, under the number naming it."""
    x = LEFT
    place = 0.0
    for _ in range(entries):
        content.save_state()
        content.set_fill_space(space)
        content.set_fill_components([place])
        content.rect(x, bottom, CHART_CELL, CHART_HEIGHT)
        content.fill()
        content.restore_state()

        content.save_state()
        content.set_fill(hqf_pdf.Color.gray(0.45))
        content.draw_text(font, 8.0, x, bottom - 12.0, f"{place:.0f}")
        content.restore_state()

        x += CHART_CELL + CHART_GAP
        place += 1.0


def drawing(content, space, top):
    """Draw the sixteen places, four to a row from the top down."""
    x = LEFT
    y = top - SQUARE
    for count, place in enumerate(PLACES):
        content.save_state()
        content.set_fill_space(space)
        content.set_fill_components([place])
        content.rect(x, y, SQUARE, SQUARE)
        content.fill()
        content.restore_state()

        if (count + 1) % ACROSS == 0:
            x = LEFT
            y -= SQUARE + SQUARE_GAP
        else:
            x += SQUARE + SQUARE_GAP


def closing(content, font, words):
    """Draw the closing note."""
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.35))
    content.draw_text(font, 9.0, LEFT, 80.0, words.closing[0])
    content.draw_text(font, 9.0, LEFT, 66.0, words.closing[1])
    content.restore_state()


def table(entries):
    """The table `entries` hold, read through the profile the library builds."""
    profile = hqf_pdf.IccBased(hqf_pdf.srgb_icc_profile(), hqf_pdf.DeviceSpace.Rgb)
    return hqf_pdf.Indexed(profile, entries)


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

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

    house = table(HOUSE)
    season = table(SEASON)
    entries = house.entries
    size = words.size.replace("{bytes}", str(len(house.lookup))).replace(
        "{numbers}", str(house.base.components)
    )
    house = document.add_color_space(house)
    season = document.add_color_space(season)

    content = hqf_pdf.Content()
    heading(content, font, words)
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.35))
    content.draw_text(font, 10.0, LEFT, 726.0, size)
    content.restore_state()

    panel_heading(content, font, 700.0, words.house[0], words.house[1])
    chart(content, font, house, entries, 640.0)
    drawing(content, house, 612.0)

    panel_heading(content, font, 380.0, words.season[0], words.season[1])
    chart(content, font, season, entries, 320.0)
    drawing(content, season, 292.0)
    closing(content, font, words)

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    document.add_page(page)

    written = document.write(out)
    print(
        f"wrote {out}: {written} bytes, {len(PLACES)} places drawn twice "
        f"through two tables of {entries} rows"
    )


if __name__ == "__main__":
    main()
