from __future__ import absolute_import, unicode_literals

import logging
import signal
import threading

from pykka import ActorDeadError
from pykka.registry import ActorRegistry

from mopidy.compat import thread


logger = logging.getLogger(__name__)


SIGNALS = dict(
    (k, v) for v, k in signal.__dict__.items()
    if v.startswith('SIG') and not v.startswith('SIG_'))


def exit_process():
    logger.debug('Interrupting main...')
    thread.interrupt_main()
    logger.debug('Interrupted main')


def exit_handler(signum, frame):
    """A :mod:`signal` handler which will exit the program on signal."""
    logger.info('Got %s signal', SIGNALS[signum])
    exit_process()


def stop_actors_by_class(klass):
    actors = ActorRegistry.get_by_class(klass)
    logger.debug('Stopping %d instance(s) of %s', len(actors), klass.__name__)
    for actor in actors:
        actor.stop()


def stop_remaining_actors():
    num_actors = len(ActorRegistry.get_all())
    while num_actors:
        logger.error(
            'There are actor threads still running, this is probably a bug')
        logger.debug(
            'Seeing %d actor and %d non-actor thread(s): %s',
            num_actors, threading.active_count() - num_actors,
            ', '.join([t.name for t in threading.enumerate()]))
        logger.debug('Stopping %d actor(s)...', num_actors)
        ActorRegistry.stop_all()
        num_actors = len(ActorRegistry.get_all())
    logger.debug('All actors stopped.')


class BaseThread(threading.Thread):
    def __init__(self):
        super(BaseThread, self).__init__()
        # No thread should block process from exiting
        self.daemon = True

    def run(self):
        logger.debug('%s: Starting thread', self.name)
        try:
            self.run_inside_try()
        except KeyboardInterrupt:
            logger.info('Interrupted by user')
        except ImportError as e:
            logger.error(e)
        except ActorDeadError as e:
            logger.warning(e)
        except Exception as e:
            logger.exception(e)
        logger.debug('%s: Exiting thread', self.name)

    def run_inside_try(self):
        raise NotImplementedError
