"""
=============================================================
  Gestion des liens de parenté - Mini-projet Python R107
=============================================================
  Auteur  : [Nom Prénom]
  Date    : [Date]
  Desc.   : Programme de gestion de liens de parenté à partir
            de deux tableaux :
              - personnes : liste de [nom, prénom, sexe, date_naissance]
              - liens     : liste de [num_enfant, num_parent]
"""

# ─────────────────────────────────────────────────────────────
# STRUCTURES DE DONNÉES
# ─────────────────────────────────────────────────────────────
# personnes : liste de listes, chaque sous-liste = 1 personne
#   personnes[i] = [nom (str), prénom (str), sexe (str 'H'/'F'),
#                   date_naissance (str 'JJ/MM/AAAA')]
#
# liens     : liste de listes, chaque sous-liste = 1 lien de parenté
#   liens[k]  = [num_enfant (int), num_parent (int)]
# ─────────────────────────────────────────────────────────────


# =============================================================
# ÉTAPE 1 – Ajout d'une personne
# =============================================================

def ajouter_personne(personnes, nom, prenom, sexe, date_naissance):
    """
    Ajoute une nouvelle personne au tableau des personnes.

    Paramètres :
      personnes      (list) : tableau des personnes (modifié en place)
      nom            (str)  : nom de famille
      prenom         (str)  : prénom
      sexe           (str)  : 'H' (Homme) ou 'F' (Femme)
      date_naissance (str)  : date au format 'JJ/MM/AAAA'

    Retour :
      (int) : numéro (indice) attribué à la nouvelle personne
    """
    nouvelle_personne = [nom, prenom, sexe, date_naissance]
    personnes.append(nouvelle_personne)
    return len(personnes) - 1   # indice de la personne ajoutée


# =============================================================
# ÉTAPE 2 – Affichage de toutes les personnes
# =============================================================

def afficher_personnes(personnes):
    """
    Affiche toutes les personnes dans l'ordre d'enregistrement.

    Paramètres :
      personnes (list) : tableau des personnes

    Retour :
      None
    """
    if len(personnes) == 0:
        print("  (aucune personne enregistrée)")
        return None

    print(f"  {'N°':<4} {'Nom':<15} {'Prénom':<15} {'Sexe':<5} {'Naissance'}")
    print("  " + "-" * 55)
    for i in range(len(personnes)):
        p = personnes[i]
        print(f"  {i:<4} {p[0]:<15} {p[1]:<15} {p[2]:<5} {p[3]}")
    return None


# =============================================================
# ÉTAPE 3 – Sélectionner le numéro d'une personne
# =============================================================

def selectionner_personne(personnes, nom, prenom):
    """
    Renvoie le numéro (indice) de la première personne dont le nom
    ET le prénom correspondent aux valeurs fournies.

    Paramètres :
      personnes (list) : tableau des personnes
      nom       (str)  : nom recherché
      prenom    (str)  : prénom recherché

    Retour :
      (int) : numéro de la personne trouvée, ou -1 si introuvable
    """
    numero = -1
    i = 0
    while i < len(personnes) and numero == -1:
        if personnes[i][0] == nom and personnes[i][1] == prenom:
            numero = i
        i += 1
    return numero


# =============================================================
# ÉTAPE 4 – Créer un lien de parenté
# =============================================================

def ajouter_lien(liens, num_enfant, num_parent):
    """
    Ajoute un lien de parenté entre un enfant et un parent.

    Paramètres :
      liens      (list) : tableau des liens (modifié en place)
      num_enfant (int)  : indice de l'enfant dans le tableau personnes
      num_parent (int)  : indice du parent dans le tableau personnes

    Retour :
      None
    """
    lien = [num_enfant, num_parent]
    liens.append(lien)
    return None


# =============================================================
# ÉTAPE 5 – Ascendants d'une personne
# =============================================================

def obtenir_ascendants(liens, num_personne):
    """
    Retourne la liste de tous les ascendants connus d'une personne,
    en remontant récursivement l'arbre généalogique.

    Paramètres :
      liens         (list) : tableau des liens de parenté
      num_personne  (int)  : numéro de la personne dont on cherche les ascendants

    Retour :
      (list of int) : numéros des ascendants (sans doublons)
    """
    ascendants = []

    # Parents directs de num_personne
    parents_directs = []
    for k in range(len(liens)):
        if liens[k][0] == num_personne:
            parents_directs.append(liens[k][1])

    # Pour chaque parent direct, on l'ajoute puis on remonte
    for num_parent in parents_directs:
        if num_parent not in ascendants:
            ascendants.append(num_parent)
        # Ascendants du parent (récursion)
        asc_du_parent = obtenir_ascendants(liens, num_parent)
        for a in asc_du_parent:
            if a not in ascendants:
                ascendants.append(a)

    return ascendants


# =============================================================
# ÉTAPE 6 – Descendants d'une personne
# =============================================================

def obtenir_descendants(liens, num_personne):
    """
    Retourne la liste de tous les descendants connus d'une personne,
    en descendant récursivement l'arbre généalogique.

    Paramètres :
      liens         (list) : tableau des liens de parenté
      num_personne  (int)  : numéro de la personne dont on cherche les descendants

    Retour :
      (list of int) : numéros des descendants (sans doublons)
    """
    descendants = []

    # Enfants directs de num_personne
    enfants_directs = []
    for k in range(len(liens)):
        if liens[k][1] == num_personne:
            enfants_directs.append(liens[k][0])

    # Pour chaque enfant direct, on l'ajoute puis on descend
    for num_enfant in enfants_directs:
        if num_enfant not in descendants:
            descendants.append(num_enfant)
        # Descendants de l'enfant (récursion)
        desc_de_enfant = obtenir_descendants(liens, num_enfant)
        for d in desc_de_enfant:
            if d not in descendants:
                descendants.append(d)

    return descendants


# =============================================================
# ÉTAPE 7 – Frères et sœurs d'une personne
# =============================================================

def obtenir_freres_soeurs(liens, num_personne):
    """
    Retourne la liste des frères et sœurs d'une personne,
    c'est-à-dire les personnes partageant au moins un parent commun.

    Paramètres :
      liens         (list) : tableau des liens de parenté
      num_personne  (int)  : numéro de la personne

    Retour :
      (list of int) : numéros des frères/sœurs (sans doublons,
                      la personne elle-même exclue)
    """
    freres_soeurs = []

    # 1. Trouver les parents de num_personne
    parents = []
    for k in range(len(liens)):
        if liens[k][0] == num_personne:
            parents.append(liens[k][1])

    # 2. Trouver tous les enfants de ces parents
    for num_parent in parents:
        for k in range(len(liens)):
            if liens[k][1] == num_parent:
                candidat = liens[k][0]
                # Exclure la personne elle-même et les doublons
                if candidat != num_personne and candidat not in freres_soeurs:
                    freres_soeurs.append(candidat)

    return freres_soeurs


# =============================================================
# ÉTAPE 8 – Affichage par ordre alphabétique
# =============================================================

def afficher_par_ordre_alpha(personnes, liste_numeros):
    """
    Affiche les informations des personnes dont les numéros sont
    dans liste_numeros, triées par ordre alphabétique (nom puis prénom).

    Paramètres :
      personnes     (list)      : tableau des personnes
      liste_numeros (list of int) : numéros des personnes à afficher

    Retour :
      None
    """
    if len(liste_numeros) == 0:
        print("  (liste vide)")
        return None

    # Copie de la liste pour ne pas modifier l'originale
    numeros_tries = liste_numeros[:]

    # Tri par insertion sur (nom, prénom)
    for i in range(1, len(numeros_tries)):
        cle = numeros_tries[i]
        p_cle = personnes[cle]
        j = i - 1
        while j >= 0 and (
            personnes[numeros_tries[j]][0] > p_cle[0] or
            (personnes[numeros_tries[j]][0] == p_cle[0] and
             personnes[numeros_tries[j]][1] > p_cle[1])
        ):
            numeros_tries[j + 1] = numeros_tries[j]
            j -= 1
        numeros_tries[j + 1] = cle

    # Affichage
    print(f"  {'N°':<4} {'Nom':<15} {'Prénom':<15} {'Sexe':<5} {'Naissance'}")
    print("  " + "-" * 55)
    for num in numeros_tries:
        p = personnes[num]
        print(f"  {num:<4} {p[0]:<15} {p[1]:<15} {p[2]:<5} {p[3]}")

    return None


# =============================================================
# ÉTAPE 9 – Affichage de la plus âgée à la plus jeune
# =============================================================

def date_vers_entier(date_str):
    """
    Convertit une date 'JJ/MM/AAAA' en entier AAAAMMJJ pour comparaison.

    Paramètres :
      date_str (str) : date au format 'JJ/MM/AAAA'

    Retour :
      (int) : entier de la forme AAAAMMJJ
    """
    parties = date_str.split('/')
    # parties[0]=jour, parties[1]=mois, parties[2]=année
    entier = int(parties[2]) * 10000 + int(parties[1]) * 100 + int(parties[0])
    return entier


def afficher_par_age(personnes, liste_numeros):
    """
    Affiche les informations des personnes dont les numéros sont
    dans liste_numeros, triées de la plus âgée (date la plus ancienne)
    à la plus jeune (date la plus récente).

    Paramètres :
      personnes     (list)        : tableau des personnes
      liste_numeros (list of int) : numéros des personnes à afficher

    Retour :
      None
    """
    if len(liste_numeros) == 0:
        print("  (liste vide)")
        return None

    # Copie de la liste
    numeros_tries = liste_numeros[:]

    # Tri par insertion sur la date de naissance (ordre croissant = plus âgé en premier)
    for i in range(1, len(numeros_tries)):
        cle = numeros_tries[i]
        date_cle = date_vers_entier(personnes[cle][3])
        j = i - 1
        while j >= 0 and date_vers_entier(personnes[numeros_tries[j]][3]) > date_cle:
            numeros_tries[j + 1] = numeros_tries[j]
            j -= 1
        numeros_tries[j + 1] = cle

    # Affichage
    print(f"  {'N°':<4} {'Nom':<15} {'Prénom':<15} {'Sexe':<5} {'Naissance'}")
    print("  " + "-" * 55)
    for num in numeros_tries:
        p = personnes[num]
        print(f"  {num:<4} {p[0]:<15} {p[1]:<15} {p[2]:<5} {p[3]}")

    return None


# =============================================================
# ÉTAPE 10 – Menu principal (interactions utilisateur)
# =============================================================

# --- Fonctions de saisie / affichage pour le menu ---

def saisir_entier(message, mini, maxi):
    """
    Demande à l'utilisateur de saisir un entier entre mini et maxi inclus.
    Recommence jusqu'à obtenir une valeur valide.

    Paramètres :
      message (str) : invite affichée à l'utilisateur
      mini    (int) : valeur minimale acceptée
      maxi    (int) : valeur maximale acceptée

    Retour :
      (int) : valeur saisie et validée
    """
    valeur = mini - 1   # initialisation hors plage
    valide = False
    while not valide:
        saisie = input(message)
        # Vérification que la saisie est bien un entier
        est_entier = True
        if len(saisie) == 0:
            est_entier = False
        else:
            debut = 0
            if saisie[0] == '-':
                debut = 1
            for c in saisie[debut:]:
                if c < '0' or c > '9':
                    est_entier = False
        if est_entier:
            valeur = int(saisie)
            if valeur >= mini and valeur <= maxi:
                valide = True
            else:
                print(f"  ⚠ Valeur hors plage [{mini}, {maxi}]. Réessayez.")
        else:
            print("  ⚠ Saisie invalide (entier attendu). Réessayez.")
    return valeur


# --- Actions du menu (saisie + traitement + affichage) ---

def action_ajouter_personne(personnes):
    """
    a) Lit les informations d'une nouvelle personne au clavier.
    b) Appelle ajouter_personne().
    c) Affiche le numéro attribué.
    """
    print("\n  -- Ajout d'une personne --")
    nom    = input("  Nom         : ").strip()
    prenom = input("  Prénom      : ").strip()
    sexe   = input("  Sexe (H/F)  : ").strip().upper()
    while sexe not in ('H', 'F'):
        sexe = input("  Sexe (H/F)  : ").strip().upper()
    date   = input("  Naissance (JJ/MM/AAAA) : ").strip()
    num = ajouter_personne(personnes, nom, prenom, sexe, date)
    print(f"  ✔ Personne ajoutée sous le numéro {num}.")
    return None


def action_afficher_toutes(personnes):
    """
    a) Aucune saisie nécessaire.
    b) Appelle afficher_personnes().
    c) L'affichage est fait par la fonction.
    """
    print("\n  -- Liste de toutes les personnes --")
    afficher_personnes(personnes)
    return None


def action_chercher_personne(personnes):
    """
    a) Lit un nom et un prénom au clavier.
    b) Appelle selectionner_personne().
    c) Affiche le numéro trouvé ou un message d'erreur.
    """
    print("\n  -- Recherche d'une personne --")
    nom    = input("  Nom    : ").strip()
    prenom = input("  Prénom : ").strip()
    num = selectionner_personne(personnes, nom, prenom)
    if num == -1:
        print(f"  ✘ Personne '{prenom} {nom}' introuvable.")
    else:
        print(f"  ✔ '{prenom} {nom}' est la personne n°{num}.")
    return None


def action_ajouter_lien(personnes, liens):
    """
    a) Lit les numéros d'enfant et de parent au clavier.
    b) Appelle ajouter_lien().
    c) Confirme l'ajout.
    """
    print("\n  -- Ajout d'un lien de parenté --")
    nb = len(personnes) - 1
    if nb < 0:
        print("  ✘ Aucune personne enregistrée.")
        return None
    num_enfant = saisir_entier(f"  Numéro de l'enfant  [0-{nb}] : ", 0, nb)
    num_parent = saisir_entier(f"  Numéro du parent    [0-{nb}] : ", 0, nb)
    ajouter_lien(liens, num_enfant, num_parent)
    e = personnes[num_enfant]
    p = personnes[num_parent]
    print(f"  ✔ Lien ajouté : {e[1]} {e[0]} est enfant de {p[1]} {p[0]}.")
    return None


def action_ascendants(personnes, liens):
    """
    a) Lit le numéro d'une personne.
    b) Appelle obtenir_ascendants().
    c) Affiche les ascendants par ordre alphabétique.
    """
    print("\n  -- Ascendants d'une personne --")
    nb = len(personnes) - 1
    if nb < 0:
        print("  ✘ Aucune personne enregistrée.")
        return None
    num = saisir_entier(f"  Numéro de la personne [0-{nb}] : ", 0, nb)
    asc = obtenir_ascendants(liens, num)
    p = personnes[num]
    print(f"\n  Ascendants de {p[1]} {p[0]} :")
    if len(asc) == 0:
        print("  (aucun ascendant connu)")
    else:
        afficher_par_ordre_alpha(personnes, asc)
    return None


def action_descendants(personnes, liens):
    """
    a) Lit le numéro d'une personne.
    b) Appelle obtenir_descendants().
    c) Affiche les descendants par ordre alphabétique.
    """
    print("\n  -- Descendants d'une personne --")
    nb = len(personnes) - 1
    if nb < 0:
        print("  ✘ Aucune personne enregistrée.")
        return None
    num = saisir_entier(f"  Numéro de la personne [0-{nb}] : ", 0, nb)
    desc = obtenir_descendants(liens, num)
    p = personnes[num]
    print(f"\n  Descendants de {p[1]} {p[0]} :")
    if len(desc) == 0:
        print("  (aucun descendant connu)")
    else:
        afficher_par_ordre_alpha(personnes, desc)
    return None


def action_freres_soeurs(personnes, liens):
    """
    a) Lit le numéro d'une personne.
    b) Appelle obtenir_freres_soeurs().
    c) Affiche les frères/sœurs par ordre alphabétique.
    """
    print("\n  -- Frères et sœurs d'une personne --")
    nb = len(personnes) - 1
    if nb < 0:
        print("  ✘ Aucune personne enregistrée.")
        return None
    num = saisir_entier(f"  Numéro de la personne [0-{nb}] : ", 0, nb)
    fs = obtenir_freres_soeurs(liens, num)
    p = personnes[num]
    print(f"\n  Frères/sœurs de {p[1]} {p[0]} :")
    if len(fs) == 0:
        print("  (aucun frère/sœur connu)")
    else:
        afficher_par_ordre_alpha(personnes, fs)
    return None


def afficher_menu():
    """
    Affiche les options du menu principal.

    Retour :
      None
    """
    print("\n" + "=" * 55)
    print("   GESTION DES LIENS DE PARENTÉ")
    print("=" * 55)
    print("  1. Ajouter une personne")
    print("  2. Afficher toutes les personnes")
    print("  3. Rechercher le numéro d'une personne")
    print("  4. Ajouter un lien de parenté")
    print("  5. Afficher les ascendants d'une personne")
    print("  6. Afficher les descendants d'une personne")
    print("  7. Afficher les frères/sœurs d'une personne")
    print("  0. Quitter")
    print("=" * 55)
    return None


def menu_principal(personnes, liens):
    """
    Boucle principale du menu interactif.
    Affiche le menu, saisit le choix, appelle la fonction adéquate.

    Paramètres :
      personnes (list) : tableau des personnes
      liens     (list) : tableau des liens de parenté

    Retour :
      None
    """
    choix = -1
    while choix != 0:
        afficher_menu()
        choix = saisir_entier("  Votre choix : ", 0, 7)

        if choix == 1:
            action_ajouter_personne(personnes)
        elif choix == 2:
            action_afficher_toutes(personnes)
        elif choix == 3:
            action_chercher_personne(personnes)
        elif choix == 4:
            action_ajouter_lien(personnes, liens)
        elif choix == 5:
            action_ascendants(personnes, liens)
        elif choix == 6:
            action_descendants(personnes, liens)
        elif choix == 7:
            action_freres_soeurs(personnes, liens)
        elif choix == 0:
            print("\n  À bientôt !")

    return None


# =============================================================
# PROGRAMME PRINCIPAL – Tests hard-codés puis menu
# =============================================================

if __name__ == "__main__":

    # ── Initialisation des deux tableaux ──
    personnes = []
    liens     = []

    # ── Ajout de personnes (tests hard-codés) ──
    #   génération 0 (grands-parents)
    ajouter_personne(personnes, "Dupont",   "Henri",   "H", "12/03/1940")  # 0
    ajouter_personne(personnes, "Dupont",   "Jeanne",  "F", "05/07/1943")  # 1
    ajouter_personne(personnes, "Martin",   "Robert",  "H", "22/11/1938")  # 2
    ajouter_personne(personnes, "Martin",   "Suzanne", "F", "30/01/1942")  # 3
    #   génération 1 (parents)
    ajouter_personne(personnes, "Dupont",   "Paul",    "H", "14/06/1968")  # 4
    ajouter_personne(personnes, "Dupont",   "Marie",   "F", "09/09/1972")  # 5  (sœur de Paul)
    ajouter_personne(personnes, "Martin",   "Sophie",  "F", "03/03/1970")  # 6  (épouse de Paul)
    #   génération 2 (enfants)
    ajouter_personne(personnes, "Dupont",   "Lucas",   "H", "18/02/1995")  # 7
    ajouter_personne(personnes, "Dupont",   "Léa",     "F", "27/08/1998")  # 8

    # ── Ajout des liens de parenté ──
    # Paul (4) et Marie (5) sont enfants d'Henri (0)
    ajouter_lien(liens, 4, 0)
    ajouter_lien(liens, 5, 0)
    # Paul (4) et Marie (5) sont enfants de Jeanne (1)
    ajouter_lien(liens, 4, 1)
    ajouter_lien(liens, 5, 1)
    # Sophie (6) est enfant de Robert (2)
    ajouter_lien(liens, 6, 2)
    # Sophie (6) est enfant de Suzanne (3)
    ajouter_lien(liens, 6, 3)
    # Lucas (7) et Léa (8) sont enfants de Paul (4)
    ajouter_lien(liens, 7, 4)
    ajouter_lien(liens, 8, 4)
    # Lucas (7) et Léa (8) sont enfants de Sophie (6)
    ajouter_lien(liens, 7, 6)
    ajouter_lien(liens, 8, 6)

    # ── Tests des fonctions ──
    print("\n" + "=" * 55)
    print("  TESTS HARD-CODÉS")
    print("=" * 55)

    print("\n[Test 2] Toutes les personnes :")
    afficher_personnes(personnes)

    print("\n[Test 3] Recherche de 'Paul Dupont' :")
    n = selectionner_personne(personnes, "Dupont", "Paul")
    print(f"  → numéro trouvé : {n}")

    print("\n[Test 5] Ascendants de Lucas (n°7) :")
    asc = obtenir_ascendants(liens, 7)
    afficher_par_ordre_alpha(personnes, asc)

    print("\n[Test 6] Descendants d'Henri (n°0) :")
    desc = obtenir_descendants(liens, 0)
    afficher_par_ordre_alpha(personnes, desc)

    print("\n[Test 7] Frères/sœurs de Lucas (n°7) :")
    fs = obtenir_freres_soeurs(liens, 7)
    afficher_par_ordre_alpha(personnes, fs)

    print("\n[Test 8] Descendants d'Henri par ordre alpha :")
    afficher_par_ordre_alpha(personnes, desc)

    print("\n[Test 9] Descendants d'Henri par âge (+ âgé → + jeune) :")
    afficher_par_age(personnes, desc)

    # ── Menu interactif ──
    menu_principal(personnes, liens)
