# Path: pyramids/sample_utils.py
# class SampleUtils:
#     def load(file_path: str) -> SampleSet:
#     def save(samples: SampleSet, file_path: str) -> None:


from typing import NewType, NamedTuple, Callable, Iterable, Tuple, Optional
from pyramids.sample_utils import SampleSet, Input, Target
Attempt = NewType('Attempt', str)
Result = NamedTuple('Result', [('input', Input), ('attempt', Attempt), ('target', Target),
                               ('score', float)])
Failure = NamedTuple('Failure', [('input', Input), ('target', Target), ('first_attempt', Attempt),
                                 ('attempt_count', int)])
IndividualTally = NamedTuple('Score', [('attempt_count', int), ('success', bool),
                                       ('first_attempt', Attempt), ('first_attempt_score', float)])
BatchTally = NamedTuple('Score', [('sample_count', int), ('failure_count', int),
                                  ('avg_first_attempt_score', float), ('success_rate', float)])


Validator = Callable[[Attempt, Target], float]
FeedbackReceiver = Callable[[float], None]
AttemptGenerator = Callable[[Input, Target], Iterable[Tuple[Attempt, Optional[FeedbackReceiver]]]]
ResultCallback = Callable[[Result], None]
FailureCallback = Callable[[Failure], None]
AttemptFunction = Callable[[Input], Attempt]


class ModelBatchController:
    """Handles batchwise training and evaluation of models."""

    @staticmethod
    def _default_validator(attempt: Attempt, target: Target):
        return attempt == target

    def __init__(self, output_validator: Validator = None, threshold: float = 1):
        self._validate_output = output_validator or self._default_validator
        self._threshold = threshold


# Path: pyramids/sample_utils.py
# class SampleUtils:
#     def load(file_path: str) -> SampleSet:
#     def save(samples: SampleSet, file_path: str) -> None:


# Path: pyramids/batching.py
from typing import NewType, NamedTuple, Callable, Iterable, Tuple, Optional
from pyramids.sample_utils import SampleSet, Input, Target

Attempt = NewType('Attempt', str)
Result = NamedTuple('Result', [('input', Input), ('attempt', Attempt), ('target', Target),
                               ('score', float)])
Failure = NamedTuple('Failure', [('input', Input), ('target', Target), ('first_attempt', Attempt),
                                 ('attempt_count', int)])
IndividualTally = NamedTuple('Score', [('attempt_count', int), ('success', bool),
                                       ('first_attempt', Attempt), ('first_attempt_score', float)])
BatchTally = NamedTuple('Score', [('sample_count', int), ('failure_count', int),
                                  ('avg_first_attempt_score', float), ('success_rate', float)])


Validator = Callable[[Attempt, Target], float]
FeedbackReceiver = Callable[[float], None]
AttemptGenerator = Callable[[Input, Target], Iterable[Tuple[Attempt, Optional[FeedbackReceiver]]]]
ResultCallback = Callable[[Result], None]
FailureCallback = Callable[[Failure], None]
AttemptFunction = Callable[[Input], Attempt]


class ModelBatchController:
    """Handles batchwise training and evaluation of models."""

    @staticmethod
    def _default_validator(attempt: Attempt, target: Target):
        return attempt == target

    def __init__(self, output_validator: Validator = None, threshold: float = 1):
        self._validate_output = output_validator or self._default_validator
        self._threshold = threshold


    def run_batch(self, samples: SampleSet, attempt_generator: AttemptGenerator,