# -*- coding: utf-8 -*-
"""Puzzle artwork redrawn for the Ukrainian edition.

The English booklet hides answers inside language-specific artwork (dotted
letters, acrostic grids, crosswords, first-letter-of-object puzzles).  Those
cannot survive translation, so the affected areas are wiped and rebuilt around
Ukrainian answers.
"""
import random

import fitz

ARIAL_BLACK = '/System/Library/Fonts/Supplemental/Arial Black.ttf'
ARIAL_BOLD = '/System/Library/Fonts/Supplemental/Arial Bold.ttf'
ARIAL = '/System/Library/Fonts/Supplemental/Arial.ttf'
RED = (0.918, 0.318, 0.353)
BLACK = (0, 0, 0)

FBLK = fitz.Font(fontfile=ARIAL_BLACK)
FB = fitz.Font(fontfile=ARIAL_BOLD)
FR = fitz.Font(fontfile=ARIAL)
ALPHABET = 'АБВГДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЮЯ'


# ------------------------------------------------------------------ helpers
COVERS = {
    2: [(105, 370, 470, 492)],
    3: [(130, 412, 440, 492), (341, 270, 490, 354)],
    4: [(345, 458, 502, 772)],
    6: [(55, 462, 508, 572)],
    7: [(84, 191, 300, 262), (122, 262, 300, 340)],
    8: [(270, 312, 498, 498)],
    9: [(80, 154, 395, 188)],
    11: [(304, 466, 510, 610)],
    15: [(105, 374, 460, 454)],
}


WIPES = []   # (page number, rect) - regions intentionally cleared


def apply_covers(page):
    """Clear the areas of this page that are rebuilt from scratch.

    Runs BEFORE any translated text is drawn, so the white cover can extend
    over places where new text will later sit.

    Text is removed by redaction; line art and images are merely covered.
    PyMuPDF's LINE_ART_REMOVE_IF_TOUCHED strips the stroke from other paths on
    the same page - it once erased the outline of a speech balloon 4 pt away
    from the redaction rect - so it must not be used.  Every cleared area sits
    on white background, which makes covering exact.
    """
    rects = COVERS.get(page.number, [])
    if not rects:
        return
    for r in rects:
        WIPES.append([page.number, [round(v, 1) for v in fitz.Rect(r)]])
        page.add_redact_annot(fitz.Rect(r))
    page.apply_redactions(images=fitz.PDF_REDACT_IMAGE_NONE,
                          graphics=fitz.PDF_REDACT_LINE_ART_NONE,
                          text=fitz.PDF_REDACT_TEXT_REMOVE)
    for r in rects:
        page.draw_rect(fitz.Rect(r), color=None, fill=(1, 1, 1))


def text(page, x, y, s, size, font=FB, file=ARIAL_BOLD, color=BLACK, render=0, border=0.9):
    kw = {}
    if render:
        kw = {'render_mode': render, 'border_width': border / size}
    page.insert_text((x, y), s, fontname='pz%d' % (abs(hash(file)) % 997),
                     fontfile=file, fontsize=size, color=color, **kw)


def centre(page, rect, s, size, font=FB, file=ARIAL_BOLD, color=BLACK):
    r = fitz.Rect(rect)
    w = font.text_length(s, fontsize=size)
    text(page, r.x0 + (r.width - w) / 2, r.y1 - (r.height - size * 0.72) / 2, s,
         size, font, file, color)


def box(page, rect, color=RED, width=0.9):
    page.draw_rect(fitz.Rect(rect), color=color, width=width)


def boxrow(page, x0, y0, n, cell):
    return [fitz.Rect(x0 + i * cell, y0, x0 + i * cell + cell, y0 + cell)
            for i in range(n)]


def draw_cell_grid(page, origin, cell, cells, prefilled=None, numbers=None,
                   fsize=12.0, nsize=8.0):
    """cells: iterable of (row, col).  prefilled/numbers: {(row, col): str}."""
    x0, y0 = origin
    prefilled = prefilled or {}
    numbers = numbers or {}
    for r, c in cells:
        rect = fitz.Rect(x0 + c * cell, y0 + r * cell, x0 + (c + 1) * cell, y0 + (r + 1) * cell)
        box(page, rect)
        if (r, c) in prefilled:
            centre(page, rect, prefilled[(r, c)], fsize)
        if (r, c) in numbers:
            text(page, rect.x0 + 1.4, rect.y0 + nsize, numbers[(r, c)], nsize,
                 FR, ARIAL, RED)


def make_wordsearch(rows, cols, words, dirs, seed):
    rnd = random.Random(seed)
    grid = [[None] * cols for _ in range(rows)]
    for w in sorted(words, key=len, reverse=True):
        for _ in range(20000):
            dr, dc = rnd.choice(dirs)
            r, c = rnd.randrange(rows), rnd.randrange(cols)
            er, ec = r + dr * (len(w) - 1), c + dc * (len(w) - 1)
            if not (0 <= er < rows and 0 <= ec < cols):
                continue
            if any(grid[r + dr * i][c + dc * i] not in (None, ch)
                   for i, ch in enumerate(w)):
                continue
            for i, ch in enumerate(w):
                grid[r + dr * i][c + dc * i] = ch
            break
        else:
            raise RuntimeError('could not place ' + w)
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] is None:
                grid[r][c] = rnd.choice(ALPHABET)
    return grid


# --------------------------------------------------- word-search letter maps
ACROSS_DOWN = [(0, 1), (1, 0)]
ALL_DIRS = [(0, 1), (1, 0), (0, -1), (-1, 0), (1, 1), (-1, -1), (1, -1), (-1, 1)]

GRID_P3 = make_wordsearch(6, 8, ['МАТВІЙ', 'МАРКО', 'ЛУКА', 'ІВАН'], ALL_DIRS, 7)
GRID_P9 = make_wordsearch(8, 9, ['ІСУС', 'БУРЯ', 'УЧНІ', 'ВІРА', 'ЧОВЕН', 'СПОКІЙ',
                                 'СТРАХ', 'ПОДИВ'], ACROSS_DOWN, 11)
# 4x7 square: КАЄМОСЯ / ПРОЩАЄ / ГРІХИ / ВИБАЧ down, БОГ across on the last row.
GRID_P13 = [list(r) for r in ('КПГВ', 'АРРИ', 'ЄОІБ', 'МЩХА', 'ОАИЧ', 'СЄЖЮ', 'ЯБОГ')]
GRID_P19 = make_wordsearch(8, 8, ['НЕДІЛЯ', 'РАНОК', 'КАМІНЬ', 'ДВА', 'ВОСКРЕС'],
                           ACROSS_DOWN, 3)


def _rows_tr(grid, page, first_block):
    """Grids stored as one block per row (p9, p13, p19)."""
    return {f'p{page}b{first_block + i}': {'lines': [[(ch, 0)] for ch in row],
                                           'center': True}
            for i, row in enumerate(grid)}


def _cols_tr(grid, page, first_block, rows):
    """Grid stored as one block per cell, column-major (p3)."""
    out = {}
    for c in range(len(grid[0])):
        for r in range(rows):
            out[f'p{page}b{first_block + c * rows + r}'] = {
                'lines': [[(grid[r][c], 0)]], 'center': True}
    return out


def grid_translations():
    tr = {}
    tr.update(_cols_tr(GRID_P3, 3, 28, 6))
    tr.update(_rows_tr(GRID_P9, 9, 30))
    tr.update(_rows_tr(GRID_P13, 13, 23))
    tr.update(_rows_tr(GRID_P19, 19, 20))
    return tr


# ------------------------------------------------------------------- page 2
def p2_dotted_letters(doc):
    page = doc[2]
    for s, dots, y in [('БЖІЦБШ', {0, 2, 4}, 413.5), ('ФЛДІЧЯГЮЩЕ', {1, 3, 5}, 467.1)]:
        size = 48.0
        while FBLK.text_length(s, fontsize=size) > 344:
            size -= 1
        x = 111.4 + (344 - FBLK.text_length(s, fontsize=size)) / 2
        for i, ch in enumerate(s):
            cw = FBLK.text_length(ch, fontsize=size)
            text(page, x, y, ch, size, FBLK, ARIAL_BLACK, BLACK, render=1, border=1.2)
            if i in dots:
                page.draw_circle((x + cw / 2, y + 8.0), 2.6, color=BLACK, fill=BLACK)
            x += cw


# ------------------------------------------------------------------- page 3
def p3_gospels_boxes(doc):
    page = doc[3]
    for r in boxrow(page, 283 - 4.5 * 24, 434, 9, 24):
        box(page, r)


def p3_anagram_frames(doc):
    """Ukrainian clue lines are longer, so the three answer frames move right."""
    page = doc[3]
    for i in range(3):
        y0 = 276.1 + i * 25.1
        box(page, (368.0, y0, 480.4, y0 + 21.7))


# ------------------------------------------------------------------- page 4
P4_KEY = 'РІЗДВО'
P4_WORDS = [('МАРІЯ', 3), ('ГАЛІЛЕЯ', 4), ('НАЗАРЕТ', 3), ('ЗАЇЗД', 5),
            ('ВІФЛЕЄМ', 1), ('ЙОСИП', 2)]


def p4_christmas_grid(doc):
    page = doc[4]
    cell = 24.5
    keyrow = max(pos for _, pos in P4_WORDS) - 1
    x0, y0 = 347.7, 570.6 - keyrow * cell
    cells, pre, nums = [], {}, {}
    for c, (word, pos) in enumerate(P4_WORDS):
        top = keyrow - (pos - 1)
        cells += [(top + i, c) for i in range(len(word))]
        pre[(keyrow, c)] = P4_KEY[c]
        nums[(top, c)] = str(c + 1)
    draw_cell_grid(page, (x0, y0), cell, cells, pre, nums, fsize=13.5, nsize=6.5)


# ------------------------------------------------------------------- page 6
P6_VERSE = [
    (57.0, 457.2, [('"Кожен, хто приходить до Мене, слухає слова Мої та виконує їх, —', None)]),
    (57.0, 482.2, [('Я покажу вам, до кого він подібний. Він — як чоловік, що будував', None),
                   ('д __ __', 1), (',', None)]),
    (57.0, 507.3, [('який копав', None), ('г __ __ __ __ __ __', 1),
                   ('і поклав основу на', None), ('с __ __ __ __', 1), ('. Коли', None)]),
    (57.0, 532.4, [('налетіла', None), ('б __ __ __', 1),
                   (', вода вдарила в дім, та не змогла', None),
                   ('з __ __ __ __ __ __ __', 1)]),
    (57.0, 557.5, [('його, бо він був добре', None),
                   ('з __ __ __ __ __ __ __ __ __', 1), ('".', None)]),
]


def p6_key_verse(doc):
    page = doc[6]
    for x0, y, parts in P6_VERSE:
        x = x0
        for s, blank in parts:
            font, ff = (FB, ARIAL_BOLD) if blank else (FR, ARIAL)
            w = font.text_length(s, fontsize=12.0)
            if blank:
                box(page, (x - 4, y - 14.5, x + w + 4, y + 4.5))
            text(page, x, y, s, 12.0, font, ff)
            x += w + (7 if blank else 5)


# ------------------------------------------------------------------- page 7
def p7_crossword(doc):
    page = doc[7]
    words = {'ІСУС': [(r, 1) for r in range(4)],
             'ПІСОК': [(0, c) for c in range(5)],
             'МУДРИЙ': [(2, c) for c in range(6)],
             'ЗЛИВА': [(5, c) for c in range(5)]}
    cells = sorted({rc for v in words.values() for rc in v})
    pre = {(0, 1): 'І', (0, 0): 'П', (2, 0): 'М', (2, 1): 'У', (5, 0): 'З'}
    nums = {(0, 1): '1', (0, 0): '2', (2, 0): '3', (5, 0): '4'}
    draw_cell_grid(page, (100, 192), 24.0, cells, pre, nums, fsize=12.5, nsize=6.5)


# ------------------------------------------------------------------- page 8
def p8_crossword(doc):
    page = doc[8]
    cells = [(0, 5), (1, 5), (2, 5), (3, 5),          # ХВИЛІ  col 5, rows 0-4
             (4, 2), (4, 3), (4, 4), (4, 5),          # УЧНІ   row 4
             (3, 2), (5, 2), (6, 2),                  # БУРЯ   col 2, rows 3-6
             (5, 0), (5, 1), (5, 3), (5, 4)]          # СТРАХ  row 5
    pre = {(4, 2): 'У', (3, 2): 'Б', (0, 5): 'Х', (4, 5): 'І', (5, 0): 'С', (5, 2): 'Р'}
    nums = {(4, 2): '1', (3, 2): '2', (0, 5): '3', (5, 0): '4'}
    draw_cell_grid(page, (300, 332), 22.0, cells, pre, nums, fsize=12.0, nsize=6.5)


# ------------------------------------------------------------------- page 9
P9_LETTERS = 'кнмВтІлждРсчАя'
P9_DOTS = {3, 5, 9, 12}


def p9_dotted_squares(doc):
    page = doc[9]
    for i, ch in enumerate(P9_LETTERS):
        r = fitz.Rect(85.5 + i * 21.7, 159.1, 85.5 + (i + 1) * 21.7, 180.7)
        box(page, r)
        centre(page, r, ch, 12.0)
        if i in P9_DOTS:
            page.draw_circle((r.x0 + 5.6, r.y0 + 5.4), 2.2, color=RED, fill=RED)


# ------------------------------------------------------------------ page 11
P11_ANSWERS = ['САМАРЯНИН', 'ОСЕЛ', 'ДВІ', 'ЗАЇЗД', 'ОЛІЯ', 'ЛЕВИТ']


def p11_word_grid(doc):
    page = doc[11]
    cell = 21.7
    x0, y0 = 310.0, 472.0
    for i, word in enumerate(P11_ANSWERS):
        for j in range(len(word)):
            r = fitz.Rect(x0 + j * cell, y0 + i * cell,
                          x0 + (j + 1) * cell, y0 + (i + 1) * cell)
            box(page, r)
            if j == 0:
                centre(page, r, word[0], 12.0)
                text(page, r.x0 + 1.3, r.y0 + 8.0, str(i + 1), 7.5, FR, ARIAL, RED)


# ------------------------------------------------------------------ page 15
def p15_thankyou_boxes(doc):
    page = doc[15]
    for r in boxrow(page, 283 - 2.5 * 26, 402, 5, 26):
        box(page, r)


_HOOKS = [(2, p2_dotted_letters), (3, p3_gospels_boxes), (3, p3_anagram_frames), (4, p4_christmas_grid),
          (6, p6_key_verse), (7, p7_crossword), (8, p8_crossword),
          (9, p9_dotted_squares), (11, p11_word_grid), (15, p15_thankyou_boxes)]


def _guard(page_no, fn):
    def wrapped(doc, only):
        if only is None or page_no in only:
            fn(doc)
    wrapped.__name__ = fn.__name__
    return wrapped


POST = [_guard(p, f) for p, f in _HOOKS]
