
from __future__ import absolute_import

import itertools
import logging
import six
import xml.etree.ElementTree as ET

from vdsm.common import cache
from vdsm.common import cpuarch
from vdsm.common import libvirtconnection
from vdsm.common.config import config


class _CpuMode:
    HOST_MODEL = 'host-model'
    CUSTOM = 'custom'


@cache.memoized
def emulated_machines(arch, capabilities=None):
    if capabilities is None:
        capabilities = _get_libvirt_caps()
    caps = ET.fromstring(capabilities)

    return (_emulated_machines_from_caps_domain(arch, caps) or
            _emulated_machines_from_caps_arch(arch, caps))


def domain_cpu_models(conn, arch, cpu_mode):
    if config.getboolean('vars', 'fake_kvm_support'):
        virt_type = 'qemu'
    else:
        virt_type = 'kvm'
    domcaps = conn.getDomainCapabilities(None, arch, None, virt_type, 0)
    if not domcaps:
        logging.error('Error while getting CPU models: '
                      'no domain capabilities found')
        return {}

    xmldomcaps = ET.fromstring(domcaps)
    cpucaps = xmldomcaps.find('cpu')
    if cpucaps is None:
        logging.error('Error while getting CPU models: '
                      'no domain CPU capabilities found')
        return {}

    dom_models = dict()
    for mode in cpucaps.findall('mode'):
        if mode.get('name') == cpu_mode and mode.get('supported') == 'yes':
            for model in mode.findall('model'):
                if cpu_mode == _CpuMode.CUSTOM:
                    usable = model.get('usable')
                else:
                    usable = 'yes'
                if model.text:
                    dom_models[model.text] = usable
    logging.debug('Supported CPU models: %s', dom_models)

    return dom_models


@cache.memoized
def compatible_cpu_models():
    c = libvirtconnection.get()
    arch = cpuarch.real()
    cpu_mode = _CpuMode.HOST_MODEL if cpuarch.is_ppc(arch) else _CpuMode.CUSTOM
    all_models = domain_cpu_models(c, arch, cpu_mode)
    compatible_models = [model for (model, usable)
                         in six.iteritems(all_models)
                         if usable == 'yes']
    if cpuarch.is_ppc(arch) and \
       'POWER9' in compatible_models and \
       'POWER8' not in compatible_models:
        compatible_models.append('POWER8')
    return list(set(["model_" + model for model in compatible_models]))


def _emulated_machines_from_caps_node(node):
    return list(set((itertools.chain.from_iterable(
        (
            (m.text, m.get('canonical'))
            if m.get('canonical') else
            (m.text,)
        )
        for m in node.iterfind('machine')))))


def _emulated_machines_from_caps_arch(arch, caps):
    arch_tag = caps.find('.//guest/arch[@name="%s"]' % arch)
    if arch_tag is None:
        logging.error('Error while looking for architecture '
                      '"%s" in libvirt capabilities', arch)
        return []

    return _emulated_machines_from_caps_node(arch_tag)


def _emulated_machines_from_caps_domain(arch, caps):
    domain_tag = caps.find(
        './/guest/arch[@name="%s"]/domain[@type="kvm"]' % arch)
    if domain_tag is None:
        logging.error('Error while looking for kvm domain (%s) '
                      'libvirt capabilities', arch)
        return []

    return _emulated_machines_from_caps_node(domain_tag)


def _get_libvirt_caps():
    conn = libvirtconnection.get()
    return conn.getCapabilities()
