import numpy as np
import pandas as pd

from py_replay_bg.environment import Environment
from py_replay_bg.model.t1d_model import T1DModel

from py_replay_bg.sensors import CGM, Sensors
from py_replay_bg.dss import DSS
from py_replay_bg.dss.default_dss_handlers import default_meal_generator_handler, standard_bolus_calculator_handler, \
    default_basal_handler, ada_hypotreatments_handler, corrects_above_250_handler

from py_replay_bg.data import ReplayBGData

from py_replay_bg.identification.mcmc import MCMC
from py_replay_bg.replay import Replayer
from py_replay_bg.visualizer import Visualizer

from py_replay_bg.input_validation import InputValidator

import os

import pickle

from py_agata.py_agata import Agata
from py_agata.utils import glucose_vector_to_dataframe
from py_agata.error import *


class ReplayBG:
    """
    Core class of ReplayBG
    
    ...
    Attributes 
    ----------
    environment: Environment
        An object that represents the hyperparameters to be used by ReplayBG environment.
    model: Model
        An object that represents the physiological model to be used by ReplayBG.
    sensors: Sensors
        An object that represents the sensors to be used by ReplayBG.
    mcmc: MCMC
        An object that represents the hyperparameters of the MCMC identification procedure.
    dss: DSS
        An object that represents the hyperparameters of the integrated decision support system.

    Methods
    -------
    run():
        Runs ReplayBG.
    """

    def __init__(self, modality, data, bw, scenario, save_name, save_folder,
                 yts=5, glucose_model='IG', pathology='t1d', exercise=False, seed=1,
                 bolus_source='data', basal_source='data', cho_source='data',
                 cgm_model='CGM',
                 n_steps=10000, to_sample=1000, save_chains=False,
                 CR=10, CF=40, GT=120,
                 meal_generator_handler=default_meal_generator_handler, meal_generator_handler_params={},
                 bolus_calculator_handler=standard_bolus_calculator_handler, bolus_calculator_handler_params={},
                 basal_handler=default_basal_handler, basal_handler_params={},
                 enable_hypotreatments=False, hypotreatments_handler=ada_hypotreatments_handler,
                 hypotreatments_handler_params={},
                 enable_correction_boluses=False, correction_boluses_handler=corrects_above_250_handler,
                 correction_boluses_handler_params={},
                 save_suffix='',
                 save_workspace=True,
                 parallelize=False,
                 n_processes=None,
                 plot_mode=True, verbose=True):
        """
        Constructs all the necessary attributes for the ReplayBG object.

        Parameters
        ----------
        modality : string, {'identification', 'replay'}
            A string that specifies if the function will be used to identify 
            the ReplayBG model on the given data or to replay the scenario specified by 
            the given data.
        data: pd.DataFrame
            Pandas dataframe which contains the data to be used by the tool.
        bw: double
            The patient's body weight.
        scenario: string, {'single-meal', 'multi-meal'}
            A string that specifies whether the given scenario refers to a single-meal scenario or a multi-meal scenario.
        save_name : string
            A string used to label, thus identify, each output file and result.

        yts: int, optional, default : 5
            An integer that specifies the data sample time (in minutes).
        glucose_model: string, {'IG','BG'}
            The model equation to be used as measured glucose.
        pathology: string, {'t1d', 't2d', 'pbh', 'healthy'}, optional, default: 't1d' 
            A string that specifies the patient pathology.
        exercise: boolean, optional, default : False
            A boolean that specifies whether to simulate exercise or not.
        seed: int, optional, default: 1
            An integer that specifies the random seed. For reproducibility.

        bolus_source : string, {'data', or 'dss'}, optional, default : 'data'
            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 : string, {'data', 'u2ss', or 'dss'}, optional, default : 'data'
            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 identification (if 'u2ss').
        cho_source : string, {'data', 'generated'}, optional, default : 'data'
            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.
        
        cgm_model: string, {'CGM','IG'}, optional, default : 'CGM'
            A string that specify the cgm model selection.
            If IG is selected, CGM measure will be the noise-free IG state at the current time.

        n_steps: int, optional, default : 10000
            Number of steps to use for the main chain. This is ignored if modality is 'replay'.
        to_sample: int, optional, default : 1000
            Number of samples to generate via the copula. This is ignored if modality is 'replay'.
        save_chains: bool, optional, default : False
            A flag that specifies whether to save the resulting mcmc chains and copula samplers.

        CR: double, optional, default : 10
            The carbohydrate-to-insulin ratio of the patient in g/U to be used by the integrated decision support system.
        CF: double, optional, default : 40
            The correction factor of the patient in mg/dl/U to be used by the integrated decision support system.
        GT: double, optional, default : 120
            The target glucose value in mg/dl to be used by the decsion support system modules.
        meal_generator_handler: function, optional, default : default_meal_generator_handler
            A callback function that implements a meal generator to be used during the replay of a given scenario.
        meal_generator_handler_params: dict, optional, default : {}
            A dictionary that contains the parameters to pass to the meal_generator_handler function.
        bolus_calculator_handler: function, optional, default : standard_bolus_calculator_handler
            A callback function that implements a bolus calculator to be used during the replay of a given scenario.
        bolus_calculator_handler_params: dict, optional, default : {}
            A dictionary that contains the parameters to pass to the bolusCalculatorHandler function. It also serves as memory
            area for the bolusCalculatorHandler function.
        basal_handler: function, optional, default : default_basal_handler
            A callback function that implements a basal controller to be used during the replay of a given scenario.
        basal_handler_params: dict, optional, default : {}
            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, optional, default : False
            A flag that specifies whether to enable hypotreatments during the replay of a given scenario.
        hypotreatments_handler: function, optional, default : ada_hypotreatments_handler
            A callback function that implements an hypotreatment strategy during the replay of a given scenario.
        hypotreatments_handler_params: dict, optional, default : {}
            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, optional, default : False
            A flag that specifies whether to enable correction boluses during the replay of a given scenario.
        correction_boluses_handler: function, optional, default : corrects_above_250_handler
            A callback function that implements a corrective bolusing strategy during the replay of a given scenario.
        correction_boluses_handler_params: dict, optional, default : {}
            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, optional, default : ''
            A string to be attached as suffix to the resulting output files' name.

        parallelize : boolean, optional, default : False
            A boolean that specifies whether to parallelize the identification process.
        plot_mode : boolean, optional, default : True
            A boolean that specifies whether to show the plot of the results or not.
        verbose : boolean, optional, default : True
            A boolean that specifies the verbosity of ReplayBG.

        Returns
        -------
        None

        Raises
        ------
        None

        See Also
        --------
        None

        Examples
        --------
        None

        References
        --------
        Cappon et al., "ReplayBG: a methodology to identify a personalized model from type 1 diabetes data and simulate glucose concentrations to
        assess alternative therapies", IEEE TBME, 2023.
        """

        # Input validation
        input_validator = InputValidator(modality=modality, data=data, BW=bw, scenario=scenario, save_name=save_name,
                                         save_suffix=save_suffix,
                                         yts=yts, glucose_model=glucose_model, pathology=pathology, exercise=exercise,
                                         seed=seed,
                                         bolus_source=bolus_source, basal_source=basal_source, cho_source=cho_source,
                                         cgm_model=cgm_model,
                                         n_steps=n_steps, to_sample=to_sample, save_chains=save_chains,
                                         CR=CR, CF=CF, GT=GT,
                                         meal_generator_handler=meal_generator_handler,
                                         meal_generator_handler_params=meal_generator_handler_params,
                                         bolus_calculator_handler=bolus_calculator_handler,
                                         bolus_calculator_handler_params=bolus_calculator_handler_params,
                                         basal_handler=basal_handler, basal_handler_params=basal_handler_params,
                                         enable_hypotreatments=enable_hypotreatments,
                                         hypotreatments_handler=hypotreatments_handler,
                                         hypotreatments_handler_params=hypotreatments_handler_params,
                                         enable_correction_boluses=enable_correction_boluses,
                                         correction_boluses_handler=correction_boluses_handler,
                                         correction_boluses_handler_params=correction_boluses_handler_params,
                                         parallelize=parallelize, plot_mode=plot_mode, verbose=verbose)
        input_validator.validate()

        # Initialize core variables
        self.environment, self.model, self.sensors, self.mcmc, self.dss = self.__init_core_variables(data=data, BW=bw,
                                                                                                     modality=modality,
                                                                                                     save_name=save_name,
                                                                                                     save_folder=save_folder,
                                                                                                     save_suffix=save_suffix,
                                                                                                     save_workspace=save_workspace,
                                                                                                     scenario=scenario,
                                                                                                     yts=yts,
                                                                                                     glucose_model=glucose_model,
                                                                                                     pathology=pathology,
                                                                                                     exercise=exercise,
                                                                                                     seed=seed,
                                                                                                     bolus_source=bolus_source,
                                                                                                     basal_source=basal_source,
                                                                                                     cho_source=cho_source,
                                                                                                     cgm_model=cgm_model,
                                                                                                     n_steps=n_steps,
                                                                                                     to_sample=to_sample,
                                                                                                     save_chains=save_chains,
                                                                                                     CR=CR, CF=CF,
                                                                                                     GT=GT,
                                                                                                     meal_generator_handler=meal_generator_handler,
                                                                                                     meal_generator_handler_params=meal_generator_handler_params,
                                                                                                     bolus_calculator_handler=bolus_calculator_handler,
                                                                                                     bolus_calculator_handler_params=bolus_calculator_handler_params,
                                                                                                     basal_handler=basal_handler,
                                                                                                     basal_handler_params=basal_handler_params,
                                                                                                     enable_hypotreatments=enable_hypotreatments,
                                                                                                     hypotreatments_handler=hypotreatments_handler,
                                                                                                     hypotreatments_handler_params=hypotreatments_handler_params,
                                                                                                     enable_correction_boluses=enable_correction_boluses,
                                                                                                     correction_boluses_handler=correction_boluses_handler,
                                                                                                     correction_boluses_handler_params=correction_boluses_handler_params,
                                                                                                     parallelize=parallelize,
                                                                                                     n_processes=n_processes,
                                                                                                     plot_mode=plot_mode,
                                                                                                     verbose=verbose)

        # ====================================================================

    def __init_core_variables(self, data, BW, modality, save_name, save_folder, save_suffix, scenario,
                              yts, glucose_model, pathology, exercise, seed,
                              bolus_source, basal_source, cho_source,
                              cgm_model,
                              n_steps, to_sample, save_chains, save_workspace,
                              CR, CF, GT,
                              meal_generator_handler, meal_generator_handler_params,
                              bolus_calculator_handler, bolus_calculator_handler_params,
                              basal_handler, basal_handler_params,
                              enable_hypotreatments, hypotreatments_handler, hypotreatments_handler_params,
                              enable_correction_boluses, correction_boluses_handler, correction_boluses_handler_params,
                              parallelize, n_processes, plot_mode, verbose):
        """
        Initializes the core variables (i.e., environment, model, sensors, mcmc, and dss) of ReplayBG.

        Parameters
        ----------
        data : pd.DataFrame
                Pandas dataframe which contains the data to be used by the tool.
        BW : double
            The patient's body weight.
        modality : string
            A string that specifies if the function will be used to identify 
            the ReplayBG model on the given data or to replay the scenario specified by the given data
        save_name : string
            A string used to label, thus identify, each output file and result.
        save_suffix : string
            A string to be attached as suffix to the resulting output files' name.
        scenario: string
            A string that specifies whether the given scenario refers to a single-meal scenario or a multi-meal scenario
        yts: int
            An integer that specifies the data sample time (in minutes).
        glucose_model: string, {'IG','BG'}
            The model equation to be used as measured glucose.
        pathology: string, {'t1d', 't2d', 'pbh', 'healthy'}
            A string that specifies the patient pathology.
        exercise: boolean
            A boolean that specifies whether to simulate exercise or not.
        seed: int
            An integer that specifies the random seed. For reproducibility.
        bolus_source : string, {'data', or 'dss'}
            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 : string, {'data', 'u2ss', or 'dss'}
            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 identification (if 'u2ss').
        cho_source : string, {'data', 'generated'}
            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.
        cgm_model: string, {'CGM','IG'}
            A string that specify the cgm model selection.
            If IG is selected, CGM measure will be the noise-free IG state at the current time.

        n_steps: int
            Number of steps to use for the main chain. This is ignored if modality is 'replay'.
        to_sample: int
            Number of samples to generate via the copula. This is ignored if modality is 'replay'.
        save_chains: bool
            A flag that specifies whether to save the resulting mcmc chains and copula samplers.

        bolus_source : string, {'data', or 'dss'}
            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 : string, {'data', 'u2ss', or 'dss'}
            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 identification (if 'u2ss').
        cho_source : string, {'data', 'generated'}
            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.
        
        cgm_model: string, {'CGM','IG'}
            A string that specify the cgm model selection.
            If IG is selected, CGM measure will be the noise-free IG state at the current time.

        CR: double
            The carbohydrate-to-insulin ratio of the patient in g/U to be used by the integrated decision support system.
        CF: double
            The correction factor of the patient in mg/dl/U to be used by the integrated decision support system.
        GT: double
            The target glucose value in mg/dl to be used by the decsion support system modules.
        meal_generator_handler: function
            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: function
            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: function
            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: function
            A callback function that implements an 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: function
            A callback function that implements a corrective bolusing 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.

        parallelize : boolean
            A boolean that specifies whether to parallelize the identification process.
        plot_mode : boolean
            A boolean that specifies whether to show the plot of the results or not.
        verbose : boolean
            A boolean that specifies the verbosity of ReplayBG.

        Returns
        -------
        environment: Environment
            An object that represents the hyperparameters to be used by ReplayBG environment.
        model: Model
            An object that represents the physiological model hyperparameters to be used by ReplayBG.
        sensors: Sensors
            An object that represents the sensors to be used by ReplayBG.
        mcmc: MCMC
            An object that represents the hyperparameters of the MCMC identification procedure.
        dss: DSS
            An object that represents the hyperparameters of the integrated decision support system.

        Raises
        ------
        None

        See Also
        --------
        None

        Examples
        --------
        None
        """

        # Initialize the environment parameters
        environment = Environment(modality=modality, save_name=save_name, save_folder=save_folder, save_suffix=save_suffix,
                                  save_workspace=save_workspace, scenario=scenario,
                                  bolus_source=bolus_source, basal_source=basal_source, cho_source=cho_source,
                                  seed=seed,
                                  parallelize=parallelize, n_processes=n_processes, plot_mode=plot_mode, verbose=verbose)

        # Initialize model
        if pathology == 't1d':
            model = T1DModel(data=data, BW=BW, yts=yts, glucose_model=glucose_model, is_single_meal=(scenario=='single-meal'), exercise=exercise)

        # Initialize sensors
        sensors = self.__init_sensors(cgm_model, model)

        # Initialize MCMC
        mcmc = MCMC(model,
                    n_steps=n_steps,
                    to_sample=to_sample,
                    save_chains=save_chains,
                    callback_ncheck=100)

        # Initialize DSS
        dss = DSS(BW=BW, CR=CR, CF=CF, GT=GT,
                  meal_generator_handler=meal_generator_handler,
                  meal_generator_handler_params=meal_generator_handler_params,
                  bolus_calculator_handler=bolus_calculator_handler,
                  bolus_calculator_handler_params=bolus_calculator_handler_params,
                  basal_handler=basal_handler, basal_handler_params=basal_handler_params,
                  enable_hypotreatments=enable_hypotreatments, hypotreatments_handler=hypotreatments_handler,
                  hypotreatments_handler_params=hypotreatments_handler_params,
                  enable_correction_boluses=enable_correction_boluses,
                  correction_boluses_handler=correction_boluses_handler,
                  correction_boluses_handler_params=correction_boluses_handler_params)

        return environment, model, sensors, mcmc, dss

    def __init_sensors(self, cgm_model, model):
        """
        Utility function that initializes the sensor core object.

        Parameters
        ----------
        cgm_model: string, {'CGM','IG'}
            A string that specify the cgm model selection.
            If IG is selected, CGM measure will be the noise-free IG state at the current time.
        model: Model
            An object that represents the physiological model hyperparameters to be used by ReplayBG.

        Returns
        -------
        sensors: Sensors
            An object that represents the sensors to be used by ReplayBG.

        Raises
        ------
        None

        See Also
        --------
        None

        Examples
        --------
        None
        """
        # Init the CGM sensor
        cgm = CGM(ts=model.yts, model=cgm_model)

        # return the object
        return Sensors(cgm=cgm)

    def run(self, data, bw):
        """
        Runs ReplayBG according to the chosen modality.

        Parameters
        ----------
        data: pd.DataFrame
            Pandas dataframe which contains the data to be used by the tool.
        bw: double
            The patient's body weight.

        Returns
        -------
        None

        Raises
        ------
        None

        See Also
        --------
        None

        Examples
        --------
        None
        """

        if self.environment.verbose:
            print('Running ReplayBG in `' + self.environment.modality + '` mode')

        # Unpack data to optimize performance
        rbg_data = ReplayBGData(data=data, rbg=self)

        # If modality is identification...
        if self.environment.modality == 'identification':
            # ...run identification
            if self.environment.verbose:
                print('Running model identification...')
            self.mcmc.identify(data=data, rbg_data=rbg_data, rbg=self)

        # Load model parameters
        if self.environment.verbose:
            print('Loading identified model parameter realizations...')
        with open(os.path.join(self.environment.replay_bg_path, 'results', 'draws',
                               'draws_' + self.environment.save_name + '.pkl'), 'rb') as file:
            identification_results = pickle.load(file)
        draws = identification_results['draws']

        # Run replay
        if self.environment.verbose:
            print('Replaying scenario...')
        replayer = Replayer(rbg_data=rbg_data, draws=draws, rbg=self)
        glucose, cgm, insulin_bolus, correction_bolus, insulin_basal, cho, hypotreatments, meal_announcement, vo2 = replayer.replay_scenario()

        if self.environment.verbose:
            print('Analyzing results...')
        analysis = self.__analyze_results(glucose, cgm, insulin_bolus, correction_bolus, insulin_basal, cho, hypotreatments, meal_announcement, vo2, data)

        # Plot results if plot_mode is enabled
        if self.environment.plot_mode:

            if self.environment.verbose:
                print('Plotting results...')

            viz = Visualizer()
            viz.plot_replaybg_results(cgm=cgm, glucose=glucose, insulin_bolus=insulin_bolus,
                                      insulin_basal=insulin_basal,
                                      cho=cho, hypotreatments=hypotreatments, correction_bolus=correction_bolus,
                                      vo2=vo2, data=data, rbg=self)

        # Save results
        if self.environment.save_workspace:
            if self.environment.verbose:
                print('Saving results in ' + os.path.join(self.environment.replay_bg_path, 'results', 'workspaces', self.environment.modality + '_' + self.environment.save_name + self.environment.save_suffix + '.pkl'))
            self.__save_results(data, bw, glucose, cgm, insulin_bolus, correction_bolus, insulin_basal, cho, hypotreatments,
                                meal_announcement, vo2, analysis)

        if self.environment.verbose:
            print('Done. Bye!')

    def __analyze_results(self, glucose, cgm, insulin_bolus, correction_bolus, insulin_basal, cho, hypotreatments, meal_announcement, vo2, data):
        """
        Analyzes ReplayBG results.

        Parameters
        ----------

        Returns
        -------

        Raises
        ------
        None

        See Also
        --------
        None

        Examples
        --------
        None
        """
        agata = Agata(glycemic_target='diabetes')

        analysis = dict()
        fields = ['median', 'ci5th', 'ci25th', 'ci75th', 'ci95th']
        for f in fields:
            analysis[f] = dict()


            # Transform the glucose profile under examination to a dataframe compatible with Agata
            glucose_profile = glucose_vector_to_dataframe(glucose[f], self.model.ts)

            # Analyse the glucose profile
            analysis[f]["glucose"] = agata.analyze_glucose_profile(glucose_profile)

            # Transform the cgm profile under examination to a dataframe compatible with Agata
            cgm_profile = glucose_vector_to_dataframe(cgm[f], self.model.yts)

            # Analyse the cgm profile
            analysis[f]["cgm"] = agata.analyze_glucose_profile(cgm_profile)


        total_insulin = np.zeros(shape=(insulin_bolus["realizations"].shape[0],))
        total_bolus_insulin = np.zeros(shape=(insulin_bolus["realizations"].shape[0],))
        total_correction_bolus_insulin = np.zeros(shape=(insulin_bolus["realizations"].shape[0],))
        total_basal_insulin = np.zeros(shape=(insulin_bolus["realizations"].shape[0],))

        total_cho = np.zeros(shape=(cho["realizations"].shape[0],))
        total_hypotreatments = np.zeros(shape=(cho["realizations"].shape[0],))
        total_meal_announcements = np.zeros(shape=(cho["realizations"].shape[0],))

        correction_bolus_insulin_number = np.zeros(shape=(insulin_bolus["realizations"].shape[0],))
        hypotreatment_number = np.zeros(shape=(cho["realizations"].shape[0],))

        exercise_session_number = np.zeros(shape=(vo2["realizations"].shape[0],))
        # TODO: add other metrics for exercise (e.g., average VO2 per session, duration of each session)

        for r in range(total_insulin.size):

            # Compute insulin amounts for each realization
            total_insulin[r] = np.sum(insulin_bolus["realizations"][r, :]) + np.sum(insulin_basal["realizations"][r, :])
            total_bolus_insulin[r] = np.sum(insulin_bolus["realizations"][r, :])
            total_basal_insulin[r] = np.sum(insulin_basal["realizations"][r, :])
            total_correction_bolus_insulin[r] = np.sum(correction_bolus["realizations"][r, :])

            # Compute CHO amounts for each realization
            total_cho[r] = np.sum(cho["realizations"][r, :])
            total_hypotreatments[r] = np.sum(hypotreatments["realizations"][r, :])
            total_meal_announcements[r] = np.sum(meal_announcement["realizations"][r, :])

            # Compute numbers for each realization
            correction_bolus_insulin_number[r] = np.where(correction_bolus["realizations"])[0].size
            hypotreatment_number[r] = np.where(hypotreatments["realizations"])[0].size

            # Compute exercise metrics for each realization
            e = np.where(hypotreatments["realizations"])[0]
            if e.size == 0:
                exercise_session_number[r] = 0
            else:
                d = np.diff(e)
                idxs = np.where(d > 1)[0]
                exercise_session_number[r] = 1 + idxs.size

        p = [50, 5, 25, 75, 95]
        for f in range(len(fields)):
            analysis[fields[f]]["event"] = dict()

            analysis[fields[f]]["event"]["total_insulin"] = np.percentile(total_insulin, p[f])
            analysis[fields[f]]["event"]["total_bolus_insulin"] = np.percentile(total_bolus_insulin, p[f])
            analysis[fields[f]]["event"]["total_basal_insulin"] = np.percentile(total_basal_insulin, p[f])
            analysis[fields[f]]["event"]["total_correction_bolus_insulin"] = np.percentile(total_correction_bolus_insulin, p[f])

            analysis[fields[f]]["event"]["total_cho"] = np.percentile(total_cho, p[f])
            analysis[fields[f]]["event"]["total_hypotreatments"] = np.percentile(total_hypotreatments, p[f])
            analysis[fields[f]]["event"]["total_meal_announcements"] = np.percentile(total_meal_announcements, p[f])

            analysis[fields[f]]["event"]["correction_bolus_insulin_number"] = np.percentile(correction_bolus_insulin_number, p[f])
            analysis[fields[f]]["event"]["hypotreatment_number"] = np.percentile(hypotreatment_number, p[f])

            analysis[fields[f]]["event"]["exercise_session_number"] = np.percentile(exercise_session_number, p[f])

        if self.environment.modality == 'identification':
            for f in fields:
                analysis[f]["identification"] = dict()

                data_hat = glucose_vector_to_dataframe(cgm[f], self.model.yts, pd.to_datetime(data.t.values[0]).to_pydatetime())

                analysis[f]["identification"]["rmse"] = rmse(data, data_hat)
                analysis[f]["identification"]["mard"] = mard(data, data_hat)
                analysis[f]["identification"]["clarke"] = clarke(data, data_hat)
                analysis[f]["identification"]["cod"] = cod(data, data_hat)
                analysis[f]["identification"]["g_rmse"] = g_rmse(data, data_hat)

        return analysis

    def __save_results(self, data, BW, glucose, cgm, insulin_bolus, correction_bolus, insulin_basal, cho,
                       hypotreatments, meal_announcement, vo2, analysis):
        """
        Save ReplayBG results.

        Parameters
        ----------

        Returns
        -------

        Raises
        ------
        None

        See Also
        --------
        None

        Examples
        --------
        None
        """
        results = dict()

        results['data'] = data
        results['BW'] = BW

        results['environment'] = self.environment
        results['mcmc'] = self.mcmc
        results['model'] = self.model
        results['sensors'] = self.sensors
        results['dss'] = self.dss

        results['glucose'] = glucose
        results['cgm'] = cgm
        results['insulin_bolus'] = insulin_bolus
        results['correction_bolus'] = correction_bolus
        results['insulin_basal'] = insulin_basal

        results['cho'] = cho
        results['hypotreatments'] = hypotreatments
        results['meal_announcement'] = meal_announcement
        results['vo2'] = vo2

        results['analysis'] = analysis

        with open(os.path.join(self.environment.replay_bg_path, 'results', 'workspaces',
                               self.environment.modality + '_' + self.environment.save_name + self.environment.save_suffix + '.pkl'),
                  'wb') as file:
            pickle.dump(results, file)
