from abc import ABC, abstractmethod
from collections import defaultdict
from collections.abc import Sequence, Iterable
from typing import Protocol

from altbacken.core.annealing import AnnealingState
from altbacken.internal.analysis.base import AnalysisResult, Analyzer


class Report[T](Protocol):
    """
    Protocol for defining a report callable used in an annealing process.

    This class represents a callable object that can be used to log or process the
    state during an annealing algorithm. It provides a structure for integrating
    custom reporting or monitoring logic into the annealing process.

    Attributes:
        None
    """
    def __call__(self, state: AnnealingState[T]) -> None:
        """

        Args:
            state:

        Returns:

        """


class SimpleReport[T](ABC):
    """
    Provides an abstract reporting mechanism for processing and outputting
    analysis results.

    This class serves as a base class for implementing specific reporting
    strategies that aggregate and present the output of multiple analyzers
    applied to a given state. It requires concrete implementation of the
    reporting behavior through the `_report_analysis` method.

    Attributes:
        analyzers (Sequence[Analyzer[T]]): A sequence of Analyzer instances
            that are invoked to process the given state and produce analysis
            results.
    """
    def __init__(self, analyzers: Sequence[Analyzer[T]]):
        self._analyzers: Sequence[Analyzer[T]] = tuple(analyzers)

    def __call__(self, state: AnnealingState[T]) -> None:
        return self._report_analysis(
            filter(None, (analyzer(state) for analyzer in self._analyzers))
        )

    @abstractmethod
    def _report_analysis(self, results: Iterable[AnalysisResult]) -> None:
        """
        Reports the analysis results.

        This abstract method is intended to handle the analysis result reporting process. Implementing
        classes should define the specific behaviors of how the analysis results are reported.

        Args:
            results (Iterable[AnalysisResult]): An iterable collection of AnalysisResult objects
                containing the analysis data to be reported.
        """



class BufferingReport[T](ABC):
    """
    Handles buffering and reporting analysis results produced by analyzers.

    This abstract base class provides a mechanism to collect and buffer analysis
    results generated by a sequence of analyzers. The results are categorized
    by topics and stored in buffers. The class implements a callable interface
    for processing results, with the option for subclasses to define how the
    buffered data is managed or processed further. This class must be subclassed
    to provide an implementation for the `close` method.

    Attributes:
        analyzers (Sequence[Analyzer[T]]): Immutable sequence of analyzers to process
            the `AnnealingState` and produce analysis results.
    """
    def __init__(self, analyzers: Sequence[Analyzer[T]]):
        self._analyzers: Sequence[Analyzer[T]] = tuple(analyzers)
        self._buffers: dict[str, list[AnalysisResult]] = defaultdict(list)

    def __call__(self, state: AnnealingState[T]) -> None:
        for result in filter(None, (analyzer(state) for analyzer in self._analyzers)):
            self._buffers[result["topic"]].append(result)

    @abstractmethod
    def close(self):
        """
        Provides an abstract method for handling the closure or termination of resources in derived classes.

        Subclasses implementing this method must define the specific procedure for releasing or closing the resource they manage.

        Raises:
            NotImplementedError: If the method is not implemented by a subclass.
        """


class MultiReport[T]:
    """
    Combines and manages multiple reporting objects.

    Allows combining multiple `Report` objects, enabling a unified usage for
    monitoring or reporting activities in processes such as simulated annealing
    or batch operations. It provides a callable interface to invoke all combined
    reports with a given state and ensures proper handling of resources by
    providing a `close` method when necessary.

    Attributes:
        reports (Sequence[Report[T]]): Collection of `Report` objects to manage.
    """
    def __init__(self, reports: Sequence[Report[T]]):
        self._reports: Sequence[Report[T]] = tuple(reports)

    def __call__(self, state: AnnealingState[T]) -> None:
        for report in self._reports:
            report(state)

    def close(self):
        for report in self._reports:
            if isinstance(report, BufferingReport):
                report.close()