from threading import (
    Condition,
)
from typing import (
    Generic,
    Callable,
    Sequence,
    Optional,
    TypeVar,
    List,
)


T = TypeVar("T")


class InFlightSyncedList(Generic[T]):
    def __init__(self, wait_condition: Condition):
        self.values: List[T] = []
        self.in_flight = {}

        self.stopped = False
        self.wait_condition = wait_condition

    def append(self, value: T):
        with self.wait_condition:
            self.values.append(value)
            self.wait_condition.notify_all()

    def extend(self, values: Sequence[T]):
        with self.wait_condition:
            for value in values:
                self.values.append(value)

    def retrieve(self, func: Callable[[T], bool]) -> Sequence[T]:
        with self.wait_condition:
            all_values = tuple(filter(func, self.values))
            ret = []
            for value in all_values:
                if value not in self.in_flight:
                    self.in_flight[value] = True
                    ret.append(value)

            return ret

    def first(self, func: Callable[[T], bool]) -> Optional[T]:
        with self.wait_condition:
            for value in self.values:
                if value not in self.in_flight and func(value):
                    self.in_flight[value] = True
                    return value

            return None

    def get_not_in_flight(self) -> Sequence[T]:
        with self.wait_condition:
            ret = []
            for value in self.values:
                if value not in self.in_flight:
                    ret.append(value)
            return ret

    def find(self, func: Callable[[T], bool]) -> Optional[T]:
        with self.wait_condition:
            for value in self.values:
                if func(value):
                    return value
            return None

    def remove(self, value: T) -> None:
        with self.wait_condition:
            if value in self.in_flight:
                del self.in_flight[value]
            try:
                self.values.remove(value)
            except ValueError:
                pass

    def remove_from_in_flight(self, value: T) -> None:
        with self.wait_condition:
            if value in self.in_flight:
                del self.in_flight[value]
                self.wait_condition.notify_all()

    def wait(self):
        with self.wait_condition:
            if self.stopped:
                return

            if len(self.values) > len(self.in_flight):
                return

            self.wait_condition.wait()

    def stop(self):
        with self.wait_condition:
            self.stopped = True
            self.wait_condition.notify_all()

    def sleep(self, timeout: float) -> None:
        with self.wait_condition:
            if self.stopped:
                return
            self.wait_condition.wait(timeout=timeout)

    def __enter__(self):
        self.wait_condition.acquire()

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.wait_condition.release()
