#!/usr/bin/env python
from walt.server import const
from walt.server.network.tools import ip, net
from walt.common.nodetypes import is_a_node_type_name
from operator import itemgetter
from itertools import groupby
from walt.server import image
from walt.common.tools import do

DHCPD_CONF_FILE = '/etc/dhcp/dhcpd.conf'

CONF_PATTERN = """
#
# DO NOT EDIT THIS FILE
#
# It is automatically generated by the walt system 
# and updated when needed.
#

# allow sending DHCP NAKs
authoritative;

# custom parameters declaration
option nfs-server code 138 = string;
option nfs-fs-path code 140 = string;
if exists dhcp-parameter-request-list {
    option dhcp-parameter-request-list=concat(
           option dhcp-parameter-request-list,8A,8C);
}

# global parameters
authoritative;
option nfs-server "%(walt_server_ip)s";
option broadcast-address %(subnet_broadcast)s;

# walt unregistered devices
subnet %(subnet_ip)s netmask %(subnet_netmask)s {
    # declare ranges of unallocated addresses
%(walt_unallocated_ranges_conf)s

    # load default image, if node
    option nfs-fs-path "%(default_fs_path)s";

    # check if the ip is already used
    ping-check = 1;

    # recheck often if you are now registered
    max-lease-time 30;
    default-lease-time 30;
}

# walt registered devices
%(walt_registered_devices_conf)s
"""

RANGE_CONF_PATTERN = "    range %(first)s %(last)s;"

NODE_CONF_PATTERN = """\
host %(hostname)s {
    hardware ethernet %(mac)s;
    fixed-address %(ip)s;
    option host-name "%(hostname)s";
    option nfs-fs-path "%(fs_path)s";
}
"""

SWITCH_CONF_PATTERN = """\
host %(hostname)s {
    hardware ethernet %(mac)s;
    fixed-address %(ip)s;
}
"""

# see http://stackoverflow.com/questions/2154249/identify-groups-of-continuous-numbers-in-a-list
def get_contiguous_ranges(ips):
    ranges=[]
    for k, g in groupby(enumerate(ips), lambda (i,x):i-int(x)):
        group = map(itemgetter(1), g)
        ranges.append((group[0], group[-1]))
    return ranges

def generate_dhcpd_conf(devices):
    subnet = net(const.WALT_SUBNET)
    devices_confs = []
    free_ips = list(subnet.hosts())
    server_ip = free_ips.pop(0)
    for device_info in devices:
        if device_info['type'] == 'switch':
            conf_pattern = SWITCH_CONF_PATTERN
        else:
            conf_pattern = NODE_CONF_PATTERN
        devices_confs.append(conf_pattern % device_info)
        free_ips.remove(ip(device_info['ip']))
    range_confs = []
    for r in get_contiguous_ranges(free_ips):
        first, last = r
        range_confs.append(\
            RANGE_CONF_PATTERN % dict(
                    first=first,
                    last=last
        ))
    default_fs = image.get_mount_path(const.DEFAULT_IMAGE)
    infos = dict(
        default_fs_path=default_fs,
        walt_server_ip=server_ip,
        subnet_ip=subnet.network_address,
        subnet_broadcast=subnet.broadcast_address,
        subnet_netmask=subnet.netmask,
        walt_registered_devices_conf='\n'.join(devices_confs),
        walt_unallocated_ranges_conf='\n'.join(range_confs)
    )
    return CONF_PATTERN % infos

QUERY_DEVICES_WITH_IP="""
    SELECT * FROM devices WHERE ip IS NOT NULL ORDER BY mac;
"""

class DHCPServer(object):
    def __init__(self, db):
        self.db = db
    def update(self, force=False):
        devices = []
        for item in \
                self.db.execute(QUERY_DEVICES_WITH_IP).fetchall():
            device_type = item.type
            device_mac = item.mac
            fs_path = None  # default
            if is_a_node_type_name(device_type):
                image_name = self.db.select_unique(
                            'nodes', mac=device_mac).image
                fs_path = image.get_mount_path(image_name)
            if device_type != 'server':
                devices.append(dict(
                    type=item.type,
                    hostname=item.name,
                    ip=item.ip,
                    mac=device_mac,
                    fs_path=fs_path))
        conf = generate_dhcpd_conf(devices)
        with open(DHCPD_CONF_FILE, 'r') as conf_file:
            old_conf = conf_file.read()
        if conf != old_conf:
            with open(DHCPD_CONF_FILE, 'w') as conf_file:
                conf_file.write(conf)
            force = True # perform the restart below
        if force == True:
            do('service isc-dhcp-server restart')
            print 'dhcpd conf updated.'

