#!/usr/bin/env python3
"""Lettore STRUTTURATO dei Santuari FARAWAY (prototipo).

Identita di un Santuario = BIOMA (colore banda) + insieme di SIMBOLI/punteggio.
- Bioma: tinta/saturazione mediana delle zone uniformi (S bassa => Rifugi).
- Simboli: template matching (multi-rotazione + multi-scala) contro la libreria
  in recognition/templates/ (okiko, clue, goldlog, night).  I simboli sono
  grafiche stampate identiche => robusto, niente soglie HSV fragili.

E la stessa logica dei NUMERI sulle Regioni, applicata ai Santuari; DINOv2
(carta intera) fallisce sui santuari stesso-bioma/stessa-arte (vedi test_realphotos.py),
mentre la firma bioma+simboli li separa.
"""
import os, glob, cv2, numpy as np

TPL_DIR = os.path.join(os.path.dirname(__file__), "templates")
NORM = 900            # lato lungo normalizzato
ROTS = [0, 1, 2, 3]   # k*90 gradi
SCALES = [0.65, 0.78, 0.9, 1.0, 1.1, 1.22, 1.35]


def normalize(path):
    im = cv2.imread(path)
    h, w = im.shape[:2]
    s = NORM / max(h, w)
    return cv2.resize(im, None, fx=s, fy=s, interpolation=cv2.INTER_AREA)


def biome(im):
    hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
    H, S, V = hsv[..., 0], hsv[..., 1], hsv[..., 2]
    Vf = V.astype(np.float32)
    grad = cv2.GaussianBlur(cv2.magnitude(cv2.Sobel(Vf, cv2.CV_32F, 1, 0, 3),
                                          cv2.Sobel(Vf, cv2.CV_32F, 0, 1, 3)), (0, 0), 3)
    sm = (grad < np.percentile(grad, 40)) & (V > 40) & (V < 240)
    s = int(np.median(S[sm])); h = int(np.median(H[sm]))
    if s < 60: return "Rifugi"
    if h < 13 or h >= 168: return "Foresta"
    if h < 38: return "Citta"
    if h < 90: return "Fiume"
    return "Deserto"


def _best_match(card_gray, tpl_gray):
    best = -1.0
    for k in ROTS:
        t = np.rot90(tpl_gray, k)
        for sc in SCALES:
            ts = cv2.resize(t, None, fx=sc, fy=sc, interpolation=cv2.INTER_AREA)
            if ts.shape[0] >= card_gray.shape[0] or ts.shape[1] >= card_gray.shape[1]:
                continue
            res = cv2.matchTemplate(card_gray, ts, cv2.TM_CCOEFF_NORMED)
            best = max(best, float(res.max()))
    return best


# 'scorebox' (quadrato bianco del valore + petali) NON e un simbolo: marca il
# TIPO di carta. Le carte-punteggio lo hanno (match alto), le 'fornisce' no.
TYPE_MARKER = "scorebox"
SCOREBOX_THR = 0.50


def _base(name):
    return name.rstrip("0123456789")     # 'clue2' -> 'clue' (esemplari multipli per simbolo)


def load_templates():
    syms, markers = {}, {}
    for p in sorted(glob.glob(os.path.join(TPL_DIR, "*.png"))):
        name = os.path.splitext(os.path.basename(p))[0]
        g = cv2.cvtColor(cv2.imread(p), cv2.COLOR_BGR2GRAY)
        if _base(name) == TYPE_MARKER:
            markers.setdefault(TYPE_MARKER, []).append(g)
        else:
            syms.setdefault(_base(name), []).append(g)
    return syms, markers


# soglie per-simbolo (il template 'night' = anello bianco fa match deboli ~0.6 su
# dischi chiari => soglia piu alta; gli altri sono ben separati). Tarate sul 1° lotto.
THR = {"okiko": 0.60, "clue": 0.60, "goldlog": 0.55, "night": 0.75}
DEFAULT_THR = 0.62


def _best_match_multi(g, tpl_list):
    return max(_best_match(g, t) for t in tpl_list)


def read(path, tpls, markers=None, thr=None):
    im = normalize(path)
    g = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
    scores = {name: _best_match_multi(g, ts) for name, ts in tpls.items()}
    def lim(n):
        if isinstance(thr, dict): return thr.get(n, DEFAULT_THR)
        if isinstance(thr, (int, float)): return thr
        return THR.get(n, DEFAULT_THR)
    syms = sorted([n for n, s in scores.items() if s >= lim(n)])
    ctype = "fornisce"
    if markers:
        sb = _best_match_multi(g, markers[TYPE_MARKER])
        scores[TYPE_MARKER] = sb
        if sb >= SCOREBOX_THR:
            ctype = "punteggio"      # carta a punteggio: i simboli sono la CONDIZIONE "per X"
    return biome(im), ctype, syms, scores


def describe(biome_, ctype, syms):
    if ctype == "punteggio":
        cond = " / ".join(syms) if syms else "carte bioma"   # es. 6583 = per carte blu/verdi
        return f"{biome_} · punteggio: N per {cond}"
    return f"{biome_} · fornisce: {', '.join(syms) if syms else '—'}"


if __name__ == "__main__":
    import argparse
    ap = argparse.ArgumentParser()
    ap.add_argument("--dir", default="/tmp/fw_crops")
    ap.add_argument("--matrix", action="store_true", help="stampa matrice punteggi")
    a = ap.parse_args()
    tpls, markers = load_templates()
    # atteso: (bioma, tipo, simboli)
    EXP = {"6580": ("Deserto", "fornisce", ["okiko"]), "6581": ("Foresta", "fornisce", ["night"]),
           "6582": ("Fiume", "fornisce", ["okiko"]), "6583": ("Rifugi", "punteggio", []),
           "6584": ("Fiume", "punteggio", ["night"]), "6585": ("Rifugi", "punteggio", ["clue"]),
           "6586": ("Rifugi", "fornisce", ["okiko", "clue"]), "6588": ("Rifugi", "fornisce", ["night", "goldlog"])}
    names = sorted(tpls) + [TYPE_MARKER]
    print("Template:", sorted(tpls), "+ marker:", TYPE_MARKER)
    if a.matrix:
        print("\nMatrice punteggi (max su rot x scala):")
        print("        " + "  ".join(f"{n[:7]:>8}" for n in names))
    ok = 0; tot = 0
    for p in sorted(glob.glob(os.path.join(a.dir, "IMG_65*.jpg"))):
        tag = os.path.basename(p)[4:8]
        if tag not in EXP: continue
        bi, ctype, syms, sc = read(p, tpls, markers)
        if a.matrix:
            print(f"  {tag}  " + "  ".join(f"{sc[n]:>8.2f}" for n in names))
        exp_bi, exp_ty, exp_sy = EXP[tag]
        good = (bi == exp_bi) and (ctype == exp_ty) and (syms == sorted(exp_sy))
        ok += good; tot += 1
        print(f"  {tag}: {describe(bi, ctype, syms)}   {'OK' if good else 'X (atteso '+describe(exp_bi,exp_ty,sorted(exp_sy))+')'}")
    print(f"\nIdentificazione: {ok}/{tot}")
