"""Corrigé de l'épreuve pratique NSI 2026, sujet n°1 (codage RLE).

Les fonctions fournies par le sujet sont recopiées telles quelles.
Les solutions sont exactement celles publiées dans la correction.
Exécuter ce fichier lance les tests des questions 2 et 4 ; le codage-décodage
des images ne s'exécute que si bac_nsi_32.png et bac_nsi_256.png sont dans le
même dossier (bibliothèque pillow requise).
"""

import os

from PIL import Image


def codage_rle(liste_octets):
    """Renvoie une liste d'octets obtenue par compression RLE"""
    liste_rle = []
    i = 0
    while i < len(liste_octets):
        valeur = liste_octets[i]
        occ = 1
        while i + occ < len(liste_octets) and liste_octets[i + occ] == valeur:
            occ += 1
        liste_rle.append(occ)
        liste_rle.append(valeur)
        i += occ
    return liste_rle


#############################################################################
# Question 2 : décodage d'une liste RLE                                     #
#############################################################################


def decodage_rle(liste_rle):
    """Renvoie la liste d'octets obtenue à partir de la liste liste_rle obtenue
    par compression RLE"""
    liste_octets = []
    i = 0
    while i < len(liste_rle):
        cpt = liste_rle[i]              # le compte
        valeur = liste_rle[i + 1]       # la valeur
        for k in range(cpt):            # on répète 'cpt' fois...
            liste_octets.append(valeur) # ... ajouter 'valeur' en fin de liste
        i = i + 2                       # '+2' car on traite les valeurs de 2 en 2
    return liste_octets


# Forme compacte mentionnée dans la correction, laissée en commentaire :
#
# def decodage_rle(liste_rle):
#     liste_octets = []
#     for i in range(0, len(liste_rle), 2):
#         liste_octets = liste_octets + [liste_rle[i + 1]] * liste_rle[i]
#     return liste_octets


def test_codage():
    assert codage_rle([255, 255, 0, 255, 255, 255]) == [2, 255, 1, 0, 3, 255]
    assert decodage_rle([2, 255, 1, 0, 3, 255]) == [255, 255, 0, 255, 255, 255]
    assert decodage_rle([]) == []
    assert decodage_rle([5, 0]) == [0, 0, 0, 0, 0]
    assert decodage_rle(codage_rle([4, 4, 4, 0, 5, 5])) == [4, 4, 4, 0, 5, 5]
    print("Tous les tests passent")


#############################################################################
# Fonctions fournies par le sujet                                           #
#############################################################################


def enregistrer_octets(nom_fichier, liste_octets):
    """Enregistre une liste de valeurs numériques entre 0 et 255 dans un
    le fichier nom_fichier. Si une valeur est plus grande que 255 on considère
    que c'est 255. De même pour les valeur plus petite que 0."""
    # Le fichier est ouvert en mode binaire pour pouvoir écrire sans restriction toute valeur
    # entre 0 et 255
    with open(nom_fichier, "wb") as fichier:
        # Pour convertir une liste de valeur entre 0 et 255 en une liste d'octets qui peuvent
        # être écrits dans le fichier, on utilise `bytes`
        fichier.write(bytes([max(0, min(255, b)) for b in liste_octets]))


def charger_octets(nom_fichier):
    """Renvoie la liste des octets présents dans le fichier nom_fichier"""
    with open(nom_fichier, "rb") as fichier:
        liste_octets = list(fichier.read())
        return liste_octets


def enregistrer_image(nom_image, largeur, liste_niveaux):
    """Enregistre un fichier image nom_image de la largeur donnée et dont les
    valeurs de niveaux de gris des pixels sont celles de la liste
    liste_niveaux"""
    hauteur = len(liste_niveaux) // largeur
    im = Image.frombytes("L", (largeur, hauteur), bytes(liste_niveaux))
    im.save(nom_image)


def charger_image(nom_image):
    """Étant donné une image nom_image, renvoie un couple (largeur, liste_niveaux) où
    largeur est la largeur de l'image et liste_niveaux est la liste des valeurs de niveaux
    de gris de l'image ligne par ligne"""

    image = Image.open(nom_image).convert("L")
    return (image.width, list(image.tobytes()))


def encoder_decoder_image(nom_image):
    """Fonction de test permettant d'encoder puis décoder une image avec un
    codage RLE. Le fichier rle est nommé nom_image.rle et le fichier decodé
    est nom_image.dec.png"""
    largeur, niveaux = charger_image(nom_image)
    enregistrer_octets(nom_image + ".rle", codage_rle(niveaux))
    niveaux_dec = charger_octets(nom_image + ".rle")
    enregistrer_image(nom_image + ".dec.png", largeur,
                      decodage_rle(niveaux_dec))


#############################################################################
# Question 3 : pas de code à écrire. Il suffit de lancer                    #
# encoder_decoder_image sur les deux images AVANT la modification de la     #
# question 4, puis d'ouvrir les fichiers .dec.png produits.                 #
#############################################################################


#############################################################################
# Question 4 : on interdit au compteur de dépasser 255                      #
#############################################################################


def codage_rle(liste_octets):
    """Renvoie une liste d'octets obtenue par compression RLE"""
    liste_rle = []
    i = 0
    while i < len(liste_octets):
        valeur = liste_octets[i]
        occ = 1
        while i + occ < len(liste_octets) and liste_octets[i + occ] == valeur and occ < 255:
            occ += 1
        liste_rle.append(occ)
        liste_rle.append(valeur)
        i += occ
    return liste_rle


def test_question_4():
    assert codage_rle([0] * 260) == [255, 0, 5, 0]
    assert decodage_rle(codage_rle([0] * 260)) == [0] * 260

    if os.path.exists("bac_nsi_32.png") and os.path.exists("bac_nsi_256.png"):
        encoder_decoder_image("bac_nsi_32.png")
        encoder_decoder_image("bac_nsi_256.png")


if __name__ == "__main__":
    test_codage()
    test_question_4()
