# Copyright (C) 2018 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.

import re

import libvirt

from virtinst import log


class _LibvirtEnumMap(object):
    """
    Helper for mapping libvirt event int values to their API names
    """
    # Some values we define to distinguish between API objects
    (DOMAIN_EVENT,
     DOMAIN_AGENT_EVENT,
     NETWORK_EVENT,
     STORAGE_EVENT,
     NODEDEV_EVENT) = range(1, 6)

    # Regex map for naming all event types depending on the API object
    _EVENT_PREFIX = {
        DOMAIN_EVENT: "VIR_DOMAIN_EVENT_ID_",
        DOMAIN_AGENT_EVENT: "VIR_DOMAIN_EVENT_ID_AGENT_",
        NETWORK_EVENT: "VIR_NETWORK_EVENT_ID_",
        STORAGE_EVENT: "VIR_STORAGE_POOL_EVENT_ID_",
        NODEDEV_EVENT: "VIR_NODE_DEVICE_EVENT_ID_",
    }

    # Regex map for 'state' values returned from lifecycle and other events
    _DETAIL1_PREFIX = {
        "VIR_DOMAIN_EVENT_ID_LIFECYCLE": "VIR_DOMAIN_EVENT_[^_]+$",
        "VIR_DOMAIN_EVENT_ID_AGENT_LIFECYCLE": "VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_[^_]+$",
        "VIR_NETWORK_EVENT_ID_LIFECYCLE": "VIR_NETWORK_EVENT_[^_]+$",
        "VIR_STORAGE_POOL_EVENT_ID_LIFECYCLE": "VIR_STORAGE_POOL_EVENT_[^_]+$",
        "VIR_NODE_DEVICE_EVENT_ID_LIFECYCLE": "VIR_NODE_DEVICE_EVENT_[^_]+$",
    }

    # Regex map for 'reason' values returned from lifecycle and other events
    _DETAIL2_PREFIX = {
        "VIR_DOMAIN_EVENT_DEFINED": "VIR_DOMAIN_EVENT_DEFINED_",
        "VIR_DOMAIN_EVENT_UNDEFINED": "VIR_DOMAIN_EVENT_UNDEFINED_",
        "VIR_DOMAIN_EVENT_STARTED": "VIR_DOMAIN_EVENT_STARTED_",
        "VIR_DOMAIN_EVENT_SUSPENDED": "VIR_DOMAIN_EVENT_SUSPENDED_",
        "VIR_DOMAIN_EVENT_RESUMED": "VIR_DOMAIN_EVENT_RESUMED_",
        "VIR_DOMAIN_EVENT_STOPPED": "VIR_DOMAIN_EVENT_STOPPED_",
        "VIR_DOMAIN_EVENT_SHUTDOWN": "VIR_DOMAIN_EVENT_SHUTDOWN_",
        "VIR_DOMAIN_EVENT_PMSUSPENDED": "VIR_DOMAIN_EVENT_PMSUSPENDED_",
        "VIR_DOMAIN_EVENT_CRASHED": "VIR_DOMAIN_EVENT_CRASHED_",
    }

    VM_STATUS_ICONS = {
        libvirt.VIR_DOMAIN_BLOCKED: "state_running",
        libvirt.VIR_DOMAIN_CRASHED: "state_shutoff",
        libvirt.VIR_DOMAIN_PAUSED: "state_paused",
        libvirt.VIR_DOMAIN_RUNNING: "state_running",
        libvirt.VIR_DOMAIN_SHUTDOWN: "state_shutoff",
        libvirt.VIR_DOMAIN_SHUTOFF: "state_shutoff",
        libvirt.VIR_DOMAIN_NOSTATE: "state_running",
        libvirt.VIR_DOMAIN_PMSUSPENDED: "state_paused",
    }

    @staticmethod
    def pretty_run_status(status, has_managed_save):
        if status == libvirt.VIR_DOMAIN_RUNNING:
            return _("Running")
        elif status == libvirt.VIR_DOMAIN_PAUSED:
            return _("Paused")
        elif status == libvirt.VIR_DOMAIN_SHUTDOWN:
            return _("Shutting Down")  # pragma: no cover
        elif status == libvirt.VIR_DOMAIN_SHUTOFF:
            if has_managed_save:
                return _("Saved")
            else:
                return _("Shutoff")
        elif status == libvirt.VIR_DOMAIN_CRASHED:
            return _("Crashed")
        elif status == libvirt.VIR_DOMAIN_PMSUSPENDED:
            return _("Suspended")

        log.debug(  # pragma: no cover
                "Unknown status %s, returning 'Unknown'", status)
        return _("Unknown")  # pragma: no cover

    @staticmethod
    def pretty_status_reason(status, reason):
        def key(x, y):
            return getattr(libvirt, "VIR_DOMAIN_" + x, y)
        reasons = {
            libvirt.VIR_DOMAIN_RUNNING: {
                key("RUNNING_BOOTED", 1):             _("Booted"),
                key("RUNNING_MIGRATED", 2):           _("Migrated"),
                key("RUNNING_RESTORED", 3):           _("Restored"),
                key("RUNNING_FROM_SNAPSHOT", 4):      _("From snapshot"),
                key("RUNNING_UNPAUSED", 5):           _("Unpaused"),
                key("RUNNING_MIGRATION_CANCELED", 6): _("Migration canceled"),
                key("RUNNING_SAVE_CANCELED", 7):      _("Save canceled"),
                key("RUNNING_WAKEUP", 8):             _("Event wakeup"),
                key("RUNNING_CRASHED", 9):            _("Crashed"),
            },
            libvirt.VIR_DOMAIN_PAUSED: {
                key("PAUSED_USER", 1):                _("User"),
                key("PAUSED_MIGRATION", 2):           _("Migrating"),
                key("PAUSED_SAVE", 3):                _("Saving"),
                key("PAUSED_DUMP", 4):                _("Dumping"),
                key("PAUSED_IOERROR", 5):             _("I/O error"),
                key("PAUSED_WATCHDOG", 6):            _("Watchdog"),
                key("PAUSED_FROM_SNAPSHOT", 7):       _("From snapshot"),
                key("PAUSED_SHUTTING_DOWN", 8):       _("Shutting down"),
                key("PAUSED_SNAPSHOT", 9):            _("Creating snapshot"),
                key("PAUSED_CRASHED", 10):            _("Crashed"),
            },
            libvirt.VIR_DOMAIN_SHUTDOWN: {
                key("SHUTDOWN_USER", 1):              _("User"),
            },
            libvirt.VIR_DOMAIN_SHUTOFF: {
                key("SHUTOFF_SHUTDOWN", 1):           _("Shut Down"),
                key("SHUTOFF_DESTROYED", 2):          _("Destroyed"),
                key("SHUTOFF_CRASHED", 3):            _("Crashed"),
                key("SHUTOFF_MIGRATED", 4):           _("Migrated"),
                key("SHUTOFF_SAVED", 5):              _("Saved"),
                key("SHUTOFF_FAILED", 6):             _("Failed"),
                key("SHUTOFF_FROM_SNAPSHOT", 7):      _("From snapshot"),
            },
            libvirt.VIR_DOMAIN_CRASHED: {
                key("CRASHED_PANICKED", 1):           _("Panicked"),
            }
        }
        return reasons.get(status) and reasons[status].get(reason)

    def __init__(self):
        self._mapping = {}

    def _make_map(self, regex):
        # Run the passed regex over dir(libvirt) output
        ret = {}
        for key in [a for a in dir(libvirt) if re.match(regex, a)]:
            val = getattr(libvirt, key)
            if type(val) is not int:  # pragma: no cover
                log.debug("libvirt regex=%s key=%s val=%s "
                    "isn't an integer", regex, key, val)
                continue
            if val in ret:  # pragma: no cover
                log.debug("libvirt regex=%s key=%s val=%s is already "
                    "in dict as key=%s", regex, key, val, regex[val])
                continue
            ret[val] = key
        return ret

    def _get_map(self, key, regex):
        if regex is None:
            return {}
        if key not in self._mapping:
            self._mapping[key] = self._make_map(regex)
        return self._mapping[key]

    def _make_strs(self, api, event, detail1, detail2):
        eventstr = str(event)
        detail1str = str(detail1)
        detail2str = str(detail2)
        eventmap = self._get_map(api, self._EVENT_PREFIX[api])

        if eventmap:
            if event not in eventmap:
                event = next(iter(eventmap))  # pragma: no cover
            eventstr = eventmap[event]
            detail1map = self._get_map(eventstr,
                    self._DETAIL1_PREFIX.get(eventstr))
            if detail1 in detail1map:
                detail1str = detail1map[detail1]
                detail2map = self._get_map(detail1str,
                        self._DETAIL2_PREFIX.get(detail1str))
                if detail2 in detail2map:
                    detail2str = detail2map[detail2]

        return eventstr, detail1str, detail2str

    def _state_str(self, api, detail1, detail2):
        ignore, d1str, d2str = self._make_strs(api, 0,
                detail1, detail2)
        return "state=%s reason=%s" % (d1str, d2str)

    def domain_lifecycle_str(self, detail1, detail2):
        return self._state_str(self.DOMAIN_EVENT, detail1, detail2)
    def network_lifecycle_str(self, detail1, detail2):
        return self._state_str(self.NETWORK_EVENT, detail1, detail2)
    def storage_lifecycle_str(self, detail1, detail2):
        return self._state_str(self.STORAGE_EVENT, detail1, detail2)
    def nodedev_lifecycle_str(self, detail1, detail2):
        return self._state_str(self.NODEDEV_EVENT, detail1, detail2)
    def domain_agent_lifecycle_str(self, detail1, detail2):
        return self._state_str(self.DOMAIN_AGENT_EVENT, detail1, detail2)


LibvirtEnumMap = _LibvirtEnumMap()
