"""Helpers for libvirt."""

import logging
from itertools import chain

import pyev
import libvirt


logger = logging.getLogger(__name__)


#: corresponding name for enum state of libvirt domains
# see http://libvirt.org/html/libvirt-libvirt.html#virDomainState
DOMAIN_STATES = (
    'stopped',  # 0 no state
    'running',  # 1 running
    'blocked',  # 2 blocked
    'paused',  # 3 paused
    'running',  # 4 shutdown
    'stopped',  # 5 shuttoff
    'crashed',  # 6 crashed
    'suspended',  # 7 suspended
)


STORAGE_STATES = (
    'inactive',
    'building',
    'running',
    'degraded',
    'inaccessible',
    '???',  # 5
)


#: libvirt events
EVENTS = (
    'Added',
    'Removed',
    'Started',
    'Suspended',
    'Resumed',
    'Stopped',
    'Saved',
    'Restored',
)


# following event loop implementation was inspired by libvirt python example
# but updated to work with libev
class LoopHandler(object):
    """This class contains the data we need to track for a single file handle.

    """
    def __init__(self, loop, handle, fd, events, cb, opaque):
        self.handle = handle
        self.fd = fd
        self._events = self.virt_to_ev(events)
        self._cb = cb
        self.opaque = opaque
        self.watcher = loop.io(self.fd, self._events, self.ev_cb)

    def ev_to_virt(self, events):
        """Convert libev events into libvirt one."""
        result = 0
        if events & pyev.EV_READ:
            result |= libvirt.VIR_EVENT_HANDLE_READABLE
        if events & pyev.EV_WRITE:
            result |= libvirt.VIR_EVENT_HANDLE_WRITABLE

        return result

    def virt_to_ev(self, events):
        """Convert libvirt event to libev one."""
        result = 0
        if events & (libvirt.VIR_EVENT_HANDLE_READABLE |
                     libvirt.VIR_EVENT_HANDLE_ERROR |
                     libvirt.VIR_EVENT_HANDLE_HANGUP):
            result |= pyev.EV_READ
        if events & libvirt.VIR_EVENT_HANDLE_WRITABLE:
            result |= pyev.EV_WRITE

        return result

    def _set(self):
        self.watcher.stop()
        if self._events != 0:
            self.watcher.set(self.fd, self._events)
            self.watcher.start()

    @property
    def events(self):
        return self._events

    @events.setter
    def events(self, events):
        self._events = self.virt_to_ev(events)
        self._set()

    def start(self):
        self.watcher.start()

    def stop(self):
        self.watcher.stop()

    def ev_cb(self, watcher, revents):
        # convert events
        events = self.ev_to_virt(revents)
        self._cb(self.handle, self.watcher.fd, events, self.opaque[0],
                 self.opaque[1])


class LoopTimer(object):
    """This class contains the data we need to track for a single periodic
    timer.

    """
    def __init__(self, loop, timer, interval, cb, opaque):
        self.timer = timer
        self._interval = float(interval)
        self._cb = cb
        self.opaque = opaque
        self.watcher = None
        self.loop = loop
        self._set()

    def _set(self):
        self.stop()
        if self._interval >= 0.:  # libvirt sends us interval == -1
            self.watcher = self.loop.timer(self._interval, self._interval,
                                           self.ev_cb)
        else:
            self.watcher = None
        self.start()

    @property
    def interval(self):
        return self._interval

    @interval.setter
    def interval(self, value):
        self._interval = float(value)
        self._set()

    def start(self):
        if self.watcher is not None:
            self.watcher.start()

    def stop(self):
        if self.watcher is not None:
            self.watcher.stop()

    def ev_cb(self, *args):
        self._cb(self.timer, self.opaque[0], self.opaque[1])


class EventLoop(object):
    """This class is used as an interface between the libvirt event handling and
    the main pyev loop.

    It cannot be used from other threads.
    """
    def __init__(self, loop):
        """
        :param loop: pyev loop instance
        """
        self.loop = loop

        def counter():
            count = 0
            while True:
                yield count
                count += 1

        self.handle_id = counter()
        self.timer_id = counter()
        self.handles = dict()
        self.timers = dict()

    def add_handle(self, fd, events, cb, opaque):
        """Registers a new file handle 'fd', monitoring  for 'events' (libvirt
        event constants), firing the callback  cb() when an event occurs.
        Returns a unique integer identier for this handle, that should be
        used to later update/remove it.

        Note: unlike in the libvirt example, we don't use an interrupt trick as
        we run everything in the same thread, furthermore, calling start watcher
        method from a different thread could be dangerous.
        """
        handle_id = self.handle_id.next()

        h = LoopHandler(self.loop, handle_id, fd, events, cb, opaque)
        h.start()
        self.handles[handle_id] = h
        # logger.debug('Add handle %d fd %d events %d', handle_id, fd, events)
        return handle_id

    def add_timer(self, interval, cb, opaque):
        """Registers a new timer with periodic expiry at 'interval' ms,
        firing cb() each time the timer expires. If 'interval' is -1,
        then the timer is registered, but not enabled.
        Returns a unique integer identier for this handle, that should be
        used to later update/remove it.

        Note: same note as for :method:`add_handle` applies here
        """
        timer_id = self.timer_id.next()

        h = LoopTimer(self.loop, timer_id, interval, cb, opaque)
        h.start()
        self.timers[timer_id] = h
        # logger.debug('Add timer %d interval %d', timer_id, interval)
        return timer_id

    def update_handle(self, handle_id, events):
        """Change the set of events to be monitored on the file handle.

        """
        h = self.handles.get(handle_id)
        if h:
            h.events = events
            # logger.debug('Update handle %d fd %d events %d', handle_id, h.fd, events)

    def update_timer(self, timer_id, interval):
        """Change the periodic frequency of the timer.

        """
        t = self.timers.get(timer_id)
        if t:
            t.interval = interval

    def remove_handle(self, handle_id):
        """Stop monitoring for events on the file handle.

        """
        h = self.handles.pop(handle_id, None)
        if h:
            h.stop()
            # logger.debug('Remove handle %d', handle_id)

    def remove_timer(self, timer_id):
        """Stop firing the periodic timer.

        """
        t = self.timers.pop(timer_id, None)
        if t:
            t.stop()
            # logger.debug('Remove timer %d', timer_id)

    def stop(self):
        for handl in chain(self.handles.itervalues(), self.timers.itervalues()):
            handl.stop()

        self.handles = dict()
        self.timers = dict()