#!/usr/bin/python3
#-*- coding: utf-8 -*-

import argparse
import re
import ipaddress
from creole.loader import creole_loader

_force_dirs = None
_force_eoleextradico = None
_force_configeol= None
_force_eoleextraconfig = None
_reload_config = True


def get_loader(mandatory_permissive=True):
    return creole_loader(load_extra=True,
                         mandatory_permissive=mandatory_permissive, force_dirs=_force_dirs,
                         force_eoleextradico=_force_eoleextradico, force_configeol=_force_configeol,
                         force_eoleextraconfig=_force_eoleextraconfig, reload_config=_reload_config)


def get_ranges():
    cfg = get_loader()
    range_specifications = list(zip([ipaddress.ip_network(u'{}/{}'.format(ip, netmask))
                                for ip, netmask in zip(list(cfg.creole.dhcp.adresse_network_dhcp.adresse_network_dhcp),
                                    list(cfg.creole.dhcp.adresse_network_dhcp.adresse_netmask_dhcp))
                               ],
                               list(cfg.creole.dhcp.adresse_network_dhcp.nom_plage_dhcp),
                               [ipaddress.ip_address(ip)
                                for ip in list(cfg.creole.dhcp.adresse_network_dhcp.ip_basse_dhcp)
                               ],
                               [ipaddress.ip_address(ip)
                                for ip in list(cfg.creole.dhcp.adresse_network_dhcp.ip_haute_dhcp)
                               ],
                               list(cfg.creole.dhcp.adresse_network_dhcp.interdire_hotes_inconnus),
                               [True if s == 'oui' else False
                                for s in list(cfg.creole.dhcp.adresse_network_dhcp.adressage_statique)]
                               ))

    ranges = {r[1]: {'first': int(r[2]),
                     'last': int(r[3]),
                     'restricted': r[4],
                     'static': r[5],
                     'subnet': r[0]}
              for r in range_specifications}
    return ranges


def subnet_host_is_in(ip, ranges):
    """Return subnet and range the host is in.
    :param host: host description
    :type host: dict('ip'=str, 'rangename'=str)
    """
    ip = int(ipaddress.ip_address(ip))
    rangename = None
    for r in ranges:
        if ranges[r]['first'] <= ip <= ranges[r]['last']:
            return r
    return 'NA'


LEASES_DATABASE = '/var/lib/dhcp/dhcpd.leases'
DHCP_LEASES_ATTRS = {
        'starts': re.compile(r'^\s*starts\s+[0-9]\s+(?P<value>.*);', re.M),
        'ends': re.compile(r'^\s*ends\s+[0-9]\s+(?P<value>.*);', re.M),
        'cltt': re.compile(r'^\s*cltt\s+[0-9]\s+(?P<value>.*);', re.M),
        'binding state': re.compile(r'^\s*binding state\s+(?P<value>.*);', re.M),
        'next binding state': re.compile(r'^\s*next binding state\s+(?P<value>\w+);', re.M),
        'rewind binding state': re.compile(r'^\s*rewind binding state\s+(?P<value>\w+);', re.M),
        'hardware ethernet': re.compile(r'^\s*hardware ethernet\s+(?P<value>[a-f0-9]{2}(:[a-f0-9]{2}){5});', re.M),
        'client-hostname': re.compile(r'^\s*client-hostname\s+"?(?P<value>\w+)"?;', re.M)
        }
leases_re = re.compile(r'^(?P<lease>lease (?P<ip>[0-9]{1,3}(?:\.[0-9]{1,3}){3})\s+\{.*?\})', re.M|re.S)


def parse_attrs(lease):
   attrs = {}
   for attribute in DHCP_LEASES_ATTRS:
     r = DHCP_LEASES_ATTRS[attribute].search(lease)
     if r:
       attrs[attribute] = r.group('value')
   return attrs


def get_leases(leases):
   l = leases_re.finditer(leases)
   return {lease.group('ip'): parse_attrs(lease.group('lease')) for lease in l}


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Expose dhcp information')
    parser.add_argument('--detailed', action='store_true')
    args = parser.parse_args()

    with open(LEASES_DATABASE, 'r') as leases_db:
        leases = leases_db.read()

    leases = get_leases(leases)
    ranges = get_ranges()
    for l, lease in leases.items():
        subnet = subnet_host_is_in(l, ranges)
        ranges[subnet].setdefault('leases', [])
        ranges[subnet]['leases'].append((l,lease))
    for r in ranges:
        current_leases = [lease for lease in ranges[r].get('leases', []) if lease[1]['binding state'] == 'active']
        total_leases = ranges[r]['last'] - ranges[r]['first'] + 1
        print(r, '{} {}'.format(total_leases - len(current_leases), total_leases))
        if args.detailed:
            for rn in ranges[r].get('leases', []):
                print('\t', rn[1].get('hardware ethernet', 'NA'), rn[0], rn[1].get('client-hostname', 'NA'), rn[1]['binding state'])
