#!/usr/bin/env python3
"""Rifa' i numeri pubblicati del caso IDEA leggendo un solo file.

    python3 verifica-idea.py matrici-idea-15042026.json

Non serve altro: ne' OSRM, ne' il motore, ne' gli indirizzi. Il file contiene
le matrici delle durate e delle distanze di ciascuna giornata, con i punti
numerati. Questo programma ricalcola da zero il giro a naso, l'ottimo esatto
per durata, e confronta i totali con quelli pubblicati su nigin.it/caso-reale.

Se un totale non torna, esce con stato diverso da zero e dice quale.
"""
import json, sys

ATTESI = {
    "fermate": 49,
    "naso_km": 368.7367, "ottimo_km": 326.9033,
    "naso_min": 478.9467, "ottimo_min": 439.2433,
}
TOL = 1e-3

def chiuso(m, giro):
    return sum(m[giro[i]][giro[i+1]] for i in range(len(giro)-1)) + m[giro[-1]][giro[0]]

def a_naso(m, start=0):
    """Greedy dal deposito sulla matrice delle durate, tie-break sull'indice."""
    giro, restanti, corrente = [start], [i for i in range(len(m)) if i != start], start
    while restanti:
        p = min(restanti, key=lambda j: (m[corrente][j], j))
        giro.append(p); restanti.remove(p); corrente = p
    return giro

def held_karp(m):
    """Ottimo esatto del ciclo chiuso: esplora tutte le sequenze. Minimo dimostrato."""
    n = len(m); pieno = 1 << (n-1); inf = float("inf")
    dp = [[inf]*(n-1) for _ in range(pieno)]; padre = [[-1]*(n-1) for _ in range(pieno)]
    for j in range(n-1): dp[1<<j][j] = m[0][j+1]
    for s in range(pieno):
        for j in range(n-1):
            if not (s>>j)&1 or dp[s][j]==inf: continue
            for k in range(n-1):
                if (s>>k)&1: continue
                t = s|(1<<k); v = dp[s][j] + m[j+1][k+1]
                if v < dp[t][k]-1e-12: dp[t][k]=v; padre[t][k]=j
    best, bj = inf, -1
    for j in range(n-1):
        v = dp[pieno-1][j] + m[j+1][0]
        if v < best: best, bj = v, j
    giro, s, j = [], pieno-1, bj
    while j != -1:
        giro.append(j+1); p = padre[s][j]; s ^= 1<<j; j = p
    return best, [0] + giro[::-1]

def main():
    percorso = sys.argv[1] if len(sys.argv) > 1 else "matrici-idea-15042026.json"
    d = json.load(open(percorso, encoding="utf-8"))
    assert d.get("contratto") == "matrici_caso_idea_v1", "file non riconosciuto"
    tot = dict(naso_km=0.0, ottimo_km=0.0, naso_min=0.0, ottimo_min=0.0); fermate = 0
    print(f"{'giorno':<11}{'n':>3}{'naso km':>11}{'ottimo km':>11}{'naso min':>11}{'ottimo min':>12}")
    for giorno, g in d["giornate"].items():
        dur, dist = g["durate"], g["distanze"]
        fermate += g["punti"] - 1
        naso = a_naso(dur)
        _, ottima = held_karp(dur)
        v = dict(naso_km=chiuso(dist,naso)/1000, ottimo_km=chiuso(dist,ottima)/1000,
                 naso_min=chiuso(dur,naso)/60,  ottimo_min=chiuso(dur,ottima)/60)
        for k in tot: tot[k] += v[k]
        print(f"{giorno:<11}{g['punti']-1:>3}{v['naso_km']:>11.4f}{v['ottimo_km']:>11.4f}"
              f"{v['naso_min']:>11.4f}{v['ottimo_min']:>12.4f}")
    print(f"\n{'TOTALE':<11}{fermate:>3}{tot['naso_km']:>11.4f}{tot['ottimo_km']:>11.4f}"
          f"{tot['naso_min']:>11.4f}{tot['ottimo_min']:>12.4f}")
    print(f"\ndifferenza distanza: {tot['naso_km']-tot['ottimo_km']:.4f} km "
          f"({(tot['naso_km']-tot['ottimo_km'])/tot['naso_km']*100:.2f}%)")
    print(f"differenza durata:   {tot['naso_min']-tot['ottimo_min']:.4f} min "
          f"({(tot['naso_min']-tot['ottimo_min'])/tot['naso_min']*100:.2f}%)")
    print("\nconfronto con i valori pubblicati su nigin.it/caso-reale.html")
    male = []
    for k, atteso in ATTESI.items():
        reale = fermate if k == "fermate" else tot[k]
        ok = abs(reale-atteso) <= (0 if k=="fermate" else TOL)
        print(f"  [{'OK ' if ok else 'FAIL'}] {k:<12} {reale:>12.4f}  atteso {atteso}")
        if not ok: male.append(k)
    print("\nESITO:", "PASS — il file riproduce i numeri pubblicati" if not male
          else "FAIL — non tornano: " + ", ".join(male))
    sys.exit(1 if male else 0)

if __name__ == "__main__":
    main()
