#!/usr/bin/env python3
"""Costruisce le carte Santuario finite applicando CORREZIONI MANUALI per-carta.

Sorgente: ritagli auto in CROP_DIR (da autocrop.py). Per ogni carta applica una
rotazione manuale di rifinitura (da data/card_overrides.json), poi normalizza
luce/colore e applica la maschera angoli -> recognition/cards53/NN.png.

Le correzioni sono PERSISTENTI (sopravvivono ai riavvii). Uso tipico:
  python recognition/build_cards.py --review 1 2 3   # provino con guide per decidere la rotazione
  (modifico data/card_overrides.json: {"1":{"rot":-1.5}, ...})
  python recognition/build_cards.py --build 1 2 3    # rigenera quelle carte
  python recognition/build_cards.py --all            # rigenera tutte
"""
import os, json, argparse, cv2, numpy as np
import card_finish as CF

HERE = os.path.dirname(__file__)
CROP_DIR = "/tmp/s53_crop3"
OUT_DIR = os.path.join(HERE, "cards53")
REVIEW_DIR = "/tmp/review"
OVR_PATH = os.path.join(HERE, "..", "data", "card_overrides.json")
REDO = set()            # da rifotografare (aggiornare quando rifatte)
DEFAULT_ROT = 1.5             # bias sistematico del warp (~+1.5 down-right su tutte); override per-carta lo sostituisce


def load_ovr():
    try:
        return json.load(open(OVR_PATH))
    except Exception:
        return {}


def _rot(im, deg):
    if abs(deg) < 0.01:
        return im
    h, w = im.shape[:2]
    M = cv2.getRotationMatrix2D((w / 2, h / 2), deg, 1.0)
    return cv2.warpAffine(im, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)


def _finish(bgr, out_path):
    im = CF.normalize_light(bgr)
    h, w = im.shape[:2]
    tx, ty = int(w * CF.TRIM_FRAC), int(h * CF.TRIM_FRAC)
    im = im[ty:h - ty, tx:w - tx]
    h, w = im.shape[:2]
    s = CF.OUT_H / h
    im = cv2.resize(im, (int(round(w * s)), CF.OUT_H), interpolation=cv2.INTER_AREA)
    rgba = cv2.cvtColor(im, cv2.COLOR_BGR2BGRA)
    rgba[:, :, 3] = CF._mask(*im.shape[:2])
    cv2.imwrite(out_path, rgba)


def _crop(n):
    p = os.path.join(CROP_DIR, f"{n:02d}.jpg")
    return cv2.imread(p) if os.path.exists(p) else None


def build(n):
    if n in REDO:
        return False
    bgr = _crop(n)
    if bgr is None:
        return False
    ovr = load_ovr().get(str(n), {})
    bgr = _rot(bgr, float(ovr.get("rot", DEFAULT_ROT)))
    os.makedirs(OUT_DIR, exist_ok=True)
    _finish(bgr, os.path.join(OUT_DIR, f"{n:02d}.png"))
    return True


def _tilt(im):
    """Tilt residuo della banda (gradi): angolo che orizzontalizza i bordi orizzontali
    nella fascia banda/pannello (y 42-82%). Misura oggettiva per livellare."""
    g = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY).astype(np.float32); H, W = g.shape
    reg = g[int(H * 0.42):int(H * 0.82), int(W * 0.10):int(W * 0.90)]
    E = np.abs(cv2.Sobel(reg, cv2.CV_32F, 0, 1, 3)); cy, cx = reg.shape[0] / 2, reg.shape[1] / 2
    best, bt, th = -1, 0, -4.0
    while th <= 4.0:
        Er = cv2.warpAffine(E, cv2.getRotationMatrix2D((cx, cy), th, 1.0), (reg.shape[1], reg.shape[0]))
        r = Er.sum(1); sc = float(np.sum((r / (r.mean() + 1e-6)) ** 2))
        if sc > best: best, bt = sc, th
        th += 0.2
    return bt


def autolevel():
    """Calcola per ogni carta la rotazione che porta il tilt della banda a ~0
    (feedback: misura -> correggi -> rimisura) e la salva in card_overrides.json."""
    ovr = load_ovr()
    for n in range(1, 54):
        bgr = _crop(n)
        if bgr is None:
            continue
        r = DEFAULT_ROT
        for _ in range(5):
            im = CF.normalize_light(_rot(bgr, r))
            t = _tilt(im)
            if abs(t) < 0.4:
                break
            r += t
        ovr[str(n)] = {"rot": round(r, 2)}
    json.dump(ovr, open(OVR_PATH, "w"), ensure_ascii=False, indent=1)
    print("auto-livello salvato per", len(ovr), "carte")


def review(nums):
    """Provino normalizzato+ruotato CON guide orizzontali, per decidere la rotazione."""
    os.makedirs(REVIEW_DIR, exist_ok=True)
    ovr = load_ovr()
    tiles = []
    for n in nums:
        bgr = _crop(n)
        if bgr is None:
            continue
        r = float(ovr.get(str(n), {}).get("rot", DEFAULT_ROT))
        im = CF.normalize_light(_rot(bgr, r))
        im = cv2.resize(im, (300, 460))
        for y in range(0, 460, 20):
            cv2.line(im, (0, y), (300, y), (0, 0, 255), 1)
        im = cv2.copyMakeBorder(im, 26, 4, 4, 4, cv2.BORDER_CONSTANT, value=(255, 255, 255))
        cv2.putText(im, f"#{n}  rot={r:+.2f}", (8, 18), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 0, 0), 2)
        tiles.append(im)
    if tiles:
        out = os.path.join(REVIEW_DIR, "review_" + "_".join(map(str, nums)) + ".png")
        cv2.imwrite(out, np.hstack(tiles))
        print(out)


if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("--review", nargs="*", type=int)
    ap.add_argument("--build", nargs="*", type=int)
    ap.add_argument("--all", action="store_true")
    ap.add_argument("--autolevel", action="store_true")
    a = ap.parse_args()
    if a.autolevel:
        autolevel()
    elif a.review:
        review(a.review)
    elif a.build:
        print("costruite:", [n for n in a.build if build(n)])
    elif a.all:
        ok = [n for n in range(1, 54) if build(n)]
        print(f"costruite {len(ok)}/53 (escluse da rifare: {sorted(REDO)})")
