import pandas as pd
import numpy as np

from typing import Callable, Dict

from py_replay_bg.input_validation import *


class InputValidatorReplay:
    """
    Class for validating the input of ReplayBG twin method.

    ...
    Attributes
    ----------
    data : pd.DataFrame
                Pandas dataframe which contains the data to be used by the tool.
    bw : float
        The patient's body weight.
    save_name : str
        A string used to label, thus identify, each output file and result.

    x0: list
        The initial model state.
    previous_data_name: str
        The name of the previous portion of data. To be used to initialize the initial conditions.

    twinning_method : str
        The method used to twin the model.

    bolus_source : str
        A string defining whether to use, during replay, the insulin bolus data contained in the 'data' timetable
        (if 'data'), or the boluses generated by the bolus calculator implemented via the provided
        'bolusCalculatorHandler' function.
    basal_source : str
        A string defining whether to use, during replay, the insulin basal data contained in the 'data' timetable
        (if 'data'), or the basal generated by the controller implemented via the provided
        'basalControllerHandler' function (if 'dss'), or fixed to the average basal rate used during
        twinning (if 'u2ss').
    cho_source : str
        A string defining whether to use, during replay, the CHO data contained in the 'data' timetable (if 'data'),
        or the CHO generated by the meal generator implemented via the provided 'mealGeneratorHandler' function.

    meal_generator_handler: Callable
        A callback function that implements a meal generator to be used during the replay of a given scenario.
    meal_generator_handler_params: dict
        A dictionary that contains the parameters to pass to the meal_generator_handler function.
    bolus_calculator_handler: Callable
        A callback function that implements a bolus calculator to be used during the replay of a given scenario.
    bolus_calculator_handler_params: dict
        A dictionary that contains the parameters to pass to the bolusCalculatorHandler function. It also serves
        as memory area for the bolusCalculatorHandler function.
    basal_handler: Callable
        A callback function that implements a basal controller to be used during the replay of a given scenario.
    basal_handler_params: dict
        A dictionary that contains the parameters to pass to the basalHandler function. It also serves as memory
        area for the basalHandler function.
    enable_hypotreatments: boolean
        A flag that specifies whether to enable hypotreatments during the replay of a given scenario.
    hypotreatments_handler: Callable
        A callback function that implements a hypotreatment strategy during the replay of a given scenario.
    hypotreatments_handler_params: dict
        A dictionary that contains the parameters to pass to the hypoTreatmentsHandler function. It also serves
        as memory area for the hypoTreatmentsHandler function.
    enable_correction_boluses: boolean
        A flag that specifies whether to enable correction boluses during the replay of a given scenario.
    correction_boluses_handler: Callable
        A callback function that implements a corrective bolus strategy during the replay of a given scenario.
    correction_boluses_handler_params: dict
        A dictionary that contains the parameters to pass to the correctionBolusesHandler function. It also serves
        as memory area for the correctionBolusesHandler function.

    save_suffix : string
        A string to be attached as suffix to the resulting output files' name.
    save_workspace: bool
        A flag that specifies whether to save the resulting workspace.

    n_replay: int
        The number of Monte Carlo replays to be performed. Ignored if twinning_method is 'map'.
    sensors: list[Sensors]
        The sensors to be used in each of the replay simulations.

    blueprint: str
            A string that specifies the blueprint to be used to create the digital twin.
    exercise: bool
        A boolean that specifies whether to use exercise model or not.

    Methods
    -------
    validate():
        Run the input validation process.
    """

    def __init__(self,
                 data: pd.DataFrame,
                 bw: float,
                 save_name: str,
                 x0: np.ndarray,
                 previous_data_name: str,
                 twinning_method: str,
                 bolus_source: str,
                 basal_source: str,
                 cho_source: str,
                 meal_generator_handler: Callable,
                 meal_generator_handler_params: Dict,
                 bolus_calculator_handler: Callable,
                 bolus_calculator_handler_params: Dict,
                 basal_handler: Callable,
                 basal_handler_params: Dict,
                 enable_hypotreatments: bool,
                 hypotreatments_handler: Callable,
                 hypotreatments_handler_params: Dict,
                 enable_correction_boluses: bool,
                 correction_boluses_handler: Callable,
                 correction_boluses_handler_params: Dict,
                 save_suffix: str,
                 save_workspace: bool,
                 n_replay: int,
                 sensors: list,
                 blueprint: str,
                 exercise: bool,
                 snack_absorption: float,
                 snack_absorption_delay: int,
                 hypotreatment_absorption: float,
                 custom_ra: CustomRaBase
                 ):
        self.data = data
        self.bw = bw
        self.save_name = save_name
        self.x0 = x0
        self.previous_data_name = previous_data_name
        self.twinning_method = twinning_method
        self.bolus_source = bolus_source
        self.basal_source = basal_source
        self.cho_source = cho_source
        self.meal_generator_handler = meal_generator_handler
        self.meal_generator_handler_params = meal_generator_handler_params
        self.bolus_calculator_handler = bolus_calculator_handler
        self.bolus_calculator_handler_params = bolus_calculator_handler_params
        self.basal_handler = basal_handler
        self.basal_handler_params = basal_handler_params
        self.enable_hypotreatments = enable_hypotreatments
        self.hypotreatments_handler = hypotreatments_handler
        self.hypotreatments_handler_params = hypotreatments_handler_params
        self.enable_correction_boluses = enable_correction_boluses
        self.correction_boluses_handler = correction_boluses_handler
        self.correction_boluses_handler_params = correction_boluses_handler_params
        self.save_suffix = save_suffix
        self.save_workspace = save_workspace
        self.n_replay = n_replay
        self.sensors = sensors
        self.blueprint = blueprint
        self.exercise = exercise
        self.snack_absorption = snack_absorption
        self.snack_absorption_delay = snack_absorption_delay
        self.hypotreatment_absorption = hypotreatment_absorption
        self.custom_ra = custom_ra

    def validate(self):
        """
        Run the input validation process.
        """

        # Validate the 'data' input
        DataValidator(modality='replay', data=self.data, blueprint=self.blueprint, exercise=self.exercise,
                      bolus_source=self.bolus_source, basal_source=self.basal_source,
                      cho_source=self.cho_source).validate()

        # Validate the 'bw' input
        BWValidator(bw=self.bw).validate()

        # Validate the 'save_name' input
        SaveNameValidator(save_name=self.save_name).validate()

        # Validate the 'save_name' input
        X0Validator(x0=self.x0).validate()

        # Validate the 'previous_data_name' input
        PreviousDataNameValidator(previous_data_name=self.previous_data_name).validate()

        # Validate the 'twinning_method' input
        TwinningMethodValidator(twinning_method=self.twinning_method).validate()

        # Validate the 'bolus_source' input
        BolusSourceValidator(bolus_source=self.bolus_source).validate()

        # Validate the 'basal_source' input
        BasalSourceValidator(basal_source=self.basal_source).validate()

        # Validate the 'cho_source' input
        CHOSourceValidator(cho_source=self.cho_source).validate()

        # Validate the 'meal_generator_handler' input
        MealGeneratorHandlerValidator(meal_generator_handler=self.meal_generator_handler).validate()

        # Validate the 'meal_generator_handler_params' input
        MealGeneratorHandlerParamsValidator(meal_generator_handler_params=self.meal_generator_handler_params).validate()

        # Validate the 'bolus_calculator_handler' input
        BolusCalculatorHandlerValidator(bolus_calculator_handler=self.bolus_calculator_handler).validate()

        # Validate the 'bolus_calculator_handler_params' input
        BolusCalculatorHandlerParamsValidator(
            bolus_calculator_handler_params=self.bolus_calculator_handler_params).validate()

        # Validate the 'basal_handler' input
        BasalHandlerValidator(basal_handler=self.basal_handler).validate()

        # Validate the 'basal_handler_params' input
        BasalHandlerParamsValidator(basal_handler_params=self.basal_handler_params).validate()

        # Validate the 'enable_hypotreatments' input
        EnableHypotreatmentsValidator(enable_hypotreatments=self.enable_hypotreatments).validate()

        # Validate the 'hypotreatments_handler' input
        HypotreatmentsHandlerValidator(hypotreatments_handler=self.hypotreatments_handler).validate()

        # Validate the 'hypotreatments_handler_params' input
        HypotreatmentsHandlerParamsValidator(
            hypotreatments_handler_params=self.hypotreatments_handler_params).validate()

        # Validate the 'enable_correction_boluses' input
        EnableCorrectionBolusesValidator(enable_correction_boluses=self.enable_correction_boluses).validate()

        # Validate the 'correction_boluses_handler' input
        CorrectionBolusesHandlerValidator(correction_boluses_handler=self.correction_boluses_handler).validate()

        # Validate the 'correction_boluses_handler_params' input
        CorrectionBolusesHandlerParamsValidator(
            correction_boluses_handler_params=self.correction_boluses_handler_params).validate()

        # Validate the 'save_suffix' input
        SaveSuffixValidator(save_suffix=self.save_suffix).validate()

        # Validate the 'save_workspace' input
        SaveWorkspaceValidator(save_workspace=self.save_workspace).validate()

        # Validate the 'n_replay' input
        NReplayValidator(n_replay=self.n_replay).validate()

        # Validate the 'sensors' input
        SensorsValidator(sensors=self.sensors).validate()

        # Validate the 'snack_absorption' input
        SnackAbsorptionValidator(snack_absorption=self.snack_absorption).validate()

        # Validate the 'snack_absorption_delay' input
        SnackAbsorptionDelayValidator(snack_absorption_delay=self.snack_absorption_delay).validate()

        # Validate the 'hypotreatment_absorption' input
        HypotreatmentAbsorptionValidator(hypotreatment_absorption=self.hypotreatment_absorption).validate()

        # Validate the 'custom_ra' input
        CustomRaValidator(custom_ra=self.custom_ra).validate()