import numpy as np

"""
In tutorial 5, we perform modeling using multiple profiles, in particular the _Gaussian_ profile from the previous
tutorials and an Exponential profile. In analysis.py, we will edit how model-data is generated from profiles such
that it is the sum of all profiles in our model.

In this module, we thus now have two classes following the PyAutoFit model component format. We have renamed the
module from 'gaussian.py' to 'profiles.py' to reflect this. We have created an abstract base class 'Profile' from
which all profiles inherit.

If you are not familiar with Python classes, in particular inheritance and the 'super' method below, you may
be unsure what the classes are doing below. I have included comments describing what these command do.

The Profile class is a base class from which all profiles we add (e.g Gaussian, Exponential, additional profiles
added down the line) will inherit. This is useful, as it signifinies which aspects of our model are different ways of
representing the same thing.
"""


class Profile:
    def __init__(self, centre=0.0, intensity=0.01):
        """Represents an Abstract 1D profile.

        Parameters
        ----------
        centre : float
            The x coordinate of the profile centre.
        intensity : float
            Overall intensity normalisation of the profile.
        """

        """
        Every profile class we add below (e.g. Gaussian, Exponential) will call this __init__ method of the Profile
        base class. Given that every profile will have a centre and intensity, this means we can set these parameters
        in the Profile class's init method instead of repeating the two lines of code for every individual profile.
        """

        self.centre = centre
        self.intensity = intensity


"""
The inclusioon of (Profile) in the _Gaussian_ below instructs Python that the _Gaussian_ class is going to inherit from
the Profile class.
"""


class Gaussian(Profile):
    def __init__(
        self,
        centre=0.0,  # <- PyAutoFit recognises these constructor arguments
        intensity=0.1,  # <- are the Gaussian's model parameters.
        sigma=0.01,
    ):
        """Represents a 1D _Gaussian_ profile, which may be treated as a model-component of PyAutoFit the
        parameters of which are fitted for by a non-linear search.

        Parameters
        ----------
        centre : float
            The x coordinate of the profile centre.
        intensity : float
            Overall intensity normalisation of the _Gaussian_ profile.
        sigma : float
            The sigma value controlling the size of the Gaussian.
        """

        """
        Writing (Profile) above does not mean the _Gaussian_ class will call the Profile class's __init__ method. To
        achieve this we have the call the 'super' method following the format below.
        """

        super(Gaussian, self).__init__(centre=centre, intensity=intensity)

        """
        This super method calls the __init__ method of the Profile class above, which means we do not need
        to write the two lines of code below (which are commented out given they are not necessary).
        """

        # self.centre = centre
        # self.intensity = intensity

        self.sigma = sigma  # We still need to set sigma for the Gaussian, of course.

    def profile_from_xvalues(self, xvalues):
        """
        Calculate the intensity of the profile on a line of Cartesian x coordinates.

        The input xvalues are translated to a coordinate system centred on the Gaussian, using its centre.

        Parameters
        ----------
        values : ndarray
            The x coordinates in the original reference frame of the grid.
        """
        transformed_xvalues = np.subtract(xvalues, self.centre)
        return np.multiply(
            np.divide(self.intensity, self.sigma * np.sqrt(2.0 * np.pi)),
            np.exp(-0.5 * np.square(np.divide(transformed_xvalues, self.sigma))),
        )


class Exponential(Profile):
    def __init__(
        self,
        centre=0.0,  # <- PyAutoFit recognises these constructor arguments are the model
        intensity=0.1,  # <- parameters of the Gaussian.
        rate=0.01,
    ):
        """Represents a 1D Exponential profile, which may be treated as a model-component of PyAutoFit the
        parameters of which are fitted for by a non-linear search.

        Parameters
        ----------
        centre : float
            The x coordinate of the profile centre.
        intensity : float
            Overall intensity normalisation of the _Gaussian_ profile.
        ratw : float
            The decay rate controlling has fast the Exponential declines.
        """

        super(Exponential, self).__init__(centre=centre, intensity=intensity)

        self.rate = rate

    def profile_from_xvalues(self, xvalues):
        """
        Calculate the intensity of the profile on a line of Cartesian x coordinates.

        The input xvalues are translated to a coordinate system centred on the Exponential, using its centre.

        Parameters
        ----------
        values : ndarray
            The x coordinates in the original reference frame of the grid.
        """
        transformed_xvalues = np.subtract(xvalues, self.centre)
        return self.intensity * np.multiply(
            self.rate, np.exp(-1.0 * self.rate * abs(transformed_xvalues))
        )
