<reponame>Kukuster/CI_methods_efficacy_analysis_toolkit
from typing import Callable, Literal, Tuple, Union
from abc import ABC, abstractmethod

from matplotlib.figure import Figure



CI_method = Callable[
    ...,
    Tuple[float, float]
]


# sort of "supported" themes. The type has to include "default"
plot_styles = Literal["default", "dark_background", "classic"]


class NoCoverageException(Exception):
    """When a user haven't calculated coverage before doing operations on it"""

class NoPlotException(Exception):
    """When a user haven't plotted the coverage before doing operations on the plot"""


class CImethod_efficacyToolkit(ABC):
    """A template for toolkit for studying efficacy of a particular CI method.
    A class has to inherit from this with specific implementations for studying 
    a CI method of particular kind (for population mean, for population proportion, etc.)

    Parameters
    ----------
    method : CI_method
        the method which efficacy you want to study

    method_name : str
        a human-readable name of the method.

    Attributes
    ----------
    method : CI_method
        the method which efficacy you want to study

    method_name : str
        a human-readable name of the method. Used in plotting

    confidence : float
        A number between 0 and 1.
        Confidence interval - coverage that you want to get.

    coverage : List | np.ndarray | Unknown
        Numbers necesserily between 0 and 100, usually of type `numpy.longdouble`.
        Coverage represents a proportion of cases that fall under the confidence interval produced
        by the given `method`.
        User can assess the efficacy of a CI method by comparing these values to the `confidence`.
        It makes sense to study the method not only for a single particular case,
        but for some ordered set of some parameters. Therefore, `coverage` is also an ordered set,
        of the same shape as the given parameters.
         - Values `< confidence` mean the `method` is more likely to cause a type I error.
         In simple words, this is bad because you would not be able say
         you are `confidence*100`% confident that the true population parameter
         lies within the interval calculated by the `method` around the calculated statistic.
         - Values `> confidence` mean the `method` is even less likely to cause a type I error,
         but may be more likely to cause a type II error.
         In simple words, it doesn't necessarily mean the `method` is bad, but it's
         just "concervative". Whether it is way too concenrvative or not is up to you.
         If you pass `0.95` (95%) to the `method`, and it gives you 99.5% coverage, 
         it is hell of a concervative method.

    figure : matplotlib.figure.Figure
        a matplotlib figure that's being generated by plotting the `coverage`

    """

    def __init__(self, method: CI_method, method_name: str):
        self._method: CI_method = method
        self._method_name: str = method_name
        pass


    @property
    def method(self):
        return self._method


    @property
    def method_name(self):
        return self._method_name


    @property
    def coverage(self):
        return self._coverage

    @coverage.setter
    def coverage(self, value):
        self._coverage = value


    @property
    def confidence(self):
        return self._confidence

    @confidence.setter
    def confidence(self, value: float):
        if not 0.01 < value < 1: raise ValueError(
            f"confidence level has to be a real value between 0 and 1. Got: confidence={value}")
        self._confidence = value


    @property
    def figure(self) -> Figure:
        return self._figure

    @figure.setter
    def figure(self, value: Figure):
        self._figure = value



    @abstractmethod
    def calculate_coverage_randomly(self,
            confidence: float,
            n_of_experiments: int
            ):
        """
        Calculates actual coverage of confidence interval for given desired `confidence`
        using a simulation with a number of random experiments (`n_of_experiments`)
        """
        self.confidence = confidence

        pass
        # here you have to set:
        # self.coverage = coverage
        pass


    @abstractmethod
    def calculate_coverage_analytically(self,
            confidence: float
            ):
        """
        Calculates actual coverage of confidence interval for given desired `confidence`
        using analytical solution
        """
        self.confidence = confidence

        pass
        # here you have to set:
        # self.coverage = coverage
        pass


    @abstractmethod
    def plot_coverage(self,
            plt_figure_num: Union[str, int],
            title: str,
            xlabel: str,
            ylabel: str,
            theme: plot_styles = "default"
            ):
        """Plots self.coverage, saves the figure to self.figure"""
        if not self.coverage: raise NoCoverageException(
            "you have to calculate coverage first before plotting it")

        pass
        # here you have to set:
        # self.figure = fig
        pass


    def show_plot(self):
        """Displays plotted figure `self.figure`"""
        if self.figure is None: raise NoPlotException(
            "you have to plot the figure before showing it")
        # https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.show
        self.figure.show()

    def save_plot(self, filename: str):
        """Saves plotted figure `self.figure` to file"""
        if self.figure is None: raise NoPlotException(
            "you have to plot the figure before saving it")
        self.figure.savefig(filename)


