import abc
import threading
import contextlib
import logging

import mini_buildd.config

LOG = logging.getLogger(__name__)


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.info(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, events, **kwargs):
        super().__init__(**kwargs)
        self._events = events
        self.lock = threading.Lock()

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

    def shutdown(self):
        self._events.put(mini_buildd.config.SHUTDOWN)

    def run(self):
        LOG.info(f"Starting event thread: {self}")
        event = self._events.get()
        while event is not mini_buildd.config.SHUTDOWN:
            try:
                LOG.info(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._events.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.info(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")
