# -*- coding: utf-8 -*-

import os
import psycopg2
from functools import wraps
import time

conn = None
# : échec définitif, on raise une exception direct
def_failed = False
def _need_conn(f):
    """Décorateur à appliquer aux fonctions nécessitant une connexion pgsql"""
    retries = 1
    delay = 5
    @wraps(f)
    def first_connect(*args, **kwargs):
        global conn, def_failed
        if def_failed:
            raise NameError("La connexion à la pase postgresql ne peut être établie.")
        attempts = 0
        while not conn or not attempts:
            host = os.getenv('DBG_ANNUAIRE', 'pgsql.v4.adm.crans.org')

            # Test habituel sur vo:
            if host == '1' or __name__.endswith('annuaires_pg_test'):
                host='localhost'

            # "connecting …"
            try:
                if not conn:
                    if attempts:
                        # Attend un peu avant de reessayer
                        time.sleep(delay)
                    conn = psycopg2.connect(user='crans_ro', database='django',
                                            host=host)
                return f(*args, **kwargs)
            except psycopg2.OperationalError:
                attempts += 1
                conn = None   # Connexion morte, on recommence à zéro
                if attempts > retries:
                    def_failed = True
                    raise NameError
            # Les scripts appelant annuaires_pg n'ont pas à connaître le
            # backend pgsql. On utilise donc une exception plus standard
    return first_connect

# Le v est virtuel.
bat_switchs = ["a", "b", "c", "g", "h", "i", "j", "m", "o", "p", "v"]

class ChbreNotFound(ValueError):
    """Lorsqu'une chambre n'existe pas"""
    pass

@_need_conn
def chbre_prises(batiment, chambre = None):
    """Correspondance chambre -> prise"""
    batiment = batiment.lower()
    if chambre:
        chambre = chambre.lower()
        cur = conn.cursor()
        cur.execute("SELECT prise_crans FROM prises_prise WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
        try:
            return "%03d" % cur.fetchone()[0]
        except TypeError:
            raise ChbreNotFound("Chambre inexistante bat %r, chbre %r" % (batiment, chambre))
    else:
        cur = conn.cursor()
        cur.execute("SELECT chambre, prise_crans FROM prises_prise WHERE batiment = %s", batiment)
        ret = {}
        for chambre, prise_crans in cur.fetchall():
            ret[chambre] = "%03d" % prise_crans
        if not ret:
            raise ValueError("Batiment %s inexistant" % batiment)
        return ret

@_need_conn
def chbre_commentaire(batiment, chambre):
    """ Renvoie le commentaire associé à la chambre """
    global conn
    batiment = batiment.lower()
    cur = conn.cursor()
    cur.execute("SELECT commentaire FROM prises_prise WHERE (batiment, chambre) = (%s,%s)", (batiment, chambre))
    try:
        return cur.fetchone()[0]
    except TypeError:
        raise ValueError("Chambre inexistante bat %r, chbre %r" % (batiment, chambre))

@_need_conn
def reverse(batiment, prise = None):
    """Correspondance prise -> chambre"""
    batiment = batiment.lower()
    if prise:
        cur = conn.cursor()
        cur.execute("SELECT chambre FROM prises_prise WHERE (batiment, prise_crans) = (%s, %s)", (batiment, int(prise)))
        try:
            return [chbre for (chbre,) in cur.fetchall()]
        except TypeError:
            raise ValueError("Prise %s inexistante" % prise)
    else:
        cur = conn.cursor()
        cur.execute("SELECT chambre, prise_crans FROM prises_prise WHERE batiment = %s", batiment)
        ret = {}
        for chambre, prise_crans in cur.fetchall():
            try:
                ret["%03d" % prise_crans].append(chambre)
            except KeyError:
                ret["%03d" % prise_crans] = [chambre]

        if not ret:
            raise ValueError("Batiment %s inexistant" % batiment)
        return ret
@_need_conn
def is_connected(batiment, chambre):
    """Cablage physique effectue ?"""
    batiment = batiment.lower()
    chambre = chambre.lower()
    cur = conn.cursor()
    cur.execute("SELECT cablage_effectue FROM prises_prise WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
    return cur.fetchone()[0]

# Prises d'uplink, de machines du crans / Prises d'utilité CRANS
uplink_prises={ 'a' :
{ 49 : 'uplink->bata-4',  50 : 'libre-service',
 149 : 'uplink->bata-4', 150 : 'libre-service',
 225 : 'uplink->bata-4', 226 : 'libre-service',
 325 : 'uplink->bata-4', 326 : 'libre-service',
 401 : 'uplink->bata-0', 402 :  'uplink->bata-1',
 403 : 'uplink->bata-2', 404 :  'uplink->bata-3',
 424 : 'uplink->backbone' },
'b' :
{ 49 : 'uplink->batb-4',  50 : 'libre-service',
 149 : 'uplink->batb-4', 150 : 'libre-service',
 249 : 'uplink->batb-4', 250 : 'libre-service', # 249 morte ?! (olasd 21/01/2010)
 349 : 'uplink->batb-4', 350 : 'libre-service',
 401 : 'uplink->batb-0', 402 :  'uplink->batb-1',
 403 : 'uplink->batb-2', 404 :  'uplink->batb-3',
 405 : 'uplink->backbone', 523 : 'uplink->batb-4',
 },
'c' :
{ 49 : 'uplink->batc-3',  50 : 'libre-service',
 149 : 'uplink->batc-3', 150 : 'libre-service',
 225 : 'uplink->batc-3', 226 : 'libre-service',
 301 : 'uplink->batc-0', 302 : 'uplink->batc-1',
 304 : 'uplink->batc-4',
 303 : 'uplink->batc-2', 324 : 'uplink->backbone',
 426 : 'uplink->batc-3',
},
'g' :
{
  22 : 'uplink->backbone',
  23 : 'libre-service', 24 : 'uplink->batg-8',

  149 : 'uplink->batg-8',  150 : 'libre-service',

 249 : 'uplink->batg-8',  250 : 'uplink->batg-3',

 325 : 'uplink->batg-8',  326 : 'libre-service',

 449 : 'uplink->batg-9',  450 : 'uplink->batg-5',

 549 : 'uplink->batg-9',  550 : 'uplink->batg-6',

 649 : 'uplink->batg-9',  650 : 'uplink->batg-5',

 725 : 'uplink->batg-9',  726 : 'libre-service',

 801 : 'uplink->batg-1',  802 : 'uplink->batg-2',

 803 : 'uplink->batg-3',  821 : 'uplink->batg-0',
 823 : 'uplink->batg-9',

 901 : 'uplink->batg-4',  902 : 'uplink->batg-5',
 903 : 'uplink->batg-6',  904 : 'uplink->batg-7',
 921 : 'uplink->batg-8',
 },
'h' :
{ 49 : 'uplink->bath-3',  50 : 'libre-service',
 149 : 'uplink->bath-3', 150 : 'libre-service',
 225 : 'uplink->bath-3', 226 : 'libre-service',
 301 : 'uplink->bath-0', 302 : 'uplink->bath-1',
 303 : 'uplink->bath-2', 324 : 'uplink->backbone' },
'i' :
{ 49 : 'uplink->bati-3',  50 : 'libre-service',
 149 : 'uplink->bati-3', 150 : 'libre-service',
 301 : 'uplink->bati-0', 302 : 'uplink->bati-1',
 324 : 'uplink->backbone' },
'j' :
{ 49 : 'uplink->batj-3', 50 : 'libre-service',
 149 : 'uplink->batj-3', 150 : 'libre-service', # XXX: 150 semble morte
 225 : 'uplink->batj-3',  226 : 'libre-service',
 321 : 'uplink->backbone',
 301 : 'uplink->batj-0', 303 : 'uplink->batj-1',
 305 : 'uplink->batj-2', 307 : 'uplink->batj-4',
 421 : 'uplink->batj-3', 422 : 'libre-service',
},
'k' : {
 25 : 'uplink->backbone',
},
'm' :
{
 49 : 'libre-service', 50 : 'uplink->batm-7',
 149 : 'libre-service', 150 : 'uplink->batm-7',
 249 : 'libre-service',  250 : 'uplink->batm-7',
 349 : 'libre-service',  350 : 'uplink->batm-7',
 449 : 'libre-service',  450 : 'uplink->batm-7',
 549 : 'libre-service',  550 : 'uplink->batm-7',
 649 : 'libre-service',  650 : 'uplink->batm-7',

 724 : 'libre-service', 723 : 'libre-service',
 722 : 'libre-service',

 721 : 'uplink->backbone', 720 : 'uplink->batm-0',
 719 : 'uplink->batm-1', 718 : 'uplink->batm-2',
 717 : 'uplink->batm-3', 716 : 'uplink->batm-4',
 715 : 'uplink->batm-5', 714 : 'uplink->batm-6',
 },
 'p' :
{
 49 : 'uplink->batp-4 (R4.1)',
 149: 'uplink->batp-4 (R3.1)',
 249: 'uplink->batp-4 (R2.1)',
 350: 'uplink->batp-4 (R1.2)',
 # On ne génère pas la conf de batp-4 automatiquement, mais ses uplinks
 # peuvent être utiles à connaître
 401: 'uplink->batp0', 402: 'uplink->batp-1',
 403: 'uplink->batp2', 403: 'uplink->batp-3',
 405: 'libre-service', 406: 'uplink->bato-1',
},
 'o' :
 {
 25 : 'uplink->bato-1', 26 : 'libre-service',
 101 : 'uplink->bato-0', 121: 'uplink->NRD',
 122: 'uplink->backbone', 123: 'uplink->backbone (unused)',
 124: 'uplink->batp-0'
 } ,
 'v' :
 {
  23: 'uplink', 24 : 'uplink',
 } ,
'backbone' : #For your consideration
 {
 #A: 12eth+12fibre,  B: 24 eth
 'A1': 'odlyd',     'B1': 'bata',
 'A2': 'komaz-ens', 'B2': 'multiprise-wifi',
 'A3': 'sable',     'B3': 'dyson',
 'A4': 'komaz',     'B4': 'fy',
 'A5': 'zbee'    ,  'B5': 'switch-ilo',
                    'B6': 'vigile 0B',
                    'B7': 'kdell',
                    'B8': 'batb',
                    'B9': '2b',
                    'B10': 'fz',
                    'B11': 'ft',
                    'B12': 'nols2',
 'A13': 'batm',
 'A14': 'batp',     'B14': 'zamok',
 'A15': 'batc',     'B15': 'charybde',
 'A16': 'bato',
 'A17': 'bath',
 'A18': 'bati',     'B18': 'nols',
 'A19': 'batj',
 'A20': 'batg',
 'A21': 'batk',
                    'B21': 'osm1',
                    'B22': 'osm1-ilo',
                    'B23': 'osm2',
                    'B24': 'osm2-ilo',
 },
}

_SPECIAL_SWITCHES = ['backbone.adm.crans.org',
    'multiprise-v6.adm.crans.org',
    'batk-0.crans.org',
    'minigiga.adm.crans.org',
    'batb-5.crans.org',
]
_HIDDEN_SWITCHES = [
    'batp-4.adm.crans.org',
    'batv-0.adm.crans.org',
]

def all_switchs(bat=None, hide=_SPECIAL_SWITCHES + _HIDDEN_SWITCHES):
    """Retourne la liste des switchs pour un batiment.

    Si bat est donné, seulement pour le bâtiment demandé, sinon pour
    tous les bâtiments. bat peut être une liste aussi. Le backbone n'est
    pas pris en compte. La convention est batx-y sauf si y=0 et on a donc
    simplement batx"""

    if bat == None:
        bat = list(bat_switchs)
    if type(bat) not in [ tuple, list ] :
        bat = [bat]
    switchs = []
    for b in bat:
        indexes = set(n/100 for n in uplink_prises[b])
        for i in indexes:
            hostname = "bat%s-%s.adm.crans.org" % (b, i)
            if hostname not in hide:
                switchs.append(hostname)
    # on ajoute quand-même le backbone et/ou multiprise-v6 si demandé
    switchs += set(_SPECIAL_SWITCHES).difference(hide)

    switchs.sort()
    return switchs

# Locaux clubs : lecture dans chbre_prises et ajout des locaux dans les bats non
# manageables
def locaux_clubs() :
    """ Retourne le dictionaire des locaux club : {bat: [locaux]} """
    # Corespondance chbre -> nom du local club
    locaux_clubs = { 'Kcl0' : 'Kfet' ,
                     'Bcl1' : 'Med',
                     'Pcl0' : 'Bds' ,
                     'Mcl0' : 'Shape',
                     'Mcl1' : 'Krobot',
                     'EXT'  : 'EXT' }
    # Ajout des locaux d'étage A, B et C
    for b in 'ABC' :
        for i in range(2,7) :
            locaux_clubs['%scl%i' % ( b, i)] = '%i@%s' % (i, b)
    # Ajout de ceux des H, I et J
    for b in 'HIJ' :
        for i in range(1,5) :
            locaux_clubs['%scl%i' % ( b, i)] = '%i@%s' % (i, b)
    # Dédoubleur pour le 3H (digicode)
    locaux_clubs['Hcl3b'] = "3@Hbis"
    # Supression du 2@B et 4@J
    locaux_clubs.pop('Bcl2')
    locaux_clubs.pop('Jcl4')

    return locaux_clubs
