import abc
from queue import Queue
from threading import Lock, Thread
from typing import Any

from structlog import get_logger

LOGGER = get_logger(__name__)


class WorkerResults(abc.ABC):
    """Interface for Worker results."""

    @abc.abstractmethod
    def add_result(self, result: Any):
        """Add the given result to this results container."""


class ThreadWorker:
    """Perform work with threads."""

    def __init__(self, n_threads: int, worker_fn, initial_results: WorkerResults):
        """
        Create a worker that uses threads to do the work.

        :param n_threads: Number of threads to use.
        :param worker_fn: Function to perform the work.
        :param initial_results: Object to collect the results.
        """
        self.n_threads = n_threads
        self.work_queue = Queue()
        self.worker_fn = worker_fn
        self.results_lock = Lock()
        self.results = initial_results

    def _add_result(self, result):
        """
        Lock results container and add this result.

        :param result: Result to add.
        """
        with self.results_lock:
            self.results.add_result(result)

    def _do_work(self):
        """Perform the work on the queue."""
        while True:
            work_item = self.work_queue.get()
            try:
                result = self.worker_fn(work_item)
                self._add_result(result)
            except:
                LOGGER.warning("exception processing item", work_item=work_item, exc_info=1)

            self.work_queue.task_done()

    def queue_work(self, work_item):
        """
        Add work to the queue.

        :param work_item: Item to add to queue.
        """
        self.work_queue.put(work_item)

    def start(self):
        """Start threads and perform the work."""
        for _ in range(self.n_threads):
            t = Thread(target=self._do_work)
            t.daemon = True
            t.start()

    def block(self):
        """Wait for the worker threads to complete."""
        self.work_queue.join()

    def get_results(self):
        """
        Get the results generated by the work.

        :return: Results generated by threads.
        """
        with self.results_lock:
            return self.results
