import logging
import random
from typing import Generic, Tuple, TypeVar, Optional, List, Protocol, runtime_checkable

import numpy as np
from autotrainer.core import get_verbose_logger

T = TypeVar("T", covariant=True)
V = TypeVar("V")

logger = get_verbose_logger(__name__)


@runtime_checkable
class ValueProvider(Protocol[T]):

    def get_next_value(self) -> T: ...


class ShuffledValueProvider(Generic[V]):

    def __init__(self, value: V, count: Optional[int] = None, *, rng: Optional[random.Random] = None):
        self._rng = rng if rng is not None else random.Random()
        self._index = 0

        if count is not None:
            if count < 1:
                raise ValueError(f"'count' must be >= 1, got {count}.")

            self._values: Optional[List[V]] = self._generate_values(value, count)
            self._shuffle()
            self._constant = None
        else:
            self._values = None
            self._constant = value

    def _generate_values(self, value: V, count: int) -> List[V]:
        ...

    def _shuffle(self):
        self._rng.shuffle(self._values)
        self._index = 0

    def get_next_value(self) -> V:
        if self._constant is not None:
            return self._constant

        result = self._values[self._index]
        self._index += 1
        if self._index >= len(self._values):
            self._shuffle()
        return result


class BooleanValueProvider(ShuffledValueProvider[bool], ValueProvider[bool]):
    def __init__(self, value: bool, count: Optional[int] = None, percentage: Optional[float] = None, *,
                 rng: Optional[random.Random] = None):
        has_count = count is not None
        has_percentage = percentage is not None

        if has_count != has_percentage:
            raise ValueError(
                "Both 'count' and 'percentage' must be provided together, or neither."
            )

        if has_percentage and not (0.0 <= percentage <= 1.0):
            raise ValueError(f"'percentage' must be between 0.0 and 1.0, got {percentage}.")

        self._percentage = percentage
        super().__init__(value, count, rng=rng)

    def _generate_values(self, value: bool, count: int) -> List[bool]:
        value_count = round(count * self._percentage)
        opposite_count = count - value_count
        return [value] * value_count + [not value] * opposite_count


class FloatValueProvider(ShuffledValueProvider[float], ValueProvider[float]):
    def __init__(self, value: float, count: Optional[int] = None, value_range: Optional[Tuple[float, float]] = None,
                 *, rng: Optional[random.Random] = None):
        has_count = count is not None
        has_range = value_range is not None

        if has_count != has_range:
            raise ValueError(
                "Both 'count' and 'value_range' must be provided together, or neither."
            )

        self._interval: float = 0.0
        self._value_range = value_range
        super().__init__(value, count, rng=rng)

    @property
    def interval(self) -> float:
        return self._interval

    def _generate_values(self, value: float, count: int) -> List[float]:
        values, step = np.linspace(self._value_range[0], self._value_range[1], num=count, retstep=True)
        self._interval = float(step)
        return values.tolist()
