#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-

u"""
Copyright (C) Valentin Samir
Licence : GPLv3

"""
import sys
import time
import datetime
import dateutil.relativedelta
if '/usr/scripts' not in sys.path:
    sys.path.append('/usr/scripts')

import config.cotisation

import lc_ldap.objets as objets
import lc_ldap.attributs as attributs
from lc_ldap.attributs import UniquenessError

import proprio
from CPS import TailCall, tailcaller, Continue

class Dialog(proprio.Dialog):
    """
    Classe Dialog spécifique aux adhérents pour gest_crans_lc
    """
    def modif_adherent_blacklist(self, adherent, cont):
        """Raccourci vers edit_blacklist spécifique aux adherent"""
        return self.edit_blacklist(obj=adherent, title="Éditions des blacklist de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), update_obj='adherent', cont=cont)


    def modif_adherent(self, cont, adherent=None, tag=None):
        """Menu d'édition d'un adhérent"""
        if adherent is None:
            adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont)
        a = attributs
        menu_droits = {
            'Administratif' : [a.cableur, a.nounou],
            'Personnel':[a.cableur, a.nounou, a.soi],
            'Études':[a.nounou, a.soi, a.cableur],
            'Chambre':[a.cableur, a.nounou],
            'Compte':[a.cableur, a.nounou],
            'GPGFingerprint' : [a.nounou, a.soi],
            'Remarques' : [a.cableur, a.nounou],
            'Droits':[a.nounou, a.bureau],
            'Blackliste':[a.cableur, a.nounou],
            'Vente':[a.cableur, a.nounou],
            'Supprimer':[a.nounou, a.bureau],
        }
        menu = {
            'Administratif' : {'text' : "Adhésion, carte étudiant, chartes", "callback":self.adherent_administratif},
            'Personnel' : {'text' : "Nom, prénom, téléphone... (ajouter l'age ?)", 'callback':self.adherent_personnel},
            'Études'       : {'text' : "Étude en cours", "callback":self.adherent_etudes},
            'Chambre'      : {'text' : 'Déménagement', "callback":self.adherent_chambre},
            'Compte'       : {'text' : "Gestion du compte crans", "adherent":"proprio", "callback":TailCall(self.proprio_compte, update_obj='adherent'), 'help':"Création/Suppression/Activation/Désactivation du compte, gestion des alias mails crans du compte"},
            'GPGFingerprint' : {'text':'Ajouter ou supprimer une empeinte GPG', 'attribut':attributs.gpgFingerprint},
            'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info},
            'Droits' : {'text':"Modifier les droits alloués à cet adhérent", "callback":self.adherent_droits},
            'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_adherent_blacklist},
            'Vente' : {'text':"Chargement solde crans, vente de cable ou adaptateur ethernet ou autre", "adherent":"proprio", "callback":self.proprio_vente},
            'Supprimer' : {'text':"Supprimer l'adhérent de la base de donnée", 'callback':TailCall(self.delete_adherent, del_cont=cont(proprio=None))},
        }
        menu_order = ['Administratif', 'Personnel', 'Études', 'Chambre', 'Compte']
        menu_compte_crans = ['Droits']
        menu_end = ['GPGFingerprint', 'Remarques', 'Blackliste', 'Vente', 'Supprimer']

        if "cransAccount" in adherent['objectClass']:
            menu_order.extend(menu_compte_crans)
        menu_order.extend(menu_end)

        def box(default_item=None):
            choices = []
            for key in menu_order:
                if self.has_right(menu_droits[key], adherent):
                    choices.append((key, menu[key]['text'], menu[key].get('help', "")))
            return self.dialog.menu(
                "Que souhaitez vous modifier ?",
                width=0,
                height=0,
                menu_height=0,
                timeout=self.timeout,
                item_help=1,
                default_item=str(default_item),
                title="Modification de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                scrollbar=True,
                cancel_label="Retour",
                backtitle=self._connected_as(),
                choices=choices)

        def todo(tag, menu, adherent, cont_ret):
            if not tag in menu_order:
                raise Continue(cont_ret)
            else:
                if 'callback' in menu[tag]:
                    raise Continue(TailCall(menu[tag]['callback'], cont=cont_ret, **{menu[tag].get('adherent', 'adherent'):adherent}))
                elif 'attribut' in menu[tag]:
                    raise Continue(TailCall(self.modif_adherent_attributs, adherent=adherent, cont=cont_ret, attr=menu[tag]['attribut'].ldap_name))
                else:
                    raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag)

        (code, tag) = self.handle_dialog(cont, box, tag)
        cont_ret = TailCall(self.modif_adherent, cont=cont, adherent=adherent, tag=tag)

        return self.handle_dialog_result(
            code=code,
            output=tag,
            cancel_cont=cont(proprio=adherent),
            error_cont=cont_ret,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, cont_ret])]
        )

    def modif_adherent_attributs(self, adherent, attr, cont):
        """Juste un raccourci vers edit_attributs spécifique aux adherents"""
        return self.edit_attributs(obj=adherent, update_obj='adherent', attr=attr, title="Modification de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), cont=cont)

    def adherent_administratif(self, cont, adherent, default_item=None):
        """Menu de gestion du compte crans d'un proprio"""

        a = attributs
        menu_droits = {
            "Adhésion": [a.cableur, a.nounou],
            'Connexion': [a.cableur, a.nounou],
            "Charte MA" : [a.nounou, a.bureau],
            "Carte Étudiant" : [a.nounou, a.cableur, a.tresorier],
        }
        menu = {
            "Adhésion" : {"text":"Pour toute réadhésion *sans* connexion.", "help":"", "callback":self.adherent_adhesion},
            'Connexion' : {'text': "Mise à jour de l'accès Internet (effectue la réadhésion si besoin)", "help":"", 'callback':self.adherent_connexion},
            "Carte Étudiant" : {"text" : "Validation de la carte étudiant", "help":"", "callback":self.adherent_carte_etudiant},
            "Charte MA" : {"text" : "Signature de la charte des membres actifs", "help":'', "callback":self.adherent_charte},
        }
        menu_order = ["Adhésion", 'Connexion']
        if self.has_right(a.tresorier, adherent) or not adherent.carte_controle():
            menu_order.append("Carte Étudiant")
        menu_order.append("Charte MA")
        def box(default_item=None):
            return self.dialog.menu(
                "Quelle action administrative effectuer",
                width=0,
                height=0,
                timeout=self.timeout,
                item_help=1,
                default_item=str(default_item),
                title="Gestion administrative de %s %s" % (adherent.get('prenom', [''])[0], adherent["nom"][0]),
                scrollbar=True,
                cancel_label="Retour",
                backtitle=self._connected_as(),
                choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], adherent)])

        def todo(tag, menu, adherent, self_cont):
            if not tag in menu_order:
                raise Continue(self_cont)
            elif 'callback' in menu[tag]:
                raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, adherent=adherent))
            else:
                raise EnvironmentError("Il n'y a pas de champs 'callback' pour le tag %s" % tag)


        (code, tag) = self.handle_dialog(cont, box, default_item)
        self_cont = TailCall(self.adherent_administratif, adherent=adherent, cont=cont, default_item=tag)
        return self.handle_dialog_result(
            code=code,
            output=tag,
            cancel_cont=cont,
            error_cont=self_cont,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, self_cont])]
        )

    def adherent_adhesion_connexion_crediter(self, facture, adherent):
        adhesion = False
        connexion = False
        if [a for a in facture["article"] if a["code"] == "ADH"]:
            adhesion = True
        if [a for a in facture["article"] if a["code"].startswith("CAI")]:
            connexion = True
        # Appeler créditer va créditer ou débiter le solde, sauver le proprio et créer la facture
        if facture.mode == 'rw':
            facture.crediter()
        else:
            with self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] as facture:
                facture.crediter()
        with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
            if facture["finAdhesion"]:
                adherent["finAdhesion"].append(facture["finAdhesion"][0])
            if facture["debutAdhesion"]:
                adherent["debutAdhesion"].append(facture["debutAdhesion"][0])
            if facture["debutConnexion"]:
                adherent["debutConnexion"].append(facture["debutConnexion"][0])
            if facture["finConnexion"]:
                adherent["finConnexion"].append(facture["finConnexion"][0])
            adherent.validate_changes()
            adherent.history_gen()
            adherent.save()
        try:
            if adhesion and not connexion:
                self.dialog.msgbox(text=u"Adhésion effectué avec success", title=u"Adhésion terminé", width=0, height=0, timeout=self.timeout)
            elif not adhesion and connexion:
                self.dialog.msgbox(text=u"Connexion prolongée avec success", title=u"Connexion prolongée", width=0, height=0, timeout=self.timeout)
            elif adhesion and connexion:
                self.dialog.msgbox(text=u"Adhésion effectué et connexion prolongée avec success", title=u"Connexion & Adhésion", width=0, height=0, timeout=self.timeout)
        except KeyboardInterrupt:
            pass
        if facture['modePaiement'][0] == "solde":
            try:
                self.dialog.msgbox(text=u"Le solde de l'adhérent à bien été débité", title="Solde débité", width=0, height=0, timeout=self.timeout)
            except KeyboardInterrupt:
                pass
        return adherent

    def adherent_adhesion(self, cont, adherent, cancel_cont=None, tag_paiment=None, comment_paiement=None, crediter=True, facture=None):
        """
        Gestion de l'adhésion à l'association d'un adhérent
        Si cancel_cont est à None, cont est utilisé en cas d'annulation
        tag_paiment : un mode de paiement
        comment_paiement : un commentaire pour la facture
        crediter : Doit-on ou non créditer tout de suite la facture
        facture : Doit-t-on éditer une facture existante
        """

        # Boite si on ne peux pas réahdérer
        def box_already(end):
            t_end = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(end))
            self.dialog.msgbox("Actuellement adhérent jusqu'au %s.\nMerci de revenir lorsqu'il restera moins de %s jours avant la fin." % (t_end, config.cotisation.delai_readh_jour),
              width=0,
              height=0,
              timeout=self.timeout,
            )

        # Boite de confirmation à l'ahésion
        def box_adherer(end=None):
            if end:
                t_end = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(end))
                adherer = self.confirm(text="Adhésion jusqu'au %s. Réadhérer ?" % t_end, title="Adhésion de %s %s" % (adherent.get("prenom", [''])[0], adherent["nom"][0]))
            else:
                adherer = self.confirm(text="Adhésion pour un an, continuer ?", title="Adhésion de %s %s" % (adherent.get("prenom", [''])[0], adherent["nom"][0]))
            return adherer

        # Suppression d'une facture si elle est généré mais non validé
        def delete_facture(facture, cont):
            if facture:
                with self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] as facture:
                    facture.delete()
            raise Continue(cont)

        # Génération de la facture pour adhésion
        def paiement(tag_paiement, adherent, finadhesion, comment, facture, cancel_cont, cont):
            now = time.time()
            new_finadhesion = datetime.datetime.fromtimestamp(max(finadhesion, now))
            new_finadhesion = time.mktime(new_finadhesion.replace(year=new_finadhesion.year + config.cotisation.duree_adh_an).timetuple()) + 86400
            new_debutadhesion = now
            if facture:
                facture = self.conn.search(dn=facture.dn, scope=0, mode='rw')[0]
                to_create = False
            else:
                facture = self.conn.newFacture(adherent.dn, {})
                to_create = True
            with facture as facture:
                facture['modePaiement'] = unicode(tag_paiement, 'utf-8')
                facture['info'] = unicode(comment, 'utf-8')
                facture['article'].append(config.cotisation.dico_adh)
                facture["finAdhesion"] = unicode(new_finadhesion)
                facture["debutAdhesion"] = unicode(new_debutadhesion)
                # On peut retarder le credit pour ajouter des contribution pour la connexion internet à la facture
                if crediter:
                    if self.confirm_item(item=facture,
                        text=u"Le paiement de %sEUR a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), tag_paiement),
                        title=u"Validation du paiement",
                        timeout=self.timeout):
                        # Appeler créditer va créditer ou débiter le solde, sauver le proprio et créer la facture
                        adherent = self.adherent_adhesion_connexion_crediter(facture, adherent)
                    else:
                        if not self.confirm(text=u"Le paiement n'a pas été reçue.\n Annuler ?", title="Annulation de l'adhésion", defaultno=True):
                            raise Continue(cancel_cont)
                else:
                    if to_create:
                        facture.create()
                    else:
                        facture.validate_changes()
                        facture.history_gen()
                        facture.save()
                    raise Continue(cont(facture=facture))
            raise Continue(cont(adherent=adherent))


        finadhesion = adherent.fin_adhesion()
        # Si fin de l'adhésion trop loin dans le futur, rien a faire
        if finadhesion and finadhesion - config.cotisation.delai_readh > time.time():
            self.handle_dialog(cancel_cont if cancel_cont else cont, box_already, finadhesion)
            raise Continue(cancel_cont if cancel_cont else cont)

        # Sinon, si on accepte l'adhésion
        elif tag_paiment or self.handle_dialog(cont, box_adherer, finadhesion):
            self_cont = TailCall(self.adherent_adhesion, cont=cont, adherent=adherent, cancel_cont=cancel_cont, tag_paiment=tag_paiment, comment_paiement=comment_paiement, crediter=crediter, facture=facture)
            # On choisi un mode de paiement
            if not tag_paiment or not comment_paiement:
                return self.proprio_choose_paiement(proprio=adherent, cont=self_cont, cancel_cont=TailCall(delete_facture, facture, cancel_cont if cancel_cont else cont))
            else:
                lcont = self_cont.copy()
                lcont(comment_paiement=None)
                return self.handle_dialog_result(
                    code=self.dialog.DIALOG_OK,
                    output=[],
                    cancel_cont=lcont,
                    error_cont=lcont,
                    codes_todo=[([self.dialog.DIALOG_OK], paiement, [tag_paiment, adherent, finadhesion, comment_paiement, facture, lcont, cont])]
                )
        else:
            return self.handle_dialog_result(
                code=self.dialog.DIALOG_OK,
                output=[],
                cancel_cont=None,
                error_cont=cancel_cont if cancel_cont else cont,
                codes_todo=[([self.dialog.DIALOG_OK], delete_facture, [facture, cancel_cont if cancel_cont else cont])]
            )

    def adherent_connexion(self, cont, adherent, cancel_cont=None, facture=None, mois=None, default_item=None, tag_paiment=None, comment_paiement=None):
        """
        Prolonger la connexion d'un adhérent
        Si cancel_cont est à None, cont sera utilisé
        facture : doit-on éditer une facture existante
        mois : de combien de mois prolonger la connexion
        default_item : le nombre de mois selectionné par defaut
        tag_paiment : le mode de paiement a utiliser si on crée une facture
        comment_paiement : un commentaire à mettre si on crée une facture
        """
        menu = {
         "An": {'text':"Prolonger d'un an (pour %s€)" % config.cotisation.plafond_contribution, 'callback':TailCall(self.adherent_connexion, cont, adherent, cancel_cont, facture, 12, default_item, tag_paiment, comment_paiement)},
         "NC": {'text':"Pas de connexion", 'callback':TailCall(self.adherent_connexion, cont, adherent, cancel_cont, facture, 12, default_item, tag_paiment, comment_paiement)}
        }
        menu_order = ["An"]
        for i in range(1, config.cotisation.duree_conn_plafond):
            if config.cotisation.contribution * i < config.cotisation.plafond_contribution:
                menu["%s mois" % i] = {'text':"Prolonger de %s mois (pour %s€)" % (i, config.cotisation.contribution * i), 'callback':TailCall(self.adherent_connexion, cont, adherent, cancel_cont, facture, i, default_item, tag_paiment, comment_paiement)}
                menu_order.append("%s mois" % i)

        if facture:
            menu_order.append("NC")

        # Une boite pour choisir un nombre de mois pour prolonger la connexion
        def box(finconnexion, default_item=None):
            t_end = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(finconnexion))
            return self.dialog.menu(
                "Connexion jusqu'au %s" % t_end if finconnexion else "N'a jamais été connecté",
                width=0,
                height=0,
                menu_height=0,
                timeout=self.timeout,
                item_help=0,
                default_item=str(default_item),
                title="Connexion de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                scrollbar=True,
                cancel_label="Retour",
                backtitle=self._connected_as(),
                choices=[(k, menu[k]['text']) for k in menu_order])

        # Génération et crédit de la facture
        def todo(adherent, mois, finadhesion, finconnexion, cancel_cont, cont, facture=None, tag_paiment=None, comment=None):
            now = time.time()
            new_finconnexion = datetime.datetime.fromtimestamp(max(finconnexion, now))
            # On ajoute 3600 secondes sur suggestion de Raphaël Bonaque (<bonaque@crans.org>), pour tenir compte des malheureux qui
            # pourraient subir le changement d'heure.
            new_finconnexion = time.mktime((new_finconnexion + dateutil.relativedelta.relativedelta(months=mois)).timetuple()) + 3600
            new_debutconnexion = max(now, finconnexion)

            if new_finconnexion > finadhesion:
                t_end_adh = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(finadhesion))
                t_end_conn = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(new_finconnexion))
                if new_finconnexion - finadhesion > 30 * 3600 * 24:
                    raise ValueError("Impossible de prolonger la connexion jusqu'au %s plus d'un mois après la fin de l'adhésion au %s" % (t_end_conn, t_end_adh))
                else:
                    if not self.confirm("La fin de la connexion de l'adhérent (%s) tombera après la fin de son adhésion (%s).\n" \
                       "S'il veut en profiter, il lui faudra éventuellement réadhérer. Continuer ?" % (t_end_conn, t_end_adh), title="Prolongement de connexon"):
                        raise Continue(cancel_cont)
            # On édite une facture existante
            if facture:
                with self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] as facture:
                    if mois:
                        facture["finConnexion"] = unicode(new_finconnexion)
                        facture["debutConnexion"] = unicode(new_debutconnexion)
                        facture["article"].append(config.cotisation.dico_cotis(mois))
                    if self.confirm_item(item=facture,
                      text=u"Le paiement de %sEUR a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), facture['modePaiement'][0]),
                      title=u"Validation du paiement",
                      timeout=self.timeout):
                        # Appeler créditer va créditer ou débiter le solde, sauver le proprio et crée
                        adherent = self.adherent_adhesion_connexion_crediter(facture, adherent)
                    else:
                        if not self.confirm(text=u"Le paiement n'a pas été reçue.\n Annuler ?", title="Annulation de l'adhésion", defaultno=True):
                            raise Continue(cancel_cont)
                        else:
                            facture.delete()

            else:
                if tag_paiment is None or comment is None:
                    raise ValueError("Il faut définir une méthode de paiement avec un commentaire")
                if not mois:
                    raise ValueError("Il faut prolonger la connexion d'un nombre de mois strictement positif")
                with self.conn.newFacture(adherent.dn, {}) as facture:
                    facture['modePaiement'] = unicode(tag_paiment, 'utf-8')
                    facture['article'].append(config.cotisation.dico_cotis(mois))
                    facture['info'] = unicode(comment, 'utf-8')
                    facture["finConnexion"] = unicode(new_finconnexion)
                    facture["debutConnexion"] = unicode(new_debutconnexion)
                    if self.confirm_item(item=facture,
                      text=u"Le paiement de %sEUR a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), tag_paiment),
                      title=u"Validation du paiement",
                      timeout=self.timeout):
                        # Appeler créditer va créditer ou débiter le solde, sauver le proprio et crée
                        adherent = self.adherent_adhesion_connexion_crediter(facture, adherent)
                    else:
                        if not self.confirm(text=u"Le paiement n'a pas été reçue.\n Annuler ?", title="Annulation de l'adhésion", defaultno=True):
                            raise Continue(cancel_cont)
            raise Continue(cont(adherent=adherent))

        def todo_mois(tag, self_cont):
            if tag == 'An':
                mois = 12
            elif tag == 'NC':
                mois = 0
            else:
                mois = int(tag.split(' ', 1)[0])
            raise Continue(self_cont(mois=mois, default_item=tag))

        self_cont = TailCall(self.adherent_connexion, cont=cont, adherent=adherent, cancel_cont=cancel_cont,
                      facture=facture, mois=mois, default_item=default_item, tag_paiment=tag_paiment,
                      comment_paiement=comment_paiement)

        finadhesion = adherent.fin_adhesion()
        # Si on édite une facture, on prolonge la date de finadhesion
        if facture and facture["finAdhesion"]:
            finadhesion = max(finadhesion, facture["finAdhesion"][0])
        finconnexion = adherent.fin_connexion()

        # Si l'adhésion fini avant la connexion
        if finadhesion <= time.time() or finadhesion <= finconnexion:
            if finadhesion:
                t_end_adh = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(finadhesion))
                # Si l'adhésion est déjà fini
                if finadhesion <= time.time():
                    self.dialog.msgbox(text=u"L'adhésion a expiré le %s, il va falloir réadhérer d'abord" % t_end_adh, title="Réadhésion nécessaire", width=0, height=0, timeout=self.timeout)
                # Sinon si elle fini avant la fin de la connexion courante
                elif finadhesion < finconnexion:
                    t_end_conn = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(finconnexion))
                    self.dialog.msgbox(text=u"L'adhésion de termine le %s, avant la fin de la connexion le %s, il va falloir réadhérer d'abord" % (t_end_adh, t_end_conn), title="Réadhésion nécessaire", width=0, height=0, timeout=self.timeout)
            # Échouera si on essaie de prolonger la connexion au dela de l'adhésion et que l'adhésion est encore valable plus de quinze jours
            return self.adherent_adhesion(cont=self_cont, cancel_cont=cont, adherent=adherent, crediter=False)

        # Si on édite une facture, elle vient actuellement forcement de adherent_adhesion
        if facture and cancel_cont is None:
            cancel_cont = TailCall(self.adherent_adhesion, cont=self_cont, adherent=adherent, cancel_cont=cont, tag_paiment=str(facture['modePaiement'][0]) if facture['modePaiement'] else None, comment_paiement=None, crediter=False, facture=facture)
            self_cont(cancel_cont=cancel_cont)

        # On choisi le nombre de mois pour prolonger la connexion
        if mois is None:
            (code, tag) = self.handle_dialog(cont, box, finconnexion, default_item)
            return self.handle_dialog_result(
                    code=code,
                    output=[],
                    cancel_cont=cancel_cont if cancel_cont else cont,
                    error_cont=self_cont,
                    codes_todo=[([self.dialog.DIALOG_OK], todo_mois, [tag, self_cont])]
                )
        # Si on connait le moyen de paiement (il peut être a l'intérieure de la facture existante)
        elif tag_paiment or facture:
            lcont = self_cont.copy()
            if facture:
                lcont(mois=None)
            else:
                lcont(tag_paiment=None)
            return self.handle_dialog_result(
                    code=self.dialog.DIALOG_OK,
                    output=[],
                    cancel_cont=lcont,
                    error_cont=lcont,
                    codes_todo=[([self.dialog.DIALOG_OK], todo, [adherent, mois, finadhesion, finconnexion, lcont, cont, facture, tag_paiment, comment_paiement])]
                )
        # Sinon, il faut choisir une méthode de paiement
        else:
            lcont = self_cont.copy()
            lcont(mois=None)
            return self.proprio_choose_paiement(proprio=adherent, cont=self_cont, cancel_cont=lcont)
        return cont

    def adherent_carte_etudiant(self, cont, adherent, values={}, cancel_cont=None):
        # Dictionnaire décrivant quelle est la valeur booléenne à donner à l'absence de l'attribut
        a = attributs
        choices = []
        if self.has_right(a.tresorier, adherent) or not adherent.carte_controle():
            choices.append((a.carteEtudiant.ldap_name, "Carte étudiant présentée", 1 if adherent[a.carteEtudiant.ldap_name] or values.get(a.carteEtudiant.ldap_name, False) else 0))
        if self.has_right(a.tresorier, adherent):
            choices.append(("controleCarte", "La carte a-t-elle été controlée", 1 if adherent.carte_controle() or values.get("controleCarte", False) else 0))

        if not choices:
            self.dialog.msgbox("Carte d'étudiant déjà validée et non modifiable", title="Gestion de la carte étudiant", width=0, height=0)
            if cancel_cont:
                cancel_cont(cont=cont)
            try:
                cont(cancel_cont=cancel_cont)
            except TypeError:
                pass
            raise Continue(cont)

        def box():
            return self.dialog.checklist("Gestion de la carte étudiant",
                            height=0,
                            width=0,
                            timeout=self.timeout,
                            list_height=7,
                            choices=choices,
                            title="Gestion de la carte étudiant")

        def todo(values, adherent, cont):
            # On met à jour chaque attribut si sa valeur à changé
            with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
                # Si on est trésorier et que controleCarte a changer on enregistre le changement
                if self.has_right(a.tresorier, adherent) and values["controleCarte"] and not adherent.carte_controle():
                    if adherent["controle"]:
                        adherent["controle"] = u"c%s" % adherent["controle"][0]
                    else:
                        adherent["controle"] = u"c"
                elif self.has_right(a.tresorier, adherent) and not values["controleCarte"] and adherent.carte_controle():
                    adherent["controle"] = unicode(adherent["controle"][0]).replace('c','')
                    if not adherent["controle"][0]:
                        adherent["controle"] = []
                # Si la carte n'est pas validé ou qu'on est trésorier, on sauvegarde les changements
                if values[a.carteEtudiant.ldap_name] and not adherent[a.carteEtudiant.ldap_name] and (not adherent.carte_controle() or self.has_right(a.tresorier, adherent)):
                    adherent[a.carteEtudiant.ldap_name] = u"TRUE"
                elif not values[a.carteEtudiant.ldap_name] and adherent[a.carteEtudiant.ldap_name] and (not adherent.carte_controle() or self.has_right(a.tresorier, adherent)):
                    adherent[a.carteEtudiant.ldap_name] = []
                    if adherent["controle"]:
                        adherent["controle"] = unicode(adherent["controle"][0]).replace('c','')
                        if not adherent["controle"][0]:
                            adherent["controle"] = []
                adherent.validate_changes()
                adherent.history_gen()
                adherent.save()
            # On s'en va en mettant à jour dans la continuation la valeur de obj
            raise Continue(cont(adherent=adherent))

        (code, output) = self.handle_dialog(cont, box)
        # On transforme la liste des cases dialog cochée en dictionnnaire
        values = dict((a[0], a[0] in output) for a in choices)

        # Une continuation que l'on suivra si quelque chose se passe mal
        retry_cont = TailCall(self.adherent_carte_etudiant, adherent=adherent, cont=cont, values=values)

        return self.handle_dialog_result(
            code=code,
            output=output,
            cancel_cont=cancel_cont if cancel_cont else cont,
            error_cont=retry_cont,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [values, adherent, cont])]
        )
    def adherent_charte(self, cont, adherent):
        a = attributs
        attribs = [a.charteMA]
        return self.edit_boolean_attributs(
                    obj=adherent,
                    attribs=attribs,
                    title="Signature de la charte membre actif de %s %s" % (adherent['prenom'][0], adherent['nom'][0]),
                    update_obj='adherent',
                    cont=cont)

    def adherent_personnel(self, cont, adherent=None,  fields_attrs={}, make_compte_crans=None, force_create=False):
        """
        Permet d'éditer les nom, prénom et téléphone d'un adhérent, ou de créer un adhérent.
        Il faut encore trouver un moyen de récupérer des valeurs pour les attributs mail et chbre
        """
        a = attributs
        # Quel sont les attributs ldap dont on veut afficher et
        # la taille du champs d'édition correspondant
        to_display = [(a.nom, 30), (a.prenom, 30), (a.tel, 30), (a.mail, 30)]
        non_empty = [a.nom, a.prenom, a.tel]
        input_type = {'default':0}

        # Quel séparateur on utilise pour les champs multivalué
        separateur = ' '

        def box(make_compte_crans):
            if force_create and adherent is None and fields_attrs and make_compte_crans is not None:
                return (self.dialog.DIALOG_OK, [fields_attrs[a] for (a, l) in to_display], make_compte_crans)
            if adherent:
                attrs = dict((k, [str(a) for a in at]) for (k, at) in adherent.items())
                if 'cransAccount' in adherent['objectClass']:
                    input_type[attributs.mail] = 2
                    to_display.append((attributs.mailExt, 30))
            else:
                attrs = {}
                if make_compte_crans is None:
                    if self.dialog.yesno("Crééra-t-on un compte crans à l'utilisateur ?", timeout=self.timeout, title="Création d'un adhérent", width=50) == self.dialog.DIALOG_OK:
                        input_type[attributs.mail] = 2
                        make_compte_crans = True
                        to_display.append((attributs.mailExt, 30))
                    else:
                        make_compte_crans = False

            fields = [(
             "%s %s:" % (a.legend, '(optionnel) ' if a.optional else ''),
             fields_attrs.get(a, separateur.join(attrs.get(a.ldap_name, [a.default] if a.default else []))),
             l+1, l,
             input_type.get(a, input_type['default'])
            ) for a,l in to_display]

            (code, tags) = self.dialog.form(
                    text="",
                    timeout=self.timeout,
                    height=0, width=0, form_height=0,
                    fields=fields,
                    title="Création d'un adhérent" if adherent is None else "Édition des informations de %s %s" % (adherent['prenom'][0], adherent['nom'][0]),
                    backtitle="Gestion des adhérents du Crans")
            return (code, tags, make_compte_crans)

        def modif_adherent(adherent, attrs):
            with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
                for (key, values) in attrs.items():
                    adherent[key] = values
                adherent.validate_changes()
                adherent.history_gen()
                adherent.save()
                return adherent

        def create_adherent(attrs, make_compte_crans, force_create, self_cont, cont):
            if not force_create:
                items = self.conn.search("(&(prenom=%s)(nom=%s))" % (attrs['prenom'], attrs['nom']))
                if items:
                    newadherent = self.select_one(items, title="Choisir un adhérant existant", text="Des adhérent avec les même noms et prénoms existent déjà, en utiliser un ?\n(Annuler pour continuer la création)", cont=self_cont(make_compte_crans=make_compte_crans, force_create=True))
                    raise Continue(cont(adherent=newadherent))
            with self.conn.newAdherent({}) as adherent:
                delay = {}
                for (key, values) in attrs.items():
                    try:
                        adherent[key] = values
                    # En cas d'erreur, on a peut être besoin du compte crans
                    except ValueError:
                        delay[key] = values
                print delay
                # on récupère la chambre
                adherent = self.adherent_chambre_campus(success_cont=None, cont=self_cont(make_compte_crans=make_compte_crans), adherent=adherent, create=True)
                # Si c'est EXT, on demande une adresse complète
                if 'EXT' in adherent['chbre']:
                    adherent = self.adherent_chambre_ext(keep_machine=True, keep_compte=True, success_cont=None, cont=self_cont(make_compte_crans=make_compte_crans), adherent=adherent, create=True)
                # Si compte crans à créer, on le crée.
                # On le met en dernier pour éviter de faire entrez plusieurs fois son mdp à l'adhérent
                # en cas d'erreur de la part du cableur
                if make_compte_crans:
                    adherent = self.proprio_compte_create(proprio=adherent, cont=self_cont(make_compte_crans=None, force_create=False, adherent=None), update_obj='adherent', return_obj=True)
                # On réeaffecte les attributs de tout à l'heure
                for (key, values) in delay.items():
                    adherent[key] = values
                # On confirme la création
                if self.confirm_item(adherent, title="Créer l'adhérent suivant ?"):
                    adherent.validate_changes()
                    adherent.create()
                else:
                    adherent = None
            return adherent

        def todo(to_display, non_empty, tags, adherent, separateur, make_compte_crans, force_create, self_cont, cont):
            attrs = {}
            # On traite les valeurs reçues
            for ((a, l), values) in zip(to_display, tags):
                if not values and a in non_empty:
                    raise ValueError(u"%s ne devrait pas être vide" % a.legend)
                values = unicode(values, 'utf-8')
                # Si le champs n'est pas single value, on utilise separateur pour découper
                # et on ne garde que les valeurs non vides
                if not a.singlevalue:
                    values = [v for v in values.split(separateur) if v]
                attrs[a.ldap_name] = values
            if adherent:
                adherent = modif_adherent(adherent, attrs)
            else:
                adherent = create_adherent(attrs, make_compte_crans, force_create, self_cont, cont)
            raise Continue(cont(adherent=adherent))


        (code, tags, make_compte_crans) = self.handle_dialog(cont, box, make_compte_crans)

        # On prépare les fiels à afficher à l'utilisateur si une erreure à lieu
        # pendant le traitement des donnée (on n'éfface pas ce qui a déjà été entré
        # c'est au cableur de corriger ou d'annuler
        fields_attrs = dict((a, values) for ((a, l), values) in zip(to_display, tags))
        retry_cont = TailCall(self.adherent_personnel, adherent=adherent, cont=cont,  fields_attrs=fields_attrs)

        return self.handle_dialog_result(
            code=code,
            output=tags,
            cancel_cont=cont,
            error_cont=retry_cont,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [to_display, non_empty, tags, adherent, separateur, make_compte_crans, force_create, retry_cont, cont])]
        )

    @tailcaller
    def adherent_chambre_campus(self, success_cont, cont, adherent, create=False):
        """Permet de faire déménager d'adhérent sur le campus"""
        def box():
            return self.dialog.inputbox(
                    "chambre ?",
                    title="%s de %s %s" % ("Création" if create else "Déménagement", adherent['prenom'][0], adherent["nom"][0]),
                    cancel_label="Retour",
                    width=50, timeout=self.timeout,
                   )

        def expulse_squatteur(adherent, chbre):
            with self.conn.search(u"chbre=%s" % chbre, mode='rw')[0] as squatteur:
                if self.confirm_item(
                  item=squatteur,
                  title="Chambre occupée",
                  defaultno=True,
                  text=u"L'adhérent ci-dessous occupé déjà la chambre %s :\n" % output,
                  text_bottom=u"\nPasser la chambre de cet adhérent en chambre inconnue ?"
                  ):
                    squatteur['chbre'] = u'????'
                    squatteur.validate_changes()
                    squatteur.history_gen()
                    squatteur.save()
                    return True
                else:
                    return False

        def set_chambre(adherent, chbre):
            try:
                adherent['postalAddress'] = []
                adherent['chbre'] = unicode(output, 'utf-8')
            except UniquenessError:
                if expulse_squatteur(adherent, chbre):
                    # La chambre est maintenant normalement libre
                    adherent['chbre'] = unicode(output, 'utf-8')
                else:
                    raise Continue(self_cont)
            return adherent

        def todo(chbre, adherent, self_cont, success_cont):
            if not output:
                raise Continue(self_cont)
            if output == "????":
                raise ValueError("Chambre ???? invalide")
            if create:
                return set_chambre(adherent, chbre)
            else:
                with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
                    adherent = set_chambre(adherent, chbre)
                    adherent.validate_changes()
                    adherent.history_gen()
                    adherent.save()
                self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % output)
                raise Continue(success_cont(adherent=adherent))

        (code, output) = self.handle_dialog(cont, box)
        self_cont = TailCall(self.adherent_chambre_campus, adherent=adherent, success_cont=success_cont, cont=cont, create=create)
        return self.handle_dialog_result(
            code=code,
            output=output,
            cancel_cont=cont,
            error_cont=self_cont,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [output, adherent, self_cont, success_cont])]
        )

    @tailcaller
    def adherent_chambre_ext(self, keep_machine, keep_compte, success_cont, cont, adherent, create=False):
        """Permet de faire déménager l'adhérent hors campus"""
        if keep_machine and not keep_compte:
            raise EnvironmentError("On ne devrait pas supprimer un compte crans et y laisser des machines")
        elif keep_machine and keep_compte:
            def box(values={}):
                form = [("Adresse", 40), ("Compl. adr.", 40), ("Code postal", 7), ("Ville", 16)]
                fields = [("%s :" % k, values.get(k, ""), l, 50) for (k, l) in form]
                return self.dialog.form(
                    text="",
                    timeout=self.timeout,
                    height=0, width=0, form_height=0,
                    fields=fields,
                    title="Paramètres machine",
                    backtitle="Gestion des machines du Crans")
            def todo(output, adherent, success_cont, cont):
                if not create:
                    if self.dialog.yesno("changer l'adresse de l'adhérent pour %s ?" % ", ".join([o for o in output if o]),
                           title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                           defaultno=True, timeout=self.timeout) == self.dialog.DIALOG_OK:
                        with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
                            adherent['postalAddress'] = [unicode(pa, 'utf-8') for pa in output]
                            adherent['chbre'] = u'EXT'
                            adherent.validate_changes()
                            adherent.history_gen()
                            adherent.save()
                            self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines conservées")
                            raise Continue(success_cont(adherent=adherent))
                    else:
                        raise Continue(cont)
                else:
                    adherent['postalAddress'] = [unicode(pa, 'utf-8') for pa in output]
                    adherent['chbre'] = u'EXT'
                    return adherent
        elif not keep_machine and keep_compte:
            if create:
                raise EnvironmentError("On ne crée pas un adhérent en lui supprimant des machines")
            def box(values={}):
                return (self.dialog.DIALOG_OK, "")
            def todo(output, adherent, success_cont, cont):
                if self.confirm_item(
                    item=adherent,
                    text=u"Supprimer toutes les machines de l'adhérent ci-dessous ?\n\n",
                    text_bottom=u"\nLa chambre de l'adhérent passera de plus en EXT.",
                    title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                    defaultno=True):
                    with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
                        for machine in adherent.machines():
                            with machine:
                                machine.delete()
                        adherent['chbre'] = u'EXT'
                        adherent.validate_changes()
                        adherent.history_gen()
                        adherent.save()
                    self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines supprimées")
                    raise Continue(success_cont(adherent=adherent))
                else:
                    raise Continue(cont)
        elif not keep_machine and not keep_compte:
            if create:
                raise EnvironmentError("On ne crée pas un adhérent en lui supprimant des machines et un compte")
            def box(values={}):
                return (self.dialog.DIALOG_OK, "")
            def todo(output, adherent, success_cont, cont):
                if adherent.get('solde', [0])[0] > 0:
                    self.dialog.msgbox("Solde de l'adhérent %s€ strictement positif, impossible de supprimer le compte\nRepasser le solde à 0€ pour supprimer le compte." % adherent.get('solde', [0])[0],
                        title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                        width=50, timeout=self.timeout)
                    raise Continue(cont)
                elif self.confirm_item(
                    item=adherent,
                    text=u"Supprimer toutes les machines et du compte crans de l'adhérent ci-dessous ?\n\n",
                    text_bottom=u"\nLa chambre de l'adhérent passera de plus en EXT.",
                    title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                    defaultno=True):
                    with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
                        for machine in adherent.machines():
                            with machine:
                                machine.delete()
                        adherent['chbre'] = u'EXT'
                        adherent.validate_changes()
                        adherent.history_gen()
                        adherent.save()
                    # On supprime le compte crans (on essaye)
                    def post_deletion(proprio, cont):
                        if not "cransAccount" in proprio['objectClass']:
                            self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines et compte crans supprimées")
                        else:
                            self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines supprimées et compte crans concervé")
                        raise Continue(cont(adherent=proprio))
                    self.proprio_compte_delete(proprio=adherent, cont=TailCall(post_deletion, proprio=adherent, cont=success_cont(adherent=adherent)), force=True)
                else:
                    raise Continue(cont)
        else:
            raise EnvironmentError("Impossible, on a fait tous les cas, python est buggué")

        (code, output) = self.handle_dialog(cont, box)
        self_cont = TailCall(self.adherent_chambre_ext, adherent=adherent, keep_machine=keep_machine, keep_compte=keep_compte, success_cont=success_cont, cont=cont)
        return self.handle_dialog_result(
            code=code,
            output=output,
            cancel_cont=cont,
            error_cont=self_cont,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [output, adherent, success_cont, cont])]
        )

    def adherent_chambre(self, adherent, cont, default_item=None):
        """Permet de faire déménager d'adhérent"""
        a = attributs
        menu_droits = {
          '0' : [a.cableur, a.nounou],
          '1' : [a.cableur, a.nounou],
          '2' : [a.cableur, a.nounou],
          '3' : [a.cableur, a.nounou],
        }
        menu = {
         "0": {'text':"Déménagement sur le campus", 'callback':self.adherent_chambre_campus, 'help': "Déménagement vers une chambre sur le campus, on ne demande que le bâtiment et la chambre"},
         "1": {'text':"Déménagement à l'extérieur en conservant les machines", "callback": TailCall(self.adherent_chambre_ext, keep_machine=True, keep_compte=True), "help": "Pour concerver ses machines, il faut donner un adresse postale complète"},
         "2": {'text':"Départ du campus en conservant son compte", "callback":TailCall(self.adherent_chambre_ext, keep_machine=False, keep_compte=True), "help":"Supprime les machines mais concerve le compte crans, l'adresse passe en EXT sans plus d'information"},
         "3": {'text':"Départ du campus en supprimant son compte", "callback":TailCall(self.adherent_chambre_ext, keep_machine=False, keep_compte=False), "help":"Supprime les machines et le compte crans, l'adhérent reste dans la base de donnée, il est possible de mettre une redirection du mail crans vers une autre adresse dans bcfg2"},
        }
        menu_order = [str(i) for i in range(4)]
        def box(default_item=None):
            return self.dialog.menu(
                "Quel est le type du déménagement ?",
                width=0,
                height=0,
                menu_height=0,
                timeout=self.timeout,
                item_help=1,
                default_item=str(default_item),
                title="Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                scrollbar=True,
                cancel_label="Retour",
                backtitle=self._connected_as(),
                choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], adherent)])

        def todo(tag, menu, adherent, self_cont, cont):
            if not tag in menu_order:
                raise Continue(self_cont)
            else:
                raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, success_cont=cont, adherent=adherent))

        (code, tag) = self.handle_dialog(cont, box, default_item)
        self_cont = TailCall(self.adherent_chambre, adherent=adherent, cont=cont, default_item=tag)
        return self.handle_dialog_result(
            code=code,
            output=tag,
            cancel_cont=cont,
            error_cont=self_cont,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, self_cont, cont])]
        )

    def create_adherent(self, cont):
        """Crée un adhérent et potentiellement son compte crans avec lui"""
        def mycont(adherent=None, **kwargs):
            if adherent:
                # Une fois l'adhérent créé, on vois s'il donne sa carte étudiant et s'il adhére/prend la connexion internet
                #adh_cont = TailCall(self.modif_adherent, cont=cont, adherent=adherent)
                conn_cont = TailCall(self.adherent_connexion, cont=cont(proprio=adherent), adherent=adherent)
                carte_cont = TailCall(self.adherent_carte_etudiant, cont=conn_cont, adherent=adherent)
                etude_cont = TailCall(self.adherent_etudes, cont=carte_cont, adherent=adherent)
                etude_cont(cancel_cont=etude_cont)
                carte_cont(cancel_cont=etude_cont)
                # Comme on crée une facture, pas de retour possible
                conn_cont(cancel_cont=conn_cont)
                raise Continue(etude_cont)
            else:
                raise Continue(cont)
        return self.adherent_personnel(cont=TailCall(mycont))

    def delete_adherent(self, cont, del_cont, adherent=None):
        """Permet la suppression d'un adhérent de la base ldap"""
        if adherent is None:
            adherent = self.select(["adherent"], "Recherche d'un adhérent pour supression", cont=cont)

        def todo(adherent):
            if self.confirm_item(item=adherent, title="Voulez vous vraiement supprimer l'adhérent ?", defaultno=True):
                with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
                    adherent.delete()
                self.dialog.msgbox("L'adherent a bien été supprimée", timeout=self.timeout, title="Suppression d'un adherent")
                raise Continue(del_cont(proprio=None))
            else:
                raise Continue(cont)

        return self.handle_dialog_result(
            code=self.dialog.DIALOG_OK,
            output="",
            cancel_cont=cont(adherent=adherent),
            error_cont=cont(adherent=adherent),
            codes_todo=[([self.dialog.DIALOG_OK], todo, [adherent])]
        )

    def adherent_droits(self, adherent, cont, choices_values=None):
        """Gestion des droits d'un adhérent"""
        def box():
            return self.dialog.checklist(
                     text="",
                     height=0, width=0, list_height=0,
                     choices=choices_values if choices_values else [(droit, "", 1 if droit in adherent["droits"] else 0) for droit in attributs.TOUS_DROITS],
                     title="Droits de %s %s" % (adherent['prenom'][0], adherent['nom'][0]),
                     timeout=self.timeout
                     )
        def todo(droits, adherent, self_cont, cont):
            # Les vérifications de sécurité sont faites dans lc_ldap
            with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
                adherent['droits'] = [unicode(d) for d in droits]
                adherent.validate_changes()
                adherent.history_gen()
                adherent.save()
                if adherent["uid"] and adherent["uid"][0] == self.conn.current_login:
                    self.check_ldap()
            raise Continue(cont(adherent=adherent))

        (code, droits) = self.handle_dialog(cont, box)
        self_cont = TailCall(self.adherent_droits, adherent=adherent, cont=cont, choices_values=[(d, "", 1 if d in droits else 0) for d in attributs.TOUS_DROITS])
        return self.handle_dialog_result(
            code=code,
            output=droits,
            cancel_cont=cont,
            error_cont=self_cont,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [droits, adherent, self_cont, cont])]
        )


    def adherent_etudes(self, adherent, cont, cancel_cont=None, default_item=None, etablissement=None, annee=None, section=None):
        """Gestion des études de l'adhérent. etudes est un triplet (établissement, année, section)"""
        choices_etablissement = [
          ('Autre', ''),
          ('ENS', 'École Normale Supérieure'),
          ('IUT Cachan', ''),
          ('Maximilien Sorre', ''),
          ('Gustave Eiffel', ''),
          ('EFREI', ''),
          ('ESTP', ''),
          ('P1', 'Université Panthéon Sorbonne'),
          ('P2', 'Université Panthéon Assas'),
          ('P3', 'Université de la Sorbonne Nouvelle'),
          ('P4', 'Université Paris Sorbonne'),
          ('P5', 'Université René Descartes'),
          ('P6', 'Université Pierre et Marie Curie'),
          ('P7', 'Université Paris Diderot'),
          ('P8', 'Université Vincennes Saint Denis'),
          ('P9', 'Université Paris Dauphine'),
          ('P10', 'Université de Nanterre'),
          ('P11', 'Université de Paris Sud (Orsay)'),
          ('P12', 'Université Val de Marne (Créteil)'),
          ('P13', 'Université Paris Nord'),
          ('IUFM', ''),
          ('Personnel ENS', "appartement ENS ou personnel CROUS"),
        ]
        LMD = ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7', 'P8', 'P9', 'P10', 'P11', 'P12', 'P13']
        LM = ['EFREI']
        LYCEE = ['Maximilien Sorre', 'Gustave Eiffel']
        ANNEE = ['IUT Cachan', 'ESTP']

        def choices(max_annee):
            c = []
            if max_annee >= 1:
                c.append(('1', '1ère année'))
            for i in range(2, max_annee+1):
                c.append((str(i), "%sème année" % i))
            c.append(('Autre', ''))
            return c
        choices_LMD = [
          ('L1', 'Licence 1ère année'),
          ('L2', 'Licence 2ième année'),
          ('L3', 'Licence 3ième année'),
          ('M1', 'Master 1ère année'),
          ('M2', 'Master 2ième année'),
          ('D1', '1ère année de thèse'),
          ('D2', '2ième année de thèse'),
          ('D3', '3ième année de thèse'),
          ('Autre', ''),
        ]
        choices_LM = choices_LMD[:5] + choices_LMD[-1:]
        choices_ENS = [
            ('1', 'Licence'),
            ('2', 'Master 1'),
            ('3', 'Agrégation'),
            ('4', 'Master 2'),
            ('5', '1ère année de thèse'),
            ('6', '2ième année de thèse'),
            ('7', '3ième année de thèse'),
            ('Autre', ''),
        ]
        section_ENS = [
           ('A0', 'Informatique'),
           ('A1', 'Mathématiques'),
           ('A2', 'Physique fondamentale'),
           ("A'2", 'Physique appliquée'),
           ("A''2", 'Chimie'),
           ('A3', 'Biochimie'),
           ('B1', 'Mécanique'),
           ('B2', 'Génie civil'),
           ('B3', 'Génie mécanique'),
           ('B4', 'Génie électrique'),
           ('C', 'Art et création industrielle'),
           ('D2', 'Economie gestion'),
           ('D3', 'Sciences sociales'),
           ('E', 'Anglais'),
           ('Autre', ''),
        ]
        choices_LYCEE = [
          ('Seconde',''),
          ('Première',''),
          ('Terminale',''),
          ('1/2', '1ère année de prépa'),
          ('3/2', '2nd année de prépa'),
          ('5/2', '3ième année de prépa'),
          ('Autre',''),
        ]

        def box_etablissement(default_item):
            if etablissement == 'Autre' or \
             (etablissement is not None and etablissement not in [i[0] for i in choices_etablissement]) or \
             (default_item is not None and default_item not in [i[0] for i in choices_etablissement]):
                return self.dialog.inputbox(
                  text="Choisissez l'établissement :",
                  title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                  timeout=self.timeout,
                  init=str(default_item) if default_item else "")
            else:
                return self.dialog.menu(
                  "Choisissez l'établissement :",
                  width=0,
                  height=0,
                  menu_height=0,
                  timeout=self.timeout,
                  item_help=0,
                  default_item=str(default_item) if default_item else 'ENS',
                  title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                  scrollbar=True,
                  cancel_label="Retour",
                  backtitle=self._connected_as(),
                  choices=choices_etablissement)

        def box_annee(default_item):
            if etablissement in LMD:
                box_choice = choices_LMD
            elif etablissement in LM:
                box_choice = choices_LM
            elif etablissement in LYCEE:
                box_choice = choices_LYCEE
            elif etablissement == 'ENS':
                box_choice = choices_ENS
            else:
                box_choice = choices(7)
            if not box_choice or annee == 'Autre' or \
             (annee is not None and annee not in [i[0] for i in box_choice]) or \
             (default_item is not None and default_item not in [i[0] for i in box_choice]):
                return self.dialog.inputbox(
                  text="Choisissez l'année administrative :",
                  title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                  timeout=self.timeout,
                  init=str(default_item) if default_item else "")
            else:
                return self.dialog.menu(
                  "Choisissez l'année administrative :",
                  width=0,
                  height=0,
                  menu_height=0,
                  timeout=self.timeout,
                  item_help=0,
                  default_item=str(default_item),
                  title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                  scrollbar=True,
                  cancel_label="Retour",
                  backtitle=self._connected_as(),
                  choices=box_choice)

        def box_section(default_item):
            if etablissement != 'ENS' or section == 'Autre' or \
              (section and section not in [i[0] for i in section_ENS]) or \
              (default_item is not None and default_item not in [i[0] for i in section_ENS]):
                return self.dialog.inputbox(
                  text="Choisissez la section :",
                  title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                  timeout=self.timeout,
                  init=str(default_item) if default_item else "")
            else:
                return self.dialog.menu(
                  "Choisissez la section :",
                  width=0,
                  height=0,
                  menu_height=0,
                  timeout=self.timeout,
                  item_help=0,
                  default_item=str(default_item),
                  title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                  scrollbar=True,
                  cancel_label="Retour",
                  backtitle=self._connected_as(),
                  choices=section_ENS)

        self_cont = TailCall(self.adherent_etudes, adherent=adherent, cont=cont, cancel_cont=cancel_cont, default_item=default_item, etablissement=etablissement, annee=annee, section=section)
        if etablissement is None or etablissement == 'Autre':
            if not default_item and adherent["etudes"]:
                default_item = str(adherent["etudes"][0])
            lcont = cancel_cont if cancel_cont else cont
            (code, etablissement) = self.handle_dialog(cancel_cont, box_etablissement, default_item)
            output = etablissement
            self_cont(default_item=etablissement)
        elif annee is None or annee == 'Autre':
            if not default_item and adherent["etudes"]:
                default_item = str(adherent["etudes"][1])
            print default_item
            lcont = TailCall(self.adherent_etudes, adherent=adherent, cont=cont, cancel_cont=cancel_cont, default_item=etablissement, etablissement=None, annee=None, section=None)
            (code , annee) = self.handle_dialog(lcont, box_annee, default_item)
            output = annee
            self_cont(default_item=annee)
        elif section is None or section == 'Autre':
            if not default_item and adherent["etudes"]:
                default_item = str(adherent["etudes"][2])
            lcont = TailCall(self.adherent_etudes, adherent=adherent, cont=cont, cancel_cont=cancel_cont, default_item=annee, etablissement=etablissement, annee=None, section=None)
            (code, section) = self.handle_dialog(lcont, box_section, default_item)
            output = section
            self_cont(default_item=section)
        else:
            output = ""
            lcont = TailCall(self.adherent_etudes, adherent=adherent, cont=cont, cancel_cont=cancel_cont, default_item=section, etablissement=etablissement, annee=annee, section=None)

        def todo(etablissement, annee, section, adherent, self_cont, cont):
            if etablissement is None or etablissement == 'Autre':
                raise Continue(self_cont(default_item=None, etablissement=etablissement))
            elif annee is None or annee == 'Autre':
                raise Continue(self_cont(default_item=None, etablissement=etablissement, annee=annee))
            elif section is None or section == 'Autre':
                raise Continue(self_cont(default_item=None, etablissement=etablissement, annee=annee, section=section))
            else:
                if not adherent["etudes"] or adherent["etudes"][0] != etablissement or adherent["etudes"][1] != annee or adherent["etudes"][2] != section:
                    with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
                        adherent["etudes"] = [unicode(etablissement), unicode(annee), unicode(section)]
                        adherent.validate_changes()
                        adherent.history_gen()
                        adherent.save()
                raise Continue(cont(adherent=adherent))
        return self.handle_dialog_result(
            code=code,
            output=output,
            cancel_cont=lcont,
            error_cont=self_cont,
            codes_todo=[([self.dialog.DIALOG_OK], todo, [etablissement, annee, section, adherent, self_cont, cont])]
        )
