import gc
import os
import time
import neopixel
from machine import Pin, PWM

try:
    import requests
except ImportError:
    import urequests as requests

from wifi import connect_wifi

ANNEE = 2022

# --- REGLAGES DE VITESSE ET DE TEMPS ---
DUREE_PAUSE = 4         # Temps d'arrêt (en secondes) sur chaque mois
DUREE_MOUVEMENT = 0.8   # Temps (en secondes) pour tourner doucement d'un mois à l'autre
ANGLE_TOTAL = 180       # Angle maximum de votre nouveau servomoteur
TRICHE_FRAICHEUR = 7.0  # (AUGMENTE) 7 degrés d'écart forcé pour un effet visuel massif
# ---------------------------------------

PIN_SERVO = 18
PIN_VILLE = 13
PIN_BOIS = 14
PIN_DIFF = 26
NB_LED = 8  

CSV_FICHIER = "arbre.csv"
COL_DATE_BOIS = "date"
COL_TEMP_BOIS = "Tair_avg"

BASE_URL = (
    "https://opendata.bordeaux-metropole.fr/api/explore/v2.1/catalog/datasets/"
    "observation-meteorologique-historiques-bordeaux-metropole-synop/records"
)
CHAMP_DATE = "date"
CHAMP_TEMP = "tc"
CACHE_VILLE = "ville_2022.csv"

# --- ECHELLE REDUITE POUR PLUS DE CONTRASTE ---
T_MIN = 5   # (Modifié) Plus bas = 5°C
T_MAX = 25  # (Modifié) Plus haut = 25°C
DIFF_MAX = 7 # Modifié pour correspondre à la triche
LUM = 80

servo = PWM(Pin(PIN_SERVO), freq=50)
bande_ville = neopixel.NeoPixel(Pin(PIN_VILLE), NB_LED)
bande_bois = neopixel.NeoPixel(Pin(PIN_BOIS), NB_LED)
bande_diff = neopixel.NeoPixel(Pin(PIN_DIFF), NB_LED)

position = [0.0]

def angle_servo(a):
    servo.duty_u16(int(1638 + (a / ANGLE_TOTAL) * (8192 - 1638)))

def balayer(cible, duree):
    depart = position[0]
    if depart == cible:
        angle_servo(cible)
        return
        
    pas_ms = 30
    n = max(1, int(duree * 1000 / pas_ms))
    for i in range(1, n + 1):
        angle_servo(depart + (cible - depart) * i / n)
        time.sleep_ms(pas_ms)
    position[0] = cible

def eteindre(bande):
    for i in range(NB_LED):
        bande[i] = (0, 0, 0)
    bande.write()

def en_celsius(v):
    return v - 273.15 if v > 100 else v

def url_mois(m):
    debut = "%d-%02d-01" % (ANNEE, m)
    fin = "%d-01-01" % (ANNEE + 1) if m == 12 else "%d-%02d-01" % (ANNEE, m + 1)
    return (
        BASE_URL
        + "?select=avg(" + CHAMP_TEMP + ")%20as%20moy"
        + "&where=" + CHAMP_DATE + "%3E%3Ddate%27" + debut + "%27%20and%20"
        + CHAMP_DATE + "%3Cdate%27" + fin + "%27"
    )

def api_mois(m):
    gc.collect()
    url = url_mois(m)
    r = requests.get(url)
    try:
        if r.status_code != 200:
            print("URL:", url)
            print("Reponse API:", r.text)
            raise RuntimeError("API : statut HTTP " + str(r.status_code))
        data = r.json()
    finally:
        r.close()
    if "results" not in data or not data["results"]:
        raise RuntimeError("API : reponse inattendue")
    v = data["results"][0]["moy"]
    return None if v is None else en_celsius(v)

def charger_ville():
    temp = [None] * 13
    if CACHE_VILLE in os.listdir():
        with open(CACHE_VILLE) as f:
            for ligne in f:
                p = ligne.strip().split(",")
                if len(p) == 2 and p[1] != "":
                    temp[int(p[0])] = float(p[1])
        return temp

    connect_wifi()
    for m in range(1, 13):
        temp[m] = api_mois(m)
    with open(CACHE_VILLE, "w") as f:
        for m in range(1, 13):
            f.write("%d,%s\n" % (m, "" if temp[m] is None else temp[m]))
    return temp

def _decouper(ligne):
    champs = []
    courant = ""
    guillemets = False
    for c in ligne.rstrip("\r\n"):
        if c == '"':
            guillemets = not guillemets
        elif c == "," and not guillemets:
            champs.append(courant)
            courant = ""
        else:
            courant += c
    champs.append(courant)
    return champs

def _convertir(valeur):
    valeur = valeur.strip()
    if valeur in ("", "NA", "#NAME?"):
        return None
    try:
        nombre = float(valeur.replace(",", "."))
    except ValueError:
        return valeur
    if "," not in valeur and "." not in valeur:
        return int(nombre)
    return nombre

def lire_csv(fichier=CSV_FICHIER):
    with open(fichier) as f:
        colonnes = _decouper(f.readline().replace("\ufeff", ""))
        for ligne in f:
            if not ligne.strip():
                continue
            valeurs = [_convertir(v) for v in _decouper(ligne)]
            yield dict(zip(colonnes, valeurs))

def parse_mois(s):
    if not isinstance(s, str):
        return None
    s = s.strip()
    try:
        if "/" in s:
            p = s.split("/")
            a, m = int(p[2][:4]), int(p[1])
        else:
            a, m = int(s[:4]), int(s[5:7])
    except (ValueError, IndexError):
        return None
    return m if a == ANNEE and 1 <= m <= 12 else None

def charger_bois():
    somme = [0.0] * 13
    n = [0] * 13
    verifie = False
    for ligne in lire_csv():
        if not verifie:
            verifie = True
        m = parse_mois(ligne[COL_DATE_BOIS])
        t = ligne[COL_TEMP_BOIS]
        if m is None or not isinstance(t, (int, float)):
            continue
        somme[m] += t
        n[m] += 1
    return [None] + [(somme[m] / n[m] if n[m] else None) for m in range(1, 13)]

def couleur_temp(t):
    # NOUVEAU CALCUL DE COULEUR ULTRA-CONTRASTÉ
    x = max(0.0, min(1.0, (t - T_MIN) / (T_MAX - T_MIN)))
    rouge = int(LUM * x)
    bleu = int(LUM * (1 - x))
    vert = int((LUM / 2) * (1 - x)) # Ajoute du vert au bleu pour faire un Cyan très visible
    return (rouge, vert, bleu)

def afficher_temp(bande, t):
    if t is None:
        eteindre(bande)
        return
    c = couleur_temp(t)
    for i in range(NB_LED):
        bande[i] = c
    bande.write()

def afficher_jauge(diff):
    if diff is None:
        eteindre(bande_diff)
        return
    niveau = round(min(abs(diff), DIFF_MAX) / DIFF_MAX * NB_LED)
    niveau = max(1, niveau)
    # Bleu Cyan vif si plus frais, Rouge vif si plus chaud
    c = (0, int(LUM/2), LUM) if diff < 0 else (LUM, 0, 0)
    for i in range(NB_LED):
        bande_diff[i] = c if i < niveau else (0, 0, 0)
    bande_diff.write()

NOMS = ["", "Jan", "Fev", "Mar", "Avr", "Mai", "Juin",
        "Juil", "Aout", "Sep", "Oct", "Nov", "Dec"]

def main():
    ville = charger_ville()
    bois = charger_bois()
    
    secteur = ANGLE_TOTAL / 11 
    
    balayer(0, 2)
    time.sleep(1)
    
    while True:
        for m in range(1, 13):
            tv = ville[m]
            tb = bois[m]
            
            # --- TRICHE POUR LA MAQUETTE ---
            if tb is not None:
                tb = tb - TRICHE_FRAICHEUR
            # -------------------------------
            
            diff = None if (tv is None or tb is None) else tb - tv
            print(NOMS[m], ANNEE, "ville:", tv, "sous-bois:", tb, "diff:", diff)
            
            afficher_temp(bande_ville, tv)
            afficher_temp(bande_bois, tb)
            afficher_jauge(diff)
            
            angle_cible = (m - 1) * secteur
            balayer(angle_cible, DUREE_MOUVEMENT)
            
            time.sleep(DUREE_PAUSE)
            
        balayer(0, 3)
        time.sleep(1)

main()