"""
Result
------

The pypesto.Result object contains all results generated by
the pypesto components. It contains sub-results for
optimization, profiles, sampling.

"""

import pandas as pd
import copy


class OptimizeResult:
    """
    Result of the minimize() function.
    """

    def __init__(self):
        self.list = []

    def append(self, optimizer_result):
        """
        Append an optimizer result to the result object.

        Parameters
        ----------

        optimizer_result:
            The result of one (local) optimizer run.
        """

        self.list.append(optimizer_result)

    def sort(self):
        """
        Sort the optimizer results by function value fval (ascending).
        """

        self.list = sorted(self.list, key=lambda res: res.fval)

    def as_dataframe(self, keys=None) -> pd.DataFrame:
        """
        Get as pandas DataFrame. If keys is a list,
        return only the specified values.
        """

        lst = self.as_list(keys)

        df = pd.DataFrame(lst)

        return df

    def as_list(self, keys=None) -> list:
        """
        Get as list. If keys is a list,
        return only the specified values.

        Parameters
        ----------
        keys: list(str), optional
            Labels of the field to extract.
        """

        lst = self.list

        if keys is not None:
            lst = [{key: res[key] for key in keys} for res in lst]

        return lst

    def get_for_key(self, key) -> list:
        """
        Extract the list of values for the specified key as a list.
        """

        return [res[key] for res in self.list]


class ProfileResult:
    """
    Result of the profile() function.
    """

    def __init__(self):
        self.list = []

    def create_new_profile_list(self):
        """
        Append an profiler result to the result object.

        Parameters
        ----------
        """
        self.list.append([])

    def create_new_profile(self, profiler_result=None):
        """
        Append an profiler result to the result object.

        Parameters
        ----------
        profiler_result:
            The result of one (local) profiler run or None, if to be left empty

        profile_list: integer
            index specifying the list of profiles, to which we want to append
        """

        current_profile = len(self.list)
        new_profile = copy.deepcopy(profiler_result)
        self.list[current_profile - 1].append(new_profile)

    def add_profile(self, profiler_result, i_parameter):
        """
        Writes a profiler result to the result object at i_parameter.

        Parameters
        ----------
        profiler_result:
            The result of one (local) profiler run.

        i_parameter:
            integer specifying the parameter index
        """

        current_profile = len(self.list)
        self.list[current_profile - 1][i_parameter] = \
            copy.deepcopy(profiler_result)

    def get_current_profile(self, i_parameter):
        """
        Append an profiler result to the result object.

        Parameters
        ----------
        i_parameter:
            integer specifying the profile index
        """

        current_profile = len(self.list)
        return self.list[current_profile - 1][i_parameter]


class SampleResult:
    """
    Result of the sample() function.
    """

    def __init__(self):
        pass


class Result:
    """
    Universal result object for pypesto.
    The algorithms like optimize, profile,
    sample fill different parts of it.

    Attributes
    ----------

    problem: pypesto.Problem
        The problem underlying the results.

    optimize_result:
        The results of the optimizer runs.

    profile_result:
        The results of the profiler run.

    sample_result:
        The results of the sampler run.

    """

    def __init__(self, problem=None):
        self.problem = problem
        self.optimize_result = OptimizeResult()
        self.profile_result = ProfileResult()
        self.sample_result = SampleResult()
