# -*- coding: utf-8 -*-
#
# RIDTOOLS.PY -- Gestion de la conversion rid <-> IP
#
# Copyright (C) 2010 Olivier Iffrig
#           (c) 2010 Nicolas Dandrimont
# Authors: Olivier Iffrig <iffrig@crans.org>
#          Nicolas Dandrimont <olasd@crans.org>
# Adapté par Pierre-Elliott Bécue pour cause de changement de schéma.
#
# This program 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 3 of the License, or
# (at your option) any later version.
#
# This program 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, see <http://www.gnu.org/licenses/>.

import netaddr
import itertools
import config

class Rid(object):
    """
    Permet de décortiquer un rid et d'obtenir les IP correspondantes

    Variables d'instance :
     - rid : le rid
     - type : type de rid (cf. config.rid et [1])
     - ipv4_dispo : une IPv4 *publique* *du crans* existe pour ce rid
     - priv : cette machine ne doit pas être accessible de l'extérieur

    [1] http://wiki.crans.org/CransTechnique/PlanAdressage#Machines
    """

    def __init__(self, rid=None, ipv4=None):
        self.rid = None
        self.type = None
        self.ipv4_dispo = False
        self.priv = False

        if rid is not None:
            self.__parse(rid)
        elif ipv4 is not None:
            self.__from_ipv4(ipv4)
        else:
            raise ValueError("Un des champs rid et ipv4 doit être défini")

    def __parse(self, rid):
        """Peuple les champs de l'instance par rapport au rid"""
        self.rid = rid

        self.type, _ = find_rid_plage(int(rid))
        if self.type == 'Inconnu':
            raise ValueError("rid inconnu : %d" % rid)

        self.ipv4_dispo = (rid & (1 << 15)) == 0 and self.type != 'special' or self.rid in config.rid_machines_speciales or (rid & (1 << 14)) != 0

        self.priv = (rid & (1 << 14)) != 0

    def __from_ipv4(self, ip):
        """Peuple les champs à partir de l'ipv4 donnée"""
        if not isinstance(ip, netaddr.IPAddress):
            ip = netaddr.IPAddress(ip)
        try:
            ip.ipv4()
        except netaddr.AddrConversionError:
            raise ValueError("Ceci n'est pas une IPv4... %s" % ip)

        self.ipv4_dispo = True
        self.priv = ip.is_private()

        for tp in config.NETs_primaires.keys():
            for net in config.NETs_primaires[tp]:
                if ip in netaddr.IPNetwork(net):
                    self.type = tp
                    break
            if self.type:
                break
        else:
            for rid, ip_speciale in config.rid_machines_speciales.items():
                if ip == netaddr.IPAddress(ip_speciale):
                    self.rid = rid
                    self.type = "special"
                    break
            else:
                raise ValueError("%s dans aucun des réseaux gérés par le Cr@ns..." % ip)

        ranges = itertools.chain(*[xrange(a, b+1) for (a,b) in config.rid[self.type]])
        if not self.rid:
            self.rid = config.rid[self.type][0][0] + ip.value - netaddr.IPNetwork(config.NETs[self.type][0]).value

        if self.rid not in ranges:
            raise ValueError("%s trop hors des plages prévues pour le réseau '%s'" % (ip, self.type))

        return self.rid

    def ipv4(self):
        """
        Génère l'IPv4 associée à la machine
        """
        if not self.ipv4_dispo:
            raise ValueError("Pas d'adresse IPv4 disponible pour la machine %r" % self)

        if not hasattr(self, '__ipv4'):
            if self.type != "special":
                net = netaddr.IPNetwork(config.NETs[self.type][0])
                offset = self.rid - config.rid[self.type][0][0]
                self.__ipv4 = netaddr.IPAddress(net.first + offset)
            else:
                self.__ipv4 = netaddr.IPAddress(config.rid_machines_speciales[self.rid])

        return self.__ipv4

    def ipv6_network(self):
        """
        Génère le réseau /64 IPv6 associé à la machine
        """
        if self.priv:
            raise ValueError("Pas de prefix ipv6 disponible pour cette machine")

        global_net = netaddr.IPNetwork(config.prefix["subnet"][0])
        global_net.prefixlen = 64
        return global_net.next(self.rid)

    def __repr__(self):
        if self.ipv4_dispo:
            return "<Rid(rid=%s, ipv4='%s')>" % (self.rid, self.ipv4())
        else:
            return "<Rid(%s)>" % self.rid

    def __int__(self):
        return self.rid

def find_rid_plage(rid):
    """Trouve la plage du rid fourni"""
    for (tp, plages) in config.rid_primaires.iteritems():
        if isinstance(plages, list):
            for begin, end in plages:
                if begin <= rid <= end:
                    return tp, config.rid_primaires[tp][0]
        else:
            (begin, end) = plages
            if begin <= rid <= end:
                return tp, (begin, end)
    else:
        return "Inconnu", (0, 0)
