"""
Entraînement au bac : polynômes et nutrition (Séance 6)
================================================================
Vérification complète des deux exercices du sujet corrigé.
"""

# ============================================================
# EXERCICE 1 : LES POLYNOMES
# ============================================================

def creer_liste():
    return None

def ajouter_en_tete(x, L):
    return (x, L)

def tete(L):
    return L[0]

def queue(L):
    return L[1]

def est_vide(L):
    return L is None

def supprimer_en_tete(L):
    return queue(L)

def polynome_vers_liste(L):
    resultat = []
    while not est_vide(L):
        resultat.append(tete(L))
        L = queue(L)
    return resultat


def construire_p1():
    p1 = creer_liste()
    p1 = ajouter_en_tete((2, 0), p1)
    p1 = ajouter_en_tete((-1, 1), p1)
    p1 = ajouter_en_tete((2, 3), p1)
    return p1

def construire_p2():
    p2 = creer_liste()
    p2 = ajouter_en_tete((1, 0), p2)
    p2 = ajouter_en_tete((3, 2), p2)
    p2 = ajouter_en_tete((5, 4), p2)
    return p2


def valuation(liste, x):
    if est_vide(liste):
        return 0
    temp = tete(liste)
    return temp[0] * x ** temp[1] + valuation(queue(liste), x)


def addition_monome_polynome(element, liste):
    if est_vide(liste):
        L = creer_liste()
        L = ajouter_en_tete(element, L)
        return L
    premier = tete(liste)
    if element[1] > premier[1]:
        return ajouter_en_tete(element, liste)
    elif element[1] == premier[1]:
        nouveau_coeff = premier[0] + element[0]
        reste = queue(liste)
        if nouveau_coeff == 0:
            return reste
        else:
            return ajouter_en_tete((nouveau_coeff, element[1]), reste)
    else:
        L = addition_monome_polynome(element, queue(liste))
        return ajouter_en_tete(premier, L)


def addition_polynome(liste1, liste2):
    if est_vide(liste1):
        return liste2
    L = addition_polynome(queue(liste1), liste2)
    return addition_monome_polynome(tete(liste1), L)


# ============================================================
# EXERCICE 2 : NUTRITION
# ============================================================

class aliment:
    def __init__(self, nom, energie, lipide, glucide, fibre, proteine, sel):
        self.nom = nom
        self.energie = energie
        self.lipide = lipide
        self.glucide = glucide
        self.fibre = fibre
        self.proteine = proteine
        self.sel = sel

    def __repr__(self):
        return (f"Intitulé : {self.nom}\n"
                f"Énergie : {self.energie} kJ\n"
                f"Lipides : {self.lipide} g\n"
                f"Glucides : {self.glucide} g\n"
                f"Fibres : {self.fibre} g\n"
                f"Proteines : {self.proteine} g\n"
                f"Sel : {self.sel}g")


def masse_plat(plat):
    masse_totale = 0
    for _, masse in plat:
        masse_totale += masse
    return masse_totale

def energie_plat(plat):
    masse_totale = masse_plat(plat)
    energie_totale = 0
    for un_aliment, masse in plat:
        energie_totale += masse * un_aliment.energie / 100
    return energie_totale / masse_totale * 100

def lipide_plat(plat):
    masse_totale = masse_plat(plat)
    lipide_total = 0
    for un_aliment, masse in plat:
        lipide_total += masse * un_aliment.lipide / 100
    return lipide_total / masse_totale * 100

def glucide_plat(plat):
    masse_totale = masse_plat(plat)
    glucide_total = 0
    for un_aliment, masse in plat:
        glucide_total += masse * un_aliment.glucide / 100
    return glucide_total / masse_totale * 100

def fibre_plat(plat):
    masse_totale = masse_plat(plat)
    fibre_total = 0
    for un_aliment, masse in plat:
        fibre_total += masse * un_aliment.fibre / 100
    return fibre_total / masse_totale * 100

def proteine_plat(plat):
    masse_totale = masse_plat(plat)
    proteine_total = 0
    for un_aliment, masse in plat:
        proteine_total += masse * un_aliment.proteine / 100
    return proteine_total / masse_totale * 100

def sel_plat(plat):
    masse_totale = masse_plat(plat)
    sel_total = 0
    for un_aliment, masse in plat:
        sel_total += masse * un_aliment.sel / 100
    return sel_total / masse_totale * 100

def fiche_info_nutrition(plat):
    return {'Energie': round(energie_plat(plat), 2),
            'Lipides': round(lipide_plat(plat), 2),
            'Glucides': round(glucide_plat(plat), 2),
            'Fibres': round(fibre_plat(plat), 2),
            'Proteines': round(proteine_plat(plat), 2),
            'Sel': round(sel_plat(plat), 2)}


if __name__ == "__main__":
    # --- Exercice 1, question 1 : construction de p1 ---
    p1 = construire_p1()
    assert polynome_vers_liste(p1) == [(2, 3), (-1, 1), (2, 0)]
    print("Q1 - construction de p1 : OK")

    # --- Question 2 : tete et queue ---
    assert tete(p1) == (2, 3)
    assert polynome_vers_liste(queue(p1)) == [(-1, 1), (2, 0)]
    print("Q2 - tete(p1) et queue(p1) : OK")

    # --- Question 3 : valuation ---
    assert valuation(p1, 2) == 16
    print("Q3 - valuation(p1, 2) == 16 : OK")

    # --- Question 4 : addition_monome_polynome (les 3 cas) ---
    p2 = construire_p2()
    assert polynome_vers_liste(p2) == [(5, 4), (3, 2), (1, 0)]

    # Cas "degre plus grand" : (3,5) contre p2 (tete de degre 4)
    r1 = addition_monome_polynome((3, 5), p2)
    assert polynome_vers_liste(r1) == [(3, 5), (5, 4), (3, 2), (1, 0)]

    # Cas "degre egal, somme non nulle" : (2,4) contre p2 (tete (5,4))
    r2 = addition_monome_polynome((2, 4), p2)
    assert polynome_vers_liste(r2) == [(7, 4), (3, 2), (1, 0)]

    # Cas "degre egal, somme nulle" : (-5,4) contre p2 (tete (5,4)) -> le terme disparait
    r3 = addition_monome_polynome((-5, 4), p2)
    assert polynome_vers_liste(r3) == [(3, 2), (1, 0)]

    # Cas "degre plus petit, recursion" : (2,1) contre p2 (aucun terme de degre 1 dans p2)
    r4 = addition_monome_polynome((2, 1), p2)
    assert polynome_vers_liste(r4) == [(5, 4), (3, 2), (2, 1), (1, 0)]
    print("Q4 - addition_monome_polynome (les 3 cas) : OK")

    # --- Question 4 : addition_polynome(p1, p2) ---
    somme = addition_polynome(p1, p2)
    resultat = polynome_vers_liste(somme)
    assert resultat == [(5, 4), (2, 3), (3, 2), (-1, 1), (3, 0)]
    # Verification par le calcul direct : p1(x) + p2(x) = 5x^4 + 2x^3 + 3x^2 - x + 3
    for x in [0, 1, 2, -3, 4.5]:
        valeur_attendue = (2*x**3 - x + 2) + (5*x**4 + 3*x**2 + 1)
        assert abs(valuation(somme, x) - valeur_attendue) < 1e-9
    print("Q4 - addition_polynome(p1, p2) == 5x^4+2x^3+3x^2-x+3 : OK")

    # ============================================================
    # EXERCICE 2 : NUTRITION
    # ============================================================

    # --- Question 1 : instanciation de riz ---
    riz = aliment('riz blanc', 1532, 0.6, 81, 1.3, 7.2, 0.01)
    assert riz.nom == 'riz blanc' and riz.energie == 1532 and riz.glucide == 81
    print("Q1 - instanciation de riz : OK")

    # --- Question 2 : __repr__ ---
    assert repr(riz) == ("Intitulé : riz blanc\n"
                          "Énergie : 1532 kJ\n"
                          "Lipides : 0.6 g\n"
                          "Glucides : 81 g\n"
                          "Fibres : 1.3 g\n"
                          "Proteines : 7.2 g\n"
                          "Sel : 0.01g")
    print("Q2 - __repr__(riz) conforme au sujet : OK")

    # --- Question 3 : quantite de glucides dans n grammes ---
    quantite_riz = 75
    quantite_glucides = quantite_riz / 100 * riz.glucide
    assert quantite_glucides == 60.75
    print("Q3 - quantite de glucides dans 75g de riz == 60.75 : OK")

    # --- Question 4a : masse_plat ---
    thon = aliment('thon au naturel', 0, 0, 0, 0, 0, 0)      # valeurs non fournies par le sujet
    tomate = aliment('tomate', 0, 0, 0, 0, 0, 0)              # (non nécessaires pour masse_plat)
    oeuf_dur = aliment('oeuf dur', 0, 0, 0, 0, 0, 0)
    mais = aliment('mais', 0, 0, 0, 0, 0, 0)
    salade_de_riz = [(riz, 500), (thon, 200), (tomate, 200), (oeuf_dur, 110), (mais, 300)]
    assert masse_plat(salade_de_riz) == 1310
    print("Q4a - masse_plat(salade_de_riz) == 1310 : OK")

    # --- Question 4b : energie_plat et fonctions similaires (formule corrigee) ---
    # Invariant : un plat compose d'un seul aliment doit redonner EXACTEMENT
    # les valeurs pour 100g de cet aliment, quelle que soit la quantite.
    plat_riz_seul_1 = [(riz, 250)]
    plat_riz_seul_2 = [(riz, 40)]
    for plat_riz_seul in (plat_riz_seul_1, plat_riz_seul_2):
        assert abs(energie_plat(plat_riz_seul) - riz.energie) < 1e-9
        assert abs(glucide_plat(plat_riz_seul) - riz.glucide) < 1e-9
        assert abs(proteine_plat(plat_riz_seul) - riz.proteine) < 1e-9
    print("Q4b - invariant (1 seul aliment => memes valeurs que pour 100g) : OK")

    # Ponderation : deux aliments fictifs A et B, pour verifier que le poids
    # de chacun dans le plat influence bien le resultat.
    A = aliment('A (exemple)', 1000, 10, 20, 1, 5, 0.1)
    B = aliment('B (exemple)', 2000, 20, 40, 2, 10, 0.2)

    plat_egal = [(A, 100), (B, 100)]   # autant de A que de B
    assert abs(energie_plat(plat_egal) - 1500) < 1e-9   # moyenne simple
    assert abs(glucide_plat(plat_egal) - 30) < 1e-9

    plat_pondere = [(A, 50), (B, 150)]  # 3 fois plus de B que de A
    assert abs(energie_plat(plat_pondere) - 1750) < 1e-9  # plus proche de B
    print("Q4b - ponderation par la masse de chaque aliment : OK")

    # --- Question 4c : fiche_info_nutrition ---
    fiche_test = fiche_info_nutrition(plat_egal)
    assert fiche_test == {'Energie': 1500.0, 'Lipides': 15.0, 'Glucides': 30.0,
                           'Fibres': 1.5, 'Proteines': 7.5, 'Sel': 0.15}
    print("Q4c - fiche_info_nutrition(plat_egal) : OK")

    print("Tous les tests : OK")
