"""
Séquence 1, Séance 2 — Les files (implémentation avec un tableau circulaire)
=============================================================================

Ce fichier correspond à la section « Les files » de la fiche
« Interfaces, implémentation bas niveau et POO ».

Il contient :
  - le code du cours (creer_file, est_vide_file, premier_file,
    retirer_file, ajouter_file) ;
  - les tests des Exercices 1 et 2 du cours.

Pour l'exécuter : télécharger ce fichier, puis dans un terminal, dans le
même dossier :
    python3 files_circulaire.py

Si tout est correct, le programme affiche "Tests des files : OK".
"""


LEN_FILE = 10


def creer_file():
    """Construit une file vide, de taille fixe LEN_FILE."""
    return {"tableau": ['_'] * LEN_FILE,
            "position_tete": 0,
            "nombre_element": 0}


def est_vide_file(file):
    """Renvoie True si la file ne contient aucun élément."""
    return file["nombre_element"] == 0


def premier_file(file):
    """Renvoie l'élément en tête de file, sans le retirer."""
    assert not est_vide_file(file), "la file est vide"
    pos = file["position_tete"]
    return file["tableau"][pos]


def retirer_file(file):
    """Retourne la file mise à jour après la sortie de l'élément en tête."""
    assert not est_vide_file(file), "la file est vide"
    pos = file["position_tete"]
    file["tableau"][pos] = '_'
    file["nombre_element"] -= 1
    file["position_tete"] = (file["position_tete"] + 1) % LEN_FILE
    return file


def ajouter_file(file, element):
    """Retourne la file mise à jour après l'ajout d'un élément en queue."""
    assert file["nombre_element"] < LEN_FILE, "la file est pleine"
    pos = file["position_tete"]
    nb_element = file["nombre_element"]
    fin = (pos + nb_element) % LEN_FILE
    file["tableau"][fin] = element
    file["nombre_element"] += 1
    return file


def test_exercice_1():
    """Exercice 1 : décrire l'état de la file F lors des évènements."""
    f = creer_file()
    # État de départ du cours : e3, e4 en tête (positions 0 et 1),
    # e1, e2 en queue (positions 8 et 9), tête en position 8.
    f["tableau"] = ['e3', 'e4', '_', '_', '_', '_', '_', '_', 'e1', 'e2']
    f["position_tete"] = 8
    f["nombre_element"] = 4

    # 1. L'arrivée d'un électeur supplémentaire (e5, en queue)
    ajouter_file(f, 'e5')
    assert f["nombre_element"] == 5
    assert f["tableau"][2] == 'e5', "e5 doit être ajouté juste après e4, en position 2"

    # 2. La sortie de l'électeur venant de voter (e1)
    servi = premier_file(f)
    retirer_file(f)
    assert servi == 'e1'
    assert f["position_tete"] == 9
    assert f["nombre_element"] == 4

    # 3. L'arrivée d'un nouvel électeur (e6)
    ajouter_file(f, 'e6')
    assert f["nombre_element"] == 5
    assert premier_file(f) == 'e2', "e2 doit toujours être en tête"

    print("Exercice 1 : OK")


def test_exercice_2():
    """Exercice 2 : écrire premier_file, retirer_file, ajouter_file."""
    f = creer_file()
    assert est_vide_file(f), "une file tout juste créée doit être vide"

    ajouter_file(f, 'a')
    ajouter_file(f, 'b')
    ajouter_file(f, 'c')
    assert premier_file(f) == 'a'

    retirer_file(f)
    assert premier_file(f) == 'b'
    assert f["nombre_element"] == 2

    # Une file pleine doit refuser un nouvel ajout
    f_pleine = creer_file()
    for i in range(LEN_FILE):
        ajouter_file(f_pleine, f"e{i}")
    try:
        ajouter_file(f_pleine, "e_de_trop")
        raise AssertionError("ajouter_file aurait dû lever une erreur sur une file pleine")
    except AssertionError as erreur:
        assert "pleine" in str(erreur)

    print("Exercice 2 : OK")


if __name__ == "__main__":
    test_exercice_1()
    test_exercice_2()
    print("Tests des files : OK")
