import abc
import contextlib
import logging
import queue
import threading

from mini_buildd import config, util

LOG = logging.getLogger(__name__)


class NoLock():
    """
    Debug/temporary only: Experienced dead locks -- not reliably reproducible for now

    May-be-related to our locking here. Use this to exclude our locking once we can
    reproduce the lockdown.
    """

    def acquire(self):
        pass

    def release(self):
        pass

    def __enter__(self):
        pass

    def __exit__(self, _type, _value, _traceback):
        pass


class Thread(threading.Thread):
    @abc.abstractmethod
    def shutdown(self):
        pass

    def __init__(self, subthreads=None, **kwargs):
        super().__init__(**kwargs, name=self.__class__.__name__)
        self.subthreads = [] if subthreads is None else subthreads

    def __str__(self):
        return self.__class__.__name__

    def start(self):
        super().start()
        for t in self.subthreads:
            t.start()

    def join(self, timeout=None):
        for t in reversed(self.subthreads):
            t.shutdown()
            t.join()
        super().join()

    @abc.abstractmethod
    def mbd_run(self):
        pass

    def run(self):
        try:
            LOG.info("Starting thread '%s'", self)
            self.mbd_run()
        except BaseException as e:
            util.log_exception(LOG, f"Error in {self}", e)
        finally:
            LOG.info("Finished thread '%s'", self)


class DeferredThread(Thread):
    def __init__(self, limiter, **kwargs):
        self.running = None
        self._limiter = limiter
        self._shutdown = None
        super().__init__(**kwargs)

    @abc.abstractmethod
    def run_deferred(self):
        pass

    def shutdown(self):
        self._shutdown = config.SHUTDOWN

    def mbd_run(self):
        self._limiter.put(None)
        try:
            with contextlib.closing(util.StopWatch()) as self.running:
                self.run_deferred()
        finally:
            self._limiter.get()


class EventThread(Thread):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.queue = queue.Queue()
        self.lock = threading.Lock()

    @abc.abstractmethod
    def run_event(self, event):
        pass

    def shutdown(self):
        self.queue.put(config.SHUTDOWN)

    def mbd_run(self):
        event = self.queue.get()
        while event is not config.SHUTDOWN:
            try:
                LOG.debug("Thread %s got event: %s", self, event)
                self.run_event(event)
            except BaseException as e:
                util.log_exception(LOG, f"Thread {self}: Error in event {event} (ignored)", e)
            finally:
                event = self.queue.get()


class PollerThread(Thread):
    def __init__(self, sleep=60, **kwargs):
        super().__init__(**kwargs)
        self._shutdown = None
        self._timer = None
        self._sleep = sleep

    @abc.abstractmethod
    def run_poller(self):
        pass

    def shutdown(self):
        self._shutdown = config.SHUTDOWN
        try:
            self._timer.cancel()
        except BaseException as e:
            LOG.warning("%s: Poll timer cancellation failed: %s", self, e)

    def mbd_run(self):
        while self._shutdown is not config.SHUTDOWN:
            try:
                # LOG.debug(f"{self}: {self._sleep} seconds until next poll...")
                self._timer = threading.Timer(self._sleep, self.run_poller)
                self._timer.run()
            except BaseException as e:
                util.log_exception(LOG, f"Thread {self}: Error in polled action (ignored)", e)
