from mot.cl_data_type import SimpleCLDataType
from mot.cl_parameter import SimpleCLFunctionParameter
from mot.model_building.parameter_functions.numdiff_info import SimpleNumDiffInfo
from mot.model_building.parameter_functions.priors import UniformWithinBoundsPrior
from mot.model_building.parameter_functions.transformations import IdentityTransform

__author__ = 'Robbert Harms'
__date__ = "2016-10-03"
__maintainer__ = "Robbert Harms"
__email__ = "robbert.harms@maastrichtuniversity.nl"


class CurrentObservationParam(SimpleCLFunctionParameter):

    def __init__(self, name='_observation'):
        """This parameter indicates that the model should inject the current observation value in the model.

        Sometimes during model linearization or other mathematical operations the current observation appears on
        both sides of the optimization equation. That is, it sometimes happens you want to use the current observation
        to model that same observation. This parameter is a signal to the model builder to inject the current
        observation.

        You can use this parameter by adding it to your model and then use the current name in your model equation.
        """
        super(CurrentObservationParam, self).__init__(SimpleCLDataType.from_string('mot_float_type'), name)


class InputDataParameter(SimpleCLFunctionParameter):

    def __init__(self, data_type, name, value):
        """These parameters signal are meant to be contain data loaded from the input data object.

        In contrast to free parameters which are being optimized (or fixed to values), these parameters are
        meant to be loaded from the input data. They can contain scalars, vectors or matrices with values
        to use for each problem instance and each data point.

        Args:
            data_type (mot.cl_data_type.SimpleCLDataType): the data type expected by this parameter
            name (str): The name of this parameter
            value (double or ndarray): The value used if no value is given in the input data.
        """
        super(InputDataParameter, self).__init__(data_type, name)
        self.value = value


class StaticMapParameter(InputDataParameter):

    def __init__(self, data_type, name, value):
        """This parameter is meant for static data that is different per problem.

        These parameters are in usage similar to fixed free parameters. They are defined as static data parameters to
        make clear that they are meant to provide additional observational data.

        Values for this parameter type are typically loaded from the input data. A default is provided in the case
        that there is no suitable value in the input data.

        Args:
            data_type (mot.cl_data_type.SimpleCLDataType): the data type expected by this parameter
            name (str): The name of this parameter
            value (double or ndarray): The value used if no value is given in the input data.
        """
        super(StaticMapParameter, self).__init__(data_type, name, value)
        self.value = value


class ProtocolParameter(InputDataParameter):

    def __init__(self, data_type, name):
        """A protocol data parameter indicates that similar named parameters may be linked together.

        When multiple functions in a composite model share a protocol parameter with the same name, the model
        builder will only load the data once and link the identical named parameters to the same value.

        The value for a protocol parameter is loaded from the input data.
        """
        super(ProtocolParameter, self).__init__(data_type, name, None)


class FreeParameter(SimpleCLFunctionParameter):

    def __init__(self, data_type, name, fixed, value, lower_bound, upper_bound,
                 parameter_transform=None, sampling_proposal_std=None,
                 sampling_prior=None, numdiff_info=None):
        """This are the kind of parameters that are generally meant to be optimized.

        These parameters may optionally be fixed to a value or list of values for all problems.

        Args:
            data_type (str or mot.cl_data_type.SimpleCLDataType): the data type expected by this parameter
            name (str): The name of this parameter
            fixed (boolean): If this parameter is fixed to the value given
            value (double or ndarray): A single value for all problems or a list of values for each problem.
            lower_bound (double): The lower bound of this parameter
            upper_bound (double): The upper bound of this parameter
            parameter_transform (AbstractTransformation): The parameter transformation function
            sampling_proposal_std (float): The proposal standard deviation, used in some MCMC sampling routines
            sampling_prior (ParameterPrior): The prior function for use in model sampling
            numdiff_info (mot.model_building.parameter_functions.numdiff_info.NumDiffInfo): the information
                for taking the numerical derivative with respect to this parameter.
        """
        super(FreeParameter, self).__init__(data_type, name)
        self._value = value
        self._lower_bound = lower_bound
        self._upper_bound = upper_bound
        self._fixed = fixed

        self._parameter_transform = parameter_transform or IdentityTransform()
        self._sampling_proposal_std = sampling_proposal_std or 1
        self._sampling_prior = sampling_prior or UniformWithinBoundsPrior()
        self._numdiff_info = numdiff_info or SimpleNumDiffInfo()

    @property
    def value(self):
        return self._value

    @property
    def lower_bound(self):
        return self._lower_bound

    @property
    def upper_bound(self):
        return self._upper_bound

    @property
    def fixed(self):
        return self._fixed

    @property
    def parameter_transform(self):
        return self._parameter_transform

    @property
    def sampling_proposal_std(self):
        return self._sampling_proposal_std

    @property
    def sampling_prior(self):
        return self._sampling_prior

    @property
    def numdiff_info(self):
        return self._numdiff_info


class LibraryParameter(SimpleCLFunctionParameter):
    """Parameters of this type are used inside library functions. They are not meant to be used in Model functions.
    """
