from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import timedelta
from typing import Callable, TypeVar, Generic, Sequence

from numpy import ndarray

from ..options import Interval, Behavior


@dataclass
class RunOptions:
    bounds: Sequence[Interval]
    iterations: int
    behavior: Behavior
    seed: int


@dataclass
class Iteration:
    """Single optimizer iteration during a run.

    Attributes:
        robustness: The robustness value of the sample
        sample: The input generated by the optimizer
    """

    robustness: float
    sample: ndarray


@dataclass
class Run:
    """All iterations of a single run of the optimizer.

    Attributes:
        history: List of optimizer iterations
        seed: The initial seed of the random number generator
        runtime: Time elapsed during run
    """

    history: Sequence[Iteration]
    run_time: timedelta

    @property
    def best_iter(self) -> Iteration:
        return min(self.history, key=lambda i: i.robustness)

    def __len__(self) -> int:
        return len(self.history)


ObjectiveFn = Callable[[ndarray], float]
_T = TypeVar("_T", bound=Run, covariant=True)


class Optimizer(ABC, Generic[_T]):
    @abstractmethod
    def optimize(self, func: ObjectiveFn, options: RunOptions) -> _T:
        raise NotImplementedError()
