"""
Module :mod:`nhe.mfd.base` defines base class for MFD and a base exception
class.
"""
import abc


class MFDError(Exception):
    """
    An error happened during MFD constraint check or modification.
    """


class BaseMFD(object):
    """
    Abstract base class for Magnitude-Frequency Distribution function.
    """
    __metaclass__ = abc.ABCMeta

    #: The list of parameters' names that are required by actual
    #: MFD implementation. The property is required to be overridden.
    #: Those and only those attributes that are listed here are allowed
    #: and required as constructor's kwargs. See :meth:`set_parameters`.
    PARAMETERS = abc.abstractproperty()

    #: The set of modification type names that are supported by an MFD.
    #: Each modification should have a corresponding method named
    #: ``modify_modificationname()`` where the actual modification
    #: logic resides.
    MODIFICATIONS = abc.abstractproperty()

    def __init__(self, **parameters):
        self.set_parameters(parameters)
        self._original_parameters = parameters.copy()

    def set_parameters(self, parameters):
        """
        Assign parameters to object's attributes.

        :param parameters:
            The dictionary of parameters as passed to the constructor.
        :raises MFDError:
            If some actual parameters are missing in :attr:`PARAMETERS`
            or if something from :attr:`PARAMETERS` is missing in actual
            parameters.

        Calls :meth:`check_constraints` once everything is assigned.
        """
        defined = set(parameters)
        required = set(self.PARAMETERS)
        unexpected = defined - required
        missing = required - defined
        if missing:
            raise MFDError('These parameters are required but missing: %s'
                           % ', '.join(sorted(missing)))
        if unexpected:
            raise MFDError('These parameters are unexpected: %s'
                           % ', ' .join(sorted(unexpected)))
        for param_name in self.PARAMETERS:
            setattr(self, param_name, parameters[param_name])
        self.check_constraints()

    def modify(self, modification, parameters):
        """
        Apply a single modification to an MFD parameters.

        Reflects the modification method and calls it passing ``parameters``
        as keyword arguments. See also :attr:`MODIFICATIONS`.

        Modifications can be applied one on top of another. The logic
        of stacking modifications is up to a specific MFD implementation.
        Any number of modifications can be reverted with a single call
        to :meth:`reset`.

        :param modification:
            String name representing the type of modification.
        :param parameters:
            Dictionary of parameters needed for modification.
        :raises MFDError:
            If ``modification`` is missing from :attr:`MODIFICATIONS`.
        """
        if not modification in self.MODIFICATIONS:
            raise MFDError('Modification %s is not supported by %s' %
                           (modification, type(self).__name__))
        meth = getattr(self, 'modify_%s' % modification)
        meth(**parameters)
        self.check_constraints()

    def reset(self):
        """
        Reset an MFD to its original state after any number of :meth:`modify`s.
        """
        self.set_parameters(self._original_parameters)

    @abc.abstractmethod
    def check_constraints(self):
        """
        Check MFD-specific constraints and raise :exc:`MFDError`
        in case of violation.

        This method must be implemented by subclasses.
        """

    @abc.abstractmethod
    def get_annual_occurrence_rates(self):
        """
        Return an MFD annual occurrence rates histogram.

        This method must be implemented by subclasses.

        :return:
            The list of tuples, each tuple containing a pair
            ``(magnitude, occurrence_rate)``. Each pair represents
            a single bin of the histogram with ``magnitude`` being
            the center of the bin. Magnitude values are monotonically
            increasing by value of bin width. ``occurence_rate``
            represents the number of events per year with magnitude
            that falls in between bin's boundaries.
        """

    @abc.abstractmethod
    def get_rescaled_mfd(self, scaling_factor):
        """
        Return a new MFD object with all the occurrence rates rescaled
        with respect to ``scaling_factor``.

        :param scaling_factor:
            A positive float number. Values more than 1 mean the occurrence
            rates get increased, and if scaling factor is between 0 and 1
            rates are conversely decreasing.
        :return:
            A new MFD object of the same kind.
        :raises MFDError:
            If scaling factor is not positive.

        It is important that all the modifications (see :meth:`modify`)
        are taken into account but it becomes impossible to :meth:`reset`
        the new MFD and return it to the original shape of the original
        MFD. However the new MFD can be modified as usual and reseted
        thereafter to the state it was returned from this method in.
        """

@@ -4331,938 +4331,4 @@
 %22%22%22%0A
-%0A    @abc.abstractmethod%0A    def get_rescaled_mfd(self, scaling_factor):%0A        %22%22%22%0A        Return a new MFD object with all the occurrence rates rescaled%0A        with respect to %60%60scaling_factor%60%60.%0A%0A        :param scaling_factor:%0A            A positive float number. Values more than 1 mean the occurrence%0A            rates get increased, and if scaling factor is between 0 and 1%0A            rates are conversely decreasing.%0A        :return:%0A            A new MFD object of the same kind.%0A        :raises MFDError:%0A            If scaling factor is not positive.%0A%0A        It is important that all the modifications (see :meth:%60modify%60)%0A        are taken into account but it becomes impossible to :meth:%60reset%60%0A        the new MFD and return it to the original shape of the original%0A        MFD. However the new MFD can be modified as usual and reseted%0A        thereafter to the state it was returned from this method in.%0A        %22%22%22%0A

Revert "mfd/base: added abstract method get_rescaled_mfd()"