#!/usr/bin/env python3
"""Aggiorna la galleria ORB usata dal riconoscitore (pwa/data/orb-gallery.{json,bin}).

Sorgente = le stesse immagini che mostra l'app: pwa/img/regions/*.jpg (id "1".."76",
"M4".."M38") e pwa/img/sanctuaries/S*.jpg (id "S0".."S52"). I descrittori sono
calcolati con GLI STESSI parametri della query nel worker: grigio, larghezza 400,
ORB a 800 keypoint, 32 byte per descrittore.

DI DEFAULT NON RICALCOLA CIO' CHE C'E' GIA'. Le 45 voci dei Santuari base furono
calcolate (16/06/2026) da PNG con alfa che poi sono stati convertiti in JPG: gli
stessi id ricalcolati oggi darebbero descrittori diversi, e quel riconoscimento e'
gia' validato su telefono. Si ricalcola solo cio' che si chiede con --rebuild.

  /usr/bin/python3 recognition/orb_gallery.py                    # aggiunge le carte mancanti
  /usr/bin/python3 recognition/orb_gallery.py --rebuild S45 S46  # e rifa' anche queste
  /usr/bin/python3 recognition/orb_gallery.py --check            # non scrive: dice cosa farebbe
"""
import os, re, json, argparse, cv2, numpy as np

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
IMG = os.path.join(ROOT, "pwa", "img")
JSON_P = os.path.join(ROOT, "pwa", "data", "orb-gallery.json")
BIN_P = os.path.join(ROOT, "pwa", "data", "orb-gallery.bin")


def sources():
    """id -> (percorso, tipo) per ogni carta che ha un'immagine."""
    out = {}
    for f in sorted(os.listdir(os.path.join(IMG, "regions"))):
        m = re.fullmatch(r"(M?\d+)\.jpg", f)
        if m:
            out[m.group(1)] = (os.path.join(IMG, "regions", f), "region")
    for f in sorted(os.listdir(os.path.join(IMG, "sanctuaries"))):
        m = re.fullmatch(r"(S\d+)\.jpg", f)
        if m:
            out[m.group(1)] = (os.path.join(IMG, "sanctuaries", f), "sanctuary")
    return out


def descriptors(path, W, nfeat):
    im = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
    if im is None:
        raise SystemExit("illeggibile: " + path)
    h, w = im.shape[:2]
    small = cv2.resize(im, (W, max(1, round(W * h / w))), interpolation=cv2.INTER_AREA)
    _, d = cv2.ORB_create(nfeat).detectAndCompute(small, None)
    return d if d is not None else np.zeros((0, 32), np.uint8)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--rebuild", nargs="*", default=[], help="id da ricalcolare anche se presenti")
    ap.add_argument("--all", action="store_true", help="ricalcola TUTTE le carte (dopo aver rifatto le immagini)")
    ap.add_argument("--check", action="store_true")
    a = ap.parse_args()

    meta = json.load(open(JSON_P))
    buf = np.fromfile(BIN_P, dtype=np.uint8)
    W, NF = meta["orbW"], meta["nfeat"]
    old = {c["id"]: (c, buf[c["off"]:c["off"] + c["n"] * 32].reshape(c["n"], 32)) for c in meta["cards"]}

    src = sources()
    rebuild = set(src) if a.all else set(a.rebuild)
    ignoti = rebuild - set(src)
    if ignoti:
        raise SystemExit("--rebuild con id inesistenti: " + ", ".join(sorted(ignoti)))
    nuovi = [i for i in src if i not in old]
    print("galleria: %d voci | immagini: %d | da aggiungere: %d | da ricalcolare: %d"
          % (len(old), len(src), len(nuovi), len(rebuild)))
    for i in sorted(nuovi):
        print("  + " + i)
    if a.check:
        return
    if not nuovi and not rebuild:
        print("niente da fare.")
        return

    # ordine stabile: prima le Regioni per numero (Meteore in coda), poi i Santuari
    def key(i):
        if i.startswith("S"):
            return (2, int(i[1:]), 0)
        if i.startswith("M"):
            return (1, int(i[1:]), 0)
        return (0, int(i), 0)

    cards, blobs, off = [], [], 0
    for i in sorted(src, key=key):
        path, tipo = src[i]
        if i in old and i not in rebuild:
            d = old[i][1]
        else:
            d = descriptors(path, W, NF)
            print("  calcolato %-5s %4d descrittori  (%s)" % (i, len(d), os.path.basename(path)))
        cards.append({"id": i, "type": tipo, "off": off, "n": int(len(d))})
        blobs.append(d.astype(np.uint8).reshape(-1))
        off += len(d) * 32

    np.concatenate(blobs).tofile(BIN_P)
    meta["cards"] = cards
    json.dump(meta, open(JSON_P, "w"), separators=(",", ":"))
    print("scritto: %d carte, %.1f MB" % (len(cards), off / 1e6))


if __name__ == "__main__":
    main()
