#!/usr/bin/env python
"""Python helper for virt-install(1)"""

import os
import platform
import re
import string
import random
import include_funcs
import yaml

__author__ = 'Jason Callaway'
__email__ = 'jason@jasoncallaway.com'
__license__ = 'Apache License Version 2.0'
__version__ = '0.1'
__status__ = 'alpha'


class KVMInstall(object):

    def setup_lvm(self):
        """Setup the VMs root volume with LVM"""

        # Grab the config values we need
        from_lvm = self.config['clone']
        size = str(self.config['disk'])
        name = self.config['name']

        # Clone an LVM volume from the baseimage volume.
        command = ['lvcreate', '-s', from_lvm, '-L', size + 'G', '-n', name]
        try:
            self.funcs.run_command(command, self.config)
        except Exception, e:
            raise e

    def setup_image(self):
        """Setup the VMs root volume with an image file"""

        # Grab the config values we need
        from_image = self.config['image']
        path = os.path.split(from_image)[0]
        extension = os.path.splitext(from_image)[1]
        size = self.config['disk']
        name = self.config['name']

        # Copy the base image to our new file.
        command = ['cp', from_image, path + '/' + name + extension]
        try:
            self.funcs.run_command(command, self.config)
        except Exception, e:
            raise e

    def generate_mac(self, prefix):
        """Generate a unique MAC address."""

        generated_mac = ''
        # Determine how long our prefix is
        num_colons = prefix.count(':')
        # Add that number of hex substrings
        for _ in range(5 - num_colons):
            # This is a little big funky. I wanted to be sure we have only
            # a-f,0-9, but the string.hexdigits string includes a-f,A-F,
            # so we have to convert to lower case and strip out duplicates
            # which we do by adding them to a set.
            domain = ''.join(set(string.hexdigits.lower()))
            new_hex = self.funcs.get_random(domain, 2)
            generated_mac = generated_mac.join(':' + new_hex)
        return self.config['mac'] + generated_mac

    def generate_ip(self, **kwargs

                    ):
        """Generate a unique IP address within the virsh DHCP scope."""

        # We don't want to generate an IP outside the DHCP range in the virsh
        # network.
        ip_start, ip_end = self.funcs.get_ip_range(self.config)
        start = re.sub('^\d{1,3}\.\d{1,3}\.\d{1,3}\.', '', ip_start)
        end = re.sub('^\d{1,3}\.\d{1,3}\.\d{1,3}\.', '', ip_end)
        # For now we only generate the last octect in an IPv4 address.
        # TODO: add support for generating other octets
        first_three_octets = re.sub('\.\d{1,3}$', '', ip_start)
        if 'octet' in kwargs:
            return first_three_octets + '.' + kwargs['octet']
        else:
            return first_three_octets + '.' + str(random.randint(int(start), int(end)))

    def setup_network(self):
        """Setup the virsh network settings for the VM."""

        # Dump the network config to an xml file for 1) easy parsing and
        # 2) backup just in case something goes sideways.
        self.funcs.net_dumpxml(self.config)

        # TODO: Add IPv6 support

        # First, find a new mac address
        try:
            mac_addresses = self.funcs.get_mac_addresses(self.config)
            new_mac = ''
            good_mac = False
            while good_mac is False:
                new_mac = self.generate_mac(self.config['mac'])
                if new_mac not in mac_addresses:
                    good_mac = True
                    self.config['new_mac'] = new_mac
                    if self.config['verbose'] is True:
                        print '  new mac found: ' + new_mac
        except Exception, e:
            raise Exception('setup_network failed ' +
                            'to generate a new mac address: ' + str(e))

        # Then find an IP address in range that doesn't already exist
        new_ip = ''
        ip_addresses = self.funcs.get_ip_addresses(self.config)
        if 'ipaddress' in self.config:
            if self.config['ipaddress'] not in ip_addresses:
                range_floor, range_ceiling = self.funcs.get_ip_range(self.config)
                new_ip = self.generate_ip(octet=self.config['ipaddress'])
            else:
                raise Exception('setup_network failed, ip address not ' +
                                'available' + self.config['ipaddress'])
        else:
            try:
                good_ip = False
                while good_ip is False:
                    new_ip = self.generate_ip()
                    if new_ip not in ip_addresses:
                        good_ip = True
                        if self.config['verbose'] is True:
                            print '  new ip found: ' + new_ip
            except Exception, e:
                raise Exception('setup_network failed ' +
                                'to generate a new ip address: ' + str(e))

        # Record the new IP for other functions' use
        self.config['new_ip'] = new_ip

        # Now generate the virst net-update command.
        command = ['virsh',
                   'net-update',
                   self.config['network'],
                   'add-last',
                   'ip-dhcp-host']
        host_xml = '"<host mac=\'' + new_mac + '\' name=\'' + \
                   self.config['name'] + '.' + self.config['domain'] + \
                   '\' ip=\'' + new_ip + '\'/>"'
        command.append(host_xml)

        # We need to run virsh net-edit twice, once for the running config
        # (current), then again for the persistent config (config).
        # To be sure we're not talking to the same List object, we'll
        # initialize two new ones with the contents of command[].
        current_command = list(command)
        config_command = list(command)

        # Now, update the current config
        try:
            current_command.append('--current')
            self.funcs.run_command(current_command, self.config)
        except Exception, e:
            raise Exception('virsh net-update --current failed: ' + str(e))

        # First, update the persistent config
        try:
            config_command.append('--config')
            self.funcs.run_command(config_command, self.config)
        except Exception, e:
            raise Exception('virsh net-update --config failed: ' + str(e))

        # Now do the same for DNS
        # Lists are funny, so we're going to create a new one. Don't want
        # any elements left over from our old command[]
        command = list()
        command = ['virsh',
                   'net-update',
                   self.config['network'],
                   'add-last',
                   'dns-host']
        host_xml = '"<host ip=\'' + new_ip + '\'><hostname>' + \
                   self.config['name'] + '.' + self.config['domain'] + \
                   '</hostname></host>"'
        command.append(host_xml)

        config_command = list(command)
        current_command = list(command)

        # Now, update the current config
        try:
            current_command.append('--current')
            self.funcs.run_command(current_command, self.config)
        except Exception, e:
            raise Exception('virsh net-update --current failed: ' + str(e))

        # First, update the persistent config
        try:
            config_command.append('--config')
            self.funcs.run_command(config_command, self.config)
        except Exception, e:
            raise Exception('virsh net-update --config failed: ' + str(e))

    def do_virtinstall(self):
        """Run the virt-install command, basically the last step."""

        network_string = 'network:' + self.config['network'] + ',' + \
            'model=virtio,mac=' + self.config['new_mac']
        command = ['virt-install',
                   '--noautoconsole',
                   '--hvm',
                   '--vnc',
                   '--name', self.config['name'],
                   '--vcpus', str(self.config['vcpus']),
                   '--ram', str(self.config['ram']),
                   '--network', network_string,
                   '--os-type', self.config['type'],
                   '--os-variant', self.config['variant'],
                   '--boot', 'hd']
        if 'clone' in self.config:
            devpath = os.path.split(self.config['clone'])[0]
            install_command = command + ['--disk', 'path=' + devpath + '/' + self.config['name']]
        else:
            imgpath = os.path.split(self.config['image'])[0]
            install_command = command + ['--disk', 'path=' + imgpath + '/' + self.config['name'] + '.img' + ',size=' + str(self.config['disk']) + ',format=qcow2']
        try:
            self.funcs.run_command(install_command, self.config)
        except Exception, e:
            raise e

    def __init__(self, parsed_args):
        # TODO: put in environemnt checks, i.e., does virt-install exist, etc.

        # TODO: verify that we're running as root.

        # Save relative path to module.
        # This is necessary because we don't know where the site-packages
        # directory will live, so we have to determine that at runtime.
        package_directory = os.path.dirname(os.path.abspath(__file__))

        # Load include_vars and funcs.
        varsyaml = os.path.join(package_directory,
                                'include_vars.yaml')
        include_vars_yaml = open(varsyaml).read()
        self.vars = yaml.load(include_vars_yaml)
        self.funcs = include_funcs.KVMInstallFuncs()

        # Check to see if we're on a supported platform.
        if platform.dist()[0] not in self.vars['supported_platforms']:
            raise Exception('unsupported platform: ' + platform.dist()[0])

        # This make my IDE happy
        self.config = {}