import logging
import weakref
from StringIO import StringIO
from xml.etree import cElementTree as et
from collections import namedtuple

from ccnode.tags import Tag, tag_inspector
from ccnode.hypervisor import lib as _libvirt
from ccnode.hypervisor.lib import DOMAIN_STATES as STATE
from ccnode.hypervisor.domains import vm_tags


logger = logging.getLogger(__name__)


NetworkInterface = namedtuple('NetworkInterface', ('source', 'mac', 'model'))


class VirtualMachine(object):
    """Represent a VM instance."""
    def __init__(self, dom, hypervisor):
        """
        :param dom: libvirt domain instance
        :param hypervisor: hypervisor where the VM is
        """
        self.hypervisor = weakref.proxy(hypervisor)

        #: UUID string of domain
        self.uuid = dom.UUIDString()
        self.name = dom.name()
        #: state of VM: started, stoped, paused
        self._state = STATE[dom.info()[0]]
        #: tags for this VM
        self.tags = dict((t.name, t) for t in tag_inspector(vm_tags, self))
        # define dynamic tags
        i = 0
        for v in self.iter_disks():
            for t in (
                Tag('disk%s_size' % i, v.capacity, 10),
                Tag('disk%s_path' % i, v.path, 10),
                Tag('disk%s_pool' % i, v.storage, 10),  # FIXME: change
                Tag('disk%s_vol' % i, v.name, 10),
            ):
                self.tags[t.name] = t

            i += 1
        i = 0
        for nic in self.iter_nics():
            for t in (
                Tag('nic%s_mac' % i, nic.mac, 10),
                Tag('nic%s_source' % i, nic.source, 10),
                Tag('nic%s_model' %i, nic.model, 10),
            ):
                self.tags[t.name] = t

        logger.debug('Virtual Machine tags: %s', self.tags)

    @property
    def state(self):
        return self._state

    @state.setter
    def state(self, value):
        self._state = value
        self.tags['status'].value = value

    @property
    def lv_dom(self):
        """Libvirt domain instance."""
        return _libvirt.connection.lookupByUUIDString(self.uuid)

    def start(self):
        self.lv_dom.create()

    def stop(self):
        self.lv_dom.shutdown()

    def suspend(self):
        self.lv_dom.suspend()

    def resume(self):
        self.lv_dom.resume()

    def destroy(self):
        self.lv_dom.destroy()

    def undefine(self):
        self.lv_dom.undefine()

    @property
    def disks(self):
        return list(self.iter_disks())

    def iter_disks(self):
        for d in et.ElementTree().parse(
            StringIO(self.lv_dom.XMLDesc(0))
        ).findall('devices/disk'):
            if d.get('device') != 'disk':
                continue

            type_ = d.get('type')

            if type_ not in ('file', 'block'):
                continue

            path = d.find('source').get(dict(file='file', block='dev')[type_])
            volume = self.hypervisor.storage.get_volume(path)
            if volume is None:
                continue

            yield volume

    @property
    def nics(self):
        return list(self.iter_nics())

    def iter_nics(self):
        for nic in et.ElementTree().parse(
            StringIO(self.lv_dom.XMLDesc(0))
        ).findall('devices/interface'):
            if nic.get('type') == 'bridge':
                try:
                    mac = nic.find('mac').get('address')
                except AttributeError:
                    mac = None
                try:
                    model = nic.find('model').get('type')
                except AttributeError:
                    model = None
                try:
                    source = nic.find('source').get('bridge')
                except AttributeError:
                    source = None
                yield NetworkInterface(
                    mac=mac,
                    source=source,
                    model=model,
                )