
from __future__ import absolute_import
from __future__ import division

import logging
import xml.etree.ElementTree as etree

from vdsm.virt import xmlconstants


_UNSPECIFIED = object()


class NotFound(Exception):
    """
    Raised when vmxml helpers can't find some requested entity.
    """
    pass


def find_all(element, tag_):
    if tag(element) == tag_:
        yield element
    for elt in element.findall('.//' + tag_):
        yield elt


def find_first(element, tag, default=_UNSPECIFIED):
    try:
        return next(find_all(element, tag))
    except StopIteration:
        if default is _UNSPECIFIED:
            raise NotFound((element, tag,))
        else:
            return default


def find_attr(element, tag, attribute):
    try:
        subelement = find_first(element, tag)
    except NotFound:
        return ''
    return attr(subelement, attribute)


def tag(element):
    return element.tag


def attr(element, attribute):
    return element.get(attribute, '')


def set_attr(element, attribute, value):
    element.set(attribute, value)


def text(element):
    return element.text or ''


def children(element, tag=None):
    if tag is None:
        return iter(element)
    else:
        return element.iterfind('./' + tag)


def append_child(element, child=None, etree_child=None):
    if child is not None and etree_child is None:
        element.append(child._elem)
    elif child is None and etree_child is not None:
        element.append(etree_child)
    else:
        raise RuntimeError(
            'append_child invoked with child=%r etree_child=%r' % (
                child, etree_child))


def remove_child(element, child):
    element.remove(child)


def replace_first_child(element, new_child):
    old_child = next(iter(element))
    element.remove(old_child)
    element.insert(0, new_child)


def has_channel(domXML, name):
    domObj = etree.fromstring(domXML)
    devices = domObj.findall('devices')

    if len(devices) == 1:
        for chan in devices[0].findall('channel'):
            targets = chan.findall('target')
            if len(targets) == 1:
                if targets[0].attrib['name'] == name:
                    return True

    return False


def has_vdsm_metadata(domXML):
    domObj = etree.fromstring(domXML)
    metadata = domObj.findall('metadata')
    nsdict = {
        xmlconstants.METADATA_VM_VDSM_PREFIX:
        xmlconstants.METADATA_VM_VDSM_URI
    }
    vdsmtag = (
        xmlconstants.METADATA_VM_VDSM_PREFIX + ':' +
        xmlconstants.METADATA_VM_VDSM_ELEMENT
    )
    for md in metadata:
        if len(md.findall(vdsmtag, nsdict)) > 0:
            return True
    return False


def device_address(device_xml, index=0):
    address_element = list(find_all(device_xml, 'address'))[index]
    return parse_address_element(address_element)


def parse_address_element(address_element):
    return {
        key.strip(): value.strip()
        for key, value in address_element.attrib.items()
    }


class Device(object):
    __slots__ = ()

    def createXmlElem(self, elemType, deviceType, attributes=()):
        elemAttrs = {}
        element = Element(elemType)

        if deviceType:
            elemAttrs['type'] = deviceType

        for attrName in attributes:
            if not hasattr(self, attrName):
                continue

            attr = getattr(self, attrName)
            if attr is None:
                log = logging.getLogger('devel')
                log.debug("Attribute '%s' of '%s' device element '%s' is None",
                          attrName, deviceType, elemType)
                continue

            if isinstance(attr, dict):
                element.appendChildWithArgs(attrName, **attr)
            else:
                elemAttrs[attrName] = attr

        element.setAttrs(**elemAttrs)
        return element


class Element(object):

    def __init__(self, tagName, text=None, namespace=None, namespace_uri=None,
                 **attrs):
        if namespace_uri is not None:
            tagName = '{%s}%s' % (namespace_uri, tagName,)
            if namespace is not None:
                etree.register_namespace(namespace, namespace_uri)
        self._elem = etree.Element(tagName)
        self.setAttrs(**attrs)
        if text is not None:
            self.appendTextNode(text)

    def __getattr__(self, name):
        return getattr(self._elem, name)

    def __len__(self):
        return len(self._elem)

    def __iter__(self):
        return iter(self._elem)

    def setAttrs(self, **attrs):
        for attrName, attrValue in attrs.items():
            self._elem.set(attrName, attrValue)

    def setAttr(self, attrName, attrValue):
        self._elem.set(attrName, attrValue)

    def appendTextNode(self, text):
        self._elem.text = text

    def appendChild(self, element=None, etree_element=None):
        append_child(self._elem, element, etree_element)

    def appendChildWithArgs(self, childName, text=None, **attrs):
        child = Element(childName, text, **attrs)
        append_child(self._elem, child)
        return child
