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

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

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

import lc_ldap.objets as objets
import lc_ldap.attributs as attributs

import certificat
import blacklist
from CPS import TailCall, tailcaller, Continue

class Dialog(certificat.Dialog, blacklist.Dialog):
    def machine_information(self, cont, machine=None, objectClass=None, proprio=None, realm=None, fields_values=None):
        """
        Permet de modifier une machine si elle est fournit par le paramètre machine
        sinon, crée une machine à partir de proprio, objectClass et realm.
        Si on ne fait qu'éditer une machine, proprio, objectClass et realm sont ignoré
        D'une machinère générale, il faudrait mettre ici tous les attributs single value
        et les multivalué que l'on peut simplement représenter de façon textuelle avec
        un séparateur.
        Pour le moment il y a :
            * host
            * macAddress
            * ipHostNumber
            * port(TCP|UDP)(in|out)
        """
        a = attributs
        # Quel sont les attributs ldap dont on veut afficher et la taille du champs d'édition correspondant
        to_display = [(a.host, 30), (a.macAddress, 17), (a.ipHostNumber, 15),
                      (a.portTCPout, 50), (a.portTCPin, 50), (a.portUDPout, 50),
                      (a.portUDPin, 50)
                     ]

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

        def box():
            if machine:
                attrs = dict((k,[str(a) for a in at]) for k,at in machine.items())
            else:
                attrs = {}

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

            return self.dialog.form(
                    text="",
                    timeout=self.timeout,
                    height=0, width=0, form_height=0,
                    fields=fields_values if fields_values else fields,
                    title="Paramètres machine",
                    backtitle="Gestion des machines du Crans")

        def check_host(host, objectClass):
            # Si c'est une machine wifi, host doit finir par wifi.crans.org
            if "machineWifi" == objectClass or 'borneWifi' == objectClass:
                hostend = ".wifi.crans.org"
            # Si c'est une machine wifi, host doit finir par crans.org
            elif "machineFixe" == objectClass:
                hostend = ".crans.org"
            # Si l'object class est machineCrans, pas de vérification
            elif "machineCrans" == objectClass:
                return host
            # Sinon, libre à chachun d'ajouter d'autres objectClass ou de filtrer
            # plus finement fonction des droits de self.conn.droits
            else:
                raise ValueError("La machine n'est ni une machine fixe, ni une machine wifi mais %s ?!?" % objectClass)

            if not host.endswith(hostend) and not '.' in host:
                host = "%s.wifi.crans.org" % host
            elif host.endswith(hostend) and '.' in host[:-len(hostend)]:
                raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend)
            elif not host.endswith(hostend) and '.' in host:
                raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend)

            return host

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

        def create_machine(proprio, realm, attrs):
            # Dans ce cas, on a besoin d'un proprio et d'un realm pour déterminer le rid
            if proprio is None or realm is None:
                raise EnvironmentError("On essaye de créer une machine mais proprio ou realm vaut None")
            ldif = {
                'macAddress': ['%s' % attrs['macAddress']],
                'host': ['%s' % attrs['host']]
            }
            with self.conn.newMachine(proprio.dn, realm, ldif) as machine:
                for (key, values) in attrs.items():
                    machine[key]=values
                if attributs.ipsec in machine.attribs:
                    machine[attributs.ipsec.ldap_name]=attributs.ipsec.default
                machine.validate_changes()
                if self.confirm_item(machine, "Voulez vous vraiement créer cette machine ?"):
                    machine.create()
                    self.display_item(machine, "La machine à bien été créée", ipsec=True)
                    return machine
                else:
                    raise Continue(cont)

        def todo(to_display, tags, objectClass, machine, proprio, realm, separateur, cont):
            attrs = {}
            # On traite les valeurs reçues
            for ((a,l),values) in zip(to_display, tags):
                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]
                # Pour host, on fait quelques vérification de syntaxe
                if a.ldap_name == 'host':
                    attrs[a.ldap_name]=check_host(values, objectClass)
                else:
                    attrs[a.ldap_name]=values
            # Soit on édite une machine existante
            if machine:
                machine = modif_machine(machine, attrs)
            # Soit on crée une nouvelle machine
            else:
                machine = create_machine(proprio, realm, attrs)
            raise Continue(cont(machine=machine))


        if machine:
            objectClass = machine["objectClass"][0]

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

        # 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_values = [("%s :" % a.legend, values, l) for ((a,l),values) in zip(to_display, tags)]
        retry_cont = TailCall(self.machine_information, machine=machine, cont=cont, objectClass=objectClass, proprio=proprio, realm=realm, fields_values=fields_values)

        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, tags, objectClass, machine, proprio, realm, separateur, cont])]
        )

    def modif_machine_blacklist(self, machine, cont):
        """Raccourci vers edit_blacklist spécifique aux machines"""
        return self.edit_blacklist(obj=machine, title="Éditions des blacklist de la machine %s" % machine['host'][0], update_obj='machine', cont=cont)



    def modif_machine_attributs(self, machine, attr, cont):
        """Juste un raccourci vers edit_attributs spécifique aux machines"""
        return self.edit_attributs(obj=machine, update_obj='machine', attr=attr, title="Modification de la machine %s" % machine['host'][0], cont=cont)

    def modif_machine_boolean(self, machine, cont):
        """Juste un raccourci vers edit_boolean_attributs spécifique aux machines"""
        a = attributs
        attribs = [a.dnsIpv6]
        return self.edit_boolean_attributs(
                    obj=machine,
                    attribs=attribs,
                    title="Édition des attributs booléen de la machine %s" % machine['host'][0],
                    update_obj='machine',
                    cont=cont)



    def modif_machine(self, cont, machine=None, tag=None):
        """
        Permet d'éditer une machine. Si fournie en paramètre on éditer en place,
        sinon, on en cherche une dans la base ldap
        """
        if machine is None:
            machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour modification", cont=cont)
        a = attributs
        menu_droits = {
          'Information' : [a.parent, a.cableur, a.nounou],
          'Autre': [a.parent, a.cableur, a.nounou],
          'Blackliste':[a.cableur, a.nounou],
          'Certificat': [a.parent, a.cableur, a.nounou],
          'Exemption' : [a.nounou],
          'Alias' : [a.parent, a.cableur, a.nounou],
          'Remarques' : [a.cableur, a.nounou],
          'SshKey' : [a.parent, a.nounou],
          'Supprimer' : [a.parent, a.cableur, a.nounou],
        }
        menu = {
            'Information' : {'text' : "Modifier le nom de machine, l'IP, adresse MAC", "callback":self.machine_information},
            'Autre'       : {'text' : "Modifier les attribut booléen comme dsnIpv6", "callback":self.modif_machine_boolean},
            'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_machine_blacklist},
            'Certificat' : {'text': 'Modifier les certificats de la machine', 'callback':self.modif_machine_certificat},
            'Exemption' : {'text':"Modifier la liste d'exemption d'upload de la machine", 'attribut':attributs.exempt},
            'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'attribut':attributs.hostAlias},
            'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info},
            'SshKey' : {'text':'Ajouter ou supprimer une clef ssh pour la machine', 'attribut':attributs.sshFingerprint},
            'Supprimer' : {'text':'Supprimer la machine', 'callback':self.delete_machine},
        }
        menu_order = ['Information', 'Blackliste', 'Certificat', 'Alias', 'Exemption', 'SshKey', 'Autre', 'Remarques', 'Supprimer']
        def box(default_item=None):
            return self.dialog.menu(
                "Que souhaitez vous modifier ?",
                width=0,
                height=0,
                menu_height=0,
                timeout=self.timeout,
                item_help=0,
                default_item=str(default_item),
                title="Modification de %s" % machine['host'][0],
                scrollbar=True,
                cancel_label="Retour",
                backtitle=self._connected_as(),
                choices=[(key,  menu[key]['text']) for key in menu_order if self.has_right(menu_droits[key], machine)])

        def todo(tag, menu, machine, cont_ret):
            if not tag in menu_order:
                raise Continue(cont_ret)
            else:
                if 'callback' in menu[tag]:
                    raise Continue(TailCall(menu[tag]['callback'], machine=machine, cont=cont_ret))
                elif 'attribut' in menu[tag]:
                    raise Continue(TailCall(self.modif_machine_attributs, machine=machine, 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_machine, cont=cont, machine=machine, tag=tag)

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

    def create_machine_proprio(self, cont, proprio, tag=None):
        """Permet d'ajouter une machine à un proprio (adherent, club ou AssociationCrans)"""
        a = attributs
        menu_droits = {
            'Fixe' : [a.soi, a.cableur, a.nounou],
            'Wifi' : [a.soi, a.cableur, a.nounou],
        }
        menu = {
            'Fixe' : {'text' : "Machine filaire", 'objectClass':'machineFixe', 'realm':'adherents'},
            'Wifi' : {'text': 'Machine sans fil', 'objectClass':'machineWifi', 'realm':'wifi-adh'},
        }
        menu_order = ['Fixe', 'Wifi']
        if isinstance(proprio, objets.AssociationCrans):
            menu_droits.update({
                'Fixe' : [a.nounou],
                'Wifi' : [a.nounou],
                'Adm' : [a.nounou],
            })
            menu.update({
                'Fixe' : {'text' : "Ajouter un serveur sur le vlan adherent", 'objectClass':'machineCrans', 'realm':'serveurs'},
                'Wifi' : {'text': 'Ajouter une borne WiFi sur le vlan wifi', 'objectClass':'borneWifi', 'realm':'bornes'},
                'Adm' : {'text' : "Ajouter un serveur sur le vlan adm", "objectClass":"machineCrans", 'realm':'adm'},
            })
            menu_order.append('Adm')
        def box(default_item=None):
            return self.dialog.menu(
                "Type de Machine ?",
                width=0,
                height=0,
                menu_height=0,
                item_help=0,
                timeout=self.timeout,
                default_item=str(default_item),
                title="Création de machines",
                scrollbar=True,
                cancel_label="Retour",
                backtitle=self._connected_as(),
                choices=[(key,  menu[key]['text']) for key in menu_order if self.has_right(menu_droits[key], proprio)])

        def todo(tag, menu, proprio, self_cont, cont):
            if not tag in menu_order:
                raise Continue(self_cont)
            else:
                return self.machine_information(
                   cont=cont,
                   machine=None,
                   objectClass=menu[tag]['objectClass'],
                   proprio=proprio,
                   realm=menu[tag]['realm']
                 )

        (code, tag) = self.handle_dialog(cont, box, tag)
        cont = cont(proprio=None) if isinstance(proprio, objets.AssociationCrans) else cont(proprio=proprio)
        self_cont = TailCall(self.create_machine_proprio, cont=cont, proprio=proprio, tag=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, proprio, self_cont, cont])]
        )

    def create_machine_adherent(self, cont, adherent=None):
        """
        Permet d'ajouter une machine à un adhérent.
        On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment)
        """
        if adherent is None:
            adherent = self.select(["adherent"], "Recherche d'un adhérent pour lui ajouter une machine", cont=cont)
        return self.create_machine_proprio(cont=cont, proprio=adherent)

    def create_machine_club(self, cont, club=None):
        """
        Permet d'ajouter une machine à un club.
        On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment)
        """
        if club is None:
            club = self.select(["club"], "Recherche d'un club pour lui ajouter une machine", cont=cont)
        return self.create_machine_proprio(cont=cont, proprio=club)

    def create_machine_crans(self, cont):
        """Permet l'ajout d'une machine à l'association"""
        associationCrans = self.conn.search(dn="ou=data,dc=crans,dc=org", scope=0)[0]
        return self.create_machine_proprio(cont=cont, proprio=associationCrans)
    def delete_machine(self, cont, machine=None):
        """Permet la suppression d'une machine de la base ldap"""
        if machine is None:
            machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour supression", cont=cont)

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

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

