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
DUREE_MOIS = 4

PIN_SERVO = 18
PIN_VILLE = 13
PIN_BOIS = 14
PIN_DIFF = 26
NB_LED = 5

CSV_FICHIER = "arbre.csv"
COL_DATE_BOIS = "date"
COL_TEMP_BOIS = "temperature"

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"

T_MIN = 0
T_MAX = 30
DIFF_MAX = 5
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)


def angle_servo(a):
    servo.duty_u16(int(1638 + (a / 180) * (8192 - 1638)))


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"]:
        print("URL:", url)
        print("Reponse API:", data)
        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])
        print("Ville: lu depuis", CACHE_VILLE)
        return temp

    connect_wifi()
    for m in range(1, 13):
        temp[m] = api_mois(m)
        print("Ville", m, temp[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:
            if COL_DATE_BOIS not in ligne or COL_TEMP_BOIS not in ligne:
                print("Colonnes du CSV:", list(ligne.keys()))
                raise RuntimeError("Colonne date/temperature introuvable dans le CSV")
            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):
    x = max(0.0, min(1.0, (t - T_MIN) / (T_MAX - T_MIN)))
    return (int(LUM * x), 0, int(LUM * (1 - x)))


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 charge_diff(diff, duree):
    if diff is None:
        eteindre(bande_diff)
        time.sleep(duree)
        return
    niveau = max(1, min(NB_LED, round(abs(diff) / DIFF_MAX * NB_LED)))
    c = (0, 0, LUM) if diff < 0 else (LUM, 30, 0)
    fin = time.ticks_add(time.ticks_ms(), duree * 1000)
    while time.ticks_diff(fin, time.ticks_ms()) > 0:
        for i in range(niveau):
            bande_diff[i] = c
            bande_diff.write()
            time.sleep_ms(150)
        time.sleep_ms(300)
        eteindre(bande_diff)
        time.sleep_ms(300)


NOMS = ["", "Jan", "Fev", "Mar", "Avr", "Mai", "Juin",
        "Juil", "Aout", "Sep", "Oct", "Nov", "Dec"]


def main():
    ville = charger_ville()
    bois = charger_bois()
    while True:
        for m in range(1, 13):
            angle_servo((m - 1) * 180 / 11)
            tv, tb = ville[m], bois[m]
            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)
            charge_diff(diff, DUREE_MOIS)
        eteindre(bande_diff)


main()