# -*- coding: utf-8 -*-
'''
Gestor de nodos para LaOtraRed La Paz - El Alto

 Copyright (C) 2017  Rodrigo Garcia

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero 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 Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''
# Este archivo contiene utilidades para calcular/manejar bloques de 
# direcciones IPv4 e IPv6

import hashlib
import uuid

from database.database import init_db, db_session
from database.database import engine
from .models import Nodo, Ubicacion

from gestor.utils import get_randomToken

from gestor.configs import RUTA_SCRIPT_GENERADOR_FIRMWARES, RUTA_IMAGENES_FIRMWARE_GENERADAS

# varios red
def numIpv4(ipv4):
    ''' Retorna el valor numerico de la direccion ipv4
    ejemplo:     ejemplo: 32.126.2.129 => 545129089'''
    r = ipv4.split(".")
    return int(r[0])*(2**24)+int(r[1])*(2**16)+int(r[2])*(2**8)+int(r[3])
    
def cadIpv4(valor_numerico_ipv4):
    ''' Retorna la representacion en cadena del valor numeico
    correspondiente a la direccion ipv4 dada
    ejemplo: 545129089 => 32.126.2.129'''
    cr = valor_numerico_ipv4
    return (str((cr&0xff000000)>>24)+"."+str((cr&0xff0000)>>16)+"." \
            +str((cr&0xff00)>>8)+"."+str(cr&0xff))

# utilidades de comprobacion de red
def obtener_bloqueIPv4Mayor(lista_bloques):
    ''' Dada la lista de bloques IPv4 retorna el bloque mayor 
    ejemplo: [100.1.1.32, 101.24.1.32, 100.90.0.1, 100.2.1.64]
             return: 101.24.1.32
    '''
    laux = []
    for bloque in lista_bloques:
        laux.append((numIpv4(bloque), bloque))
    laux.sort(reverse=True)
    return laux[0][1]

def obtener_bloqueIPv4Menor(lista_bloques):
    ''' Dada la lista de bloques IPv4 retorna el bloque menor 
    ejemplo: [100.1.1.32, 101.24.1.32, 100.90.0.1, 100.2.1.64]
    return: 100.1.1.32
    '''
    laux = []
    for bloque in lista_bloques:
        laux.append((numIpv4(bloque), bloque))
    laux.sort()
    return laux[0][1]

def obtener_mascaraIpv4(cidr):
    ''' Dado el CIDR obtiene la mascara de red ipv4
    ejemplo /27 => 255.255.255.224
    '''
    c = 0
    B1=""
    B2=""
    B3=""
    B4=""
    while c < cidr:
        if c < 8:
            B1+= "1"
        if c > 7 and c < 16:
            B2+= "1"
        if c > 15 and c < 24:
            B3+= "1"
        if c > 23:
            B4+= "1"
        c+=1
    #completando con 0
    while c < 32: 
        if c < 8:
            B1+= "0"
        if c > 7 and c < 16:
            B2+= "0"
        if c > 15 and c < 24:
            B3+= "0"
        if c > 23:
            B4+= "0"
        c+=1
    # conversion a decimal
    return str(int(B1,base=2))+"."+str(int(B2,base=2))+ \
        "."+str(int(B3,base=2))+"."+str(int(B4,base=2))

def obtener_direccionRedIpv4(ipv4, cidr):
    ''' Obtiene la direccion de red ipv4 , desde la ip y cidr
    ejemplo: 100.65.124.199 /26 => 100.65.124.192
    '''
    mascara = obtener_mascaraIpv4(cidr)

    ip_bytes = ipv4.split(".")
    mascara_bytes = mascara.split(".")

    B4 = str(int(ip_bytes[3]) & int(mascara_bytes[3]))
    B3 = str(int(ip_bytes[2]) & int(mascara_bytes[2]))
    B2 = str(int(ip_bytes[1]) & int(mascara_bytes[1]))
    B1 = str(int(ip_bytes[0]) & int(mascara_bytes[0]))

    return B1+"."+B2+"."+B3+"."+B4

def siguiente_bloqueIpv4Disponible(cidr, publico=True):
    ''' Dado el CIDR comprueba en la base de datos los bloques disponibles
    en los registros de los nodos y devuelve el siguiente bloque IPv4
    disponible para ese CIDR, se sigue la politica:

    CIDR     bloque
    /24      10.64.0.0 - 10.64.0.255
    /25      10.64.1.0 - 10.64.1.255
    /26      10.64.2.0 - 10.64.2.255
    /27      10.64.3.0 - 10.64.3.255
    /28      10.64.4.0 - 10.64.4.255
    /24      10.64.5.0 - 10.64.5.255
    /25      10.64.6.0 – 10.64.6.255
    ...      ...
    (asi sucesivamente)

    TODO: devolver el primer bloque disponible significa tambien
    buscar entre los no usados (nodos que no han confirmado su participacion)
    Esto se puede obtener buscando los nodos proyectados con 
    fecha de creacion prolongada y que no estan activos (no se han
    confirmado), luego obtener el bloque menor.
    Asi se puede llenar el 'hueco' que ha dejado el nodo no usado ni
    confirmado
    '''
    print ("---- Calculando bloque de direcciones IPv4 -----")
    # obteniendo el mayor bloque ipv4 con cidr igual
    lista = []
    if publico:
        for nodo in db_session.query(Nodo).\
            filter(Nodo.ipv4_cidr == cidr).all():
        
            lista.append(nodo.ipv4_network_addr)
    else:
        for nodo in db_session.query(Nodo).all():
            lista.append(nodo.bloque_ipv4_privado)
        
    # filtrando entre ip publicas o privadas
    nlista = []
    for ipv4addr in lista:
        if publico:
            if ipv4addr.startswith("10.64"):
                nlista.append(ipv4addr)
        else:
            if ipv4addr.startswith("172.24"):
                nlista.append(ipv4addr)
            
    # primera insercion
    if len(nlista) == 0:
        if publico:
            if cidr == 24:
                nlista.append("10.63.255.0") # truco para empezar en 10.64.0.0
            elif cidr == 25:
                nlista.append("10.64.1.0")
            elif cidr == 26:
                nlista.append("10.64.2.0")
            elif cidr == 27:
                nlista.append("10.64.3.0")
            elif cidr == 28:
                nlista.append("10.64.4.0")
        else:
            nlista.append("172.24.0.0")

    bloque_mayor = obtener_bloqueIPv4Mayor(nlista)
    ultima_dir_red = obtener_direccionRedIpv4(bloque_mayor, cidr)
    print ("Bloque mayor:"+bloque_mayor)
    print ("cidr:"+str(cidr))
    print ("Ultima red:"+ultima_dir_red)

    # calculando salto y siguiente direcicon de red
    m1 = numIpv4(ultima_dir_red) % 256
    m2 = 0
    p = 32 - cidr

    if cidr == 28:
        m2 = (numIpv4(ultima_dir_red) + 16) % 256
    elif cidr == 27:
        m2 = (numIpv4(ultima_dir_red) + 32) % 256
    elif cidr == 26:
        m2 = (numIpv4(ultima_dir_red) + 64) % 256
    elif cidr == 25:
        m2 = (numIpv4(ultima_dir_red) + 128) % 256
    elif cidr == 24:
        m2 = (numIpv4(ultima_dir_red) + 256) % 256

    if m1 > m2: 
        # saltando 5 subredes /24
        n_r = numIpv4(ultima_dir_red) + (256*5)
    else:
        n_r = numIpv4(ultima_dir_red)
    
    print("Siguiente bloque:"+cadIpv4(n_r + 2**p))
    print ("------------------------------------------------")
    return cadIpv4(n_r + 2**p)

def get_cidrIpV4(num_direcciones):
    ''' Retorna el cidr necesario para el `num_direcciones' dadas
    '''
    n = int(num_direcciones)
    if n <= 14:
        return 28
    if n > 14 and n <= 30:
        return 27
    if n > 30 and n <= 62:
        return 26
    if n > 62 and n <= 126:
        return 25
    if n > 126 and n <= 254:
        return 24
    return -1

def siguiente_bloqueIpv6Disponible():
    ''' Dado el CIDR comprueba en la base de datos los bloques disponibles
    en los registros de los nodos y devuelve el siguiente bloque IPv6

    El bloque ipv6 se obtiene de manera aleatoria, usando el bloque:
      fc01:1934:fffe:9493::/64 
    que comparten todos los nodos de la red, un router deberia tener esto:
      fc01:1934:fffe:9493:c24a:ff:fedd:5e7a/128
    La asignacion genera un valor aleatorio para los ultimos
    4 grupos (en ipv6) es decir:
      fc01:1934:fff3:9493:<rand>:<rand>:<rand>:<rand>/128
    y comprueba que no exista este numero antes.
    '''
    print ("---- Calculando bloque de direcciones IPv6 -----")
    unico = False
    ipv6 = 'fc01:1934:fffe:9493:'
    while not unico:
        token = get_randomToken()
        g1 = token[0:4]
        g2 = token[4:8]
        g3 = token[11:15]
        g4 = token[19:23]
        aux = ipv6+g1+':'+g2+":"+g3+":"+g4+"/128"

        n1 = db_session.query(Nodo).\
             filter(Nodo.bloque_ipv6 == aux).count()
        if n1 == 0:
            unico = True
            ipv6 = aux
    print ("siguiente bloque: "+ipv6)
    print ("------------------------------------------------")
    return ipv6
    
