import abc
import threading
import queue
import contextlib
import logging

import mini_buildd.config

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)
        self.subthreads = [] if subthreads is None else subthreads

    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()


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 = mini_buildd.config.SHUTDOWN

    def run(self):
        self._limiter.put(None)
        try:
            LOG.debug(f"{self}: Starting deferred")
            with contextlib.closing(mini_buildd.misc.StopWatch()) as self.running:
                self.run_deferred()
        except BaseException as e:
            mini_buildd.log_exception(LOG, f"Error in thread (ignored): {self}", e)
        finally:
            self._limiter.get()
        LOG.debug(f"{self}: FINISHED")


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(mini_buildd.config.SHUTDOWN)

    def run(self):
        LOG.debug(f"Starting event thread: {self}")
        event = self.queue.get()
        while event is not mini_buildd.config.SHUTDOWN:
            try:
                LOG.debug(f"{self}: Event {event}")
                self.run_event(event)
            except BaseException as e:
                mini_buildd.log_exception(LOG, f"Error in thread (ignored): {self}", e)
            event = self.queue.get()
        LOG.debug(f"{self}: FINISHED")


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 = mini_buildd.config.SHUTDOWN
        try:
            self._timer.cancel()
        except BaseException as e:
            LOG.warning(f"{self}: Poll timer cancellation failed: {e}")

    def run(self):
        LOG.debug(f"Starting poller thread: {self}")
        while self._shutdown is not mini_buildd.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:
                mini_buildd.log_exception(LOG, f"Error in thread (ignored): {self}", e)
        LOG.debug(f"{self}: FINISHED")
