import time
from typing import Any, List, ClassVar
from threading import Thread

from ._WorkerThread import WorkerThread
from ._ThreadPoolParameters import ThreadPoolParameters


class ZeroMqThreadPool(object):
    def __init__(self, context, parameters, worker_address, is_daemon, thread_class, *args, **kwargs):
        # type: (Any, ThreadPoolParameters, str, bool, ClassVar, Any, Any) -> None
        self.context = context
        self.parameters = parameters
        self.worker_address = worker_address
        self.thread_class = thread_class
        self.args = args
        self.kwargs = kwargs

        self.threads = []  # type: List[WorkerThread]
        for i in range(0, self.parameters.initial_count, 1):
            self.create_worker_thread()

        self.monitor_thread = Thread(target=self.monitor)
        self.monitor_thread.daemon = is_daemon
        self.monitor_thread.start()

    def get_current_thread_count(self):
        return len(self.threads)

    def join(self):
        self.monitor_thread.join()

    def dispose(self):
        for thread in self.threads:
            thread.stop()

        for thread in self.threads:
            thread.join()

        self.threads.clear()
        self.monitor_thread.join()

    def create_worker_thread(self):
        args = [self.context, self.worker_address, self.parameters.timeout]
        args.extend(self.args)
        thread = self.thread_class(*args, **self.kwargs)
        thread.start()
        self.threads.append(thread)

    def monitor(self):
        while len(self.threads) > 0:
            time.sleep(self.parameters.monitor_timeout)
            busy_threads_count = 0
            idle_threads = []
            for thread in self.threads:
                if thread.is_working:
                    busy_threads_count += 1
                elif thread.is_idle:
                    idle_threads.append(thread)

            if self.must_increment_threads(busy_threads_count):
                increment = self.calculate_increment()
                for i in range(0, increment, 1):
                    self.create_worker_thread()

            if self.must_stop_threads(idle_threads):
                for thread in idle_threads:
                    thread.stop()
                    self.threads.remove(thread)

                    if len(self.threads) == self.parameters.initial_count:
                        break

    def must_increment_threads(self, busy_threads_count):
        return busy_threads_count == len(self.threads) and len(self.threads) < self.parameters.max_count

    def calculate_increment(self):
        if len(self.threads) + self.parameters.increment <= self.parameters.max_count:
            return self.parameters.increment
        else:
            return self.parameters.max_count - len(self.threads)

    def must_stop_threads(self, idle_threads):
        return len(self.threads) > self.parameters.initial_count and len(idle_threads) > 0
