#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
"""Fournit des outils et fonctions appelables au besoin"""

from gestion import annuaires_pg
from multiprocessing import Process, Manager

from .switch import HPSwitch
from .mac import MACFactory, format_mac

def filter_uplink(switch, stuff):
    """Filtre les prises uplink d'un retour.
    stuff est une liste de la forme
    [(port_id, mac), ...]"""
    sortie = []

    # Retourne "batg", "4.adm.crans.org", par exemple.
    bat, num = switch.split('-', 1)

    # On filtre ce qui n'est pas utile.
    bat = bat[-1]
    num = int(num[0])

    # On récupère les infos utiles.
    uplink = annuaires_pg.uplink_prises[bat]
    gen_num_prise = 100 * num

    for (port_id, mac) in stuff:
        num_prise = gen_num_prise + port_id
        if not num_prise in uplink:
            sortie.append((port_id, mac))

    return sortie

# +--------------------------------------------------------+
# |              Mac Tracking Functions                    |
# +--------------------------------------------------------+
"""Ces fonctions servent à tracker une mac sur le réseau.
La fonction trace_mac s'occupe de ce travail. Elle utilise
la librairie multiprocessing pour spawner un process par
switch, et aller récupérer auprès de ceux-ci la liste des
MACs connectées, et les ports allant bien.

Multiprocessing ne mettant pas en place du partage de variable
par défaut, les objets retournés le sont via un Manager, dans
un dico, sans structure complexe.

Une solution dans laquelle les switches seraient renvoyés dans
leur structure python existe, mais elle est plus coûteuse,
et peu utile dans notre cas. (l'overhead engendré par la méthode
à base de dicos et régénération dans le processus parent est
epsilonesque)"""

def fetch_all_ports(switch, output):
    """Récupère l'ensemble des ports d'un switch, avec les MACS
    dessus."""

    sw = HPSwitch(switch)
    # output est un Manager().dict()
    __stuff = sw.fetch_all_ports()
    __stuff = filter_uplink(switch, __stuff)
    output[switch] = __stuff

def populate_all_switches(switches=None):
    """Remplit l'ensemble des switches avec les MACS qui sont
    présentes sur leurs ports.

    Peut également ne remplir qu'une liste spécifique si fournie
    en argument."""

    if switches == None:
        switches = annuaires_pg.all_switchs()

    hp_switches = {
        switch : HPSwitch(switch)
        for switch in switches
    }
    processes = {}

    # La sortie des appels de fetch_all_ports sera écrite dans ce dico.
    # On évitera la concurrence en utilisant le nom du switch comme
    # séparateur
    output = Manager().dict()

    # Dans une première boucle, on crée les switches. Et on met
    # les processes en mode actif.
    for switch in switches:
        hp_switches[switch].flush_ports()
        processes[switch] = Process(target=fetch_all_ports, args=(switch, output), name=switch)
        processes[switch].start()

    # On fait la jointure des processes dans une seconde
    # boucle, pour s'assurer que les processes ont bien
    # tous été lancés avant de commencer à les sonder.
    for switch in switches:
        processes[switch].join()

    for switch in switches:
        if output[switch] is not None:
            for (iid, val) in output[switch]:
                if hp_switches[switch].ports.get(iid, None) is not None:
                    hp_switches[switch].ports[iid].append_mac(val)
        else:
            print "Output for switch %s is None." % (switch,)

def trace_mac(mac, in_all_switches=False):
    """Cherche une MAC. Si in_all_switches est à True, commence
    par instancier tous les switches, et à les peupler.

    Cette méthode est assez agressive, il faut l'utiliser avec
    précaution."""
    if in_all_switches:
        populate_all_switches()

    mac = format_mac(mac)

    # On boucle sur les macs dans la factory
    __mac = MACFactory.get_mac(mac)
    if __mac is not None:
        # On boucle sur les parents (des ports) à la recherche
        # de ceux qui appartiennent au switch courant.
        __parents = []
        for parent in __mac.parents.itervalues():
            __parents.append(parent)

        # Si on en a trouvé, on les retourne avec la mac.
        if __parents:
            return (__mac, __parents)

    return None
