from dataclasses import dataclass

from moviepy.Effect import Effect


@dataclass
class AccelDecel(Effect):
    """Accelerates and decelerates a clip, useful for GIF making.

    Parameters
    ----------

    new_duration : float
      Duration for the new transformed clip. If None, will be that of the
      current clip.

    abruptness : float
      Slope shape in the acceleration-deceleration function. It will depend
      on the value of the parameter:

      * ``-1 < abruptness < 0``: speed up, down, up.
      * ``abruptness == 0``: no effect.
      * ``abruptness > 0``: speed down, up, down.

    soonness : float
      For positive abruptness, determines how soon the transformation occurs.
      Should be a positive number.

    Raises
    ------

    ValueError
      When ``sooness`` argument is lower than 0.

    Examples
    --------

    The following graphs show functions generated by different combinations
    of arguments, where the value of the slopes represents the speed of the
    videos generated, being the linear function (in red) a combination that
    does not produce any transformation.

    .. image:: /_static/medias/accel_decel-fx-params.png
      :alt: acced_decel FX parameters combinations
    """

    new_duration: float = None
    abruptness: float = 1.0
    soonness: float = 1.0

    def _f_accel_decel(
        self, t, old_duration, new_duration, abruptness=1.0, soonness=1.0
    ):
        a = 1.0 + abruptness

        def _f(t):
            def f1(t):
                return (0.5) ** (1 - a) * (t**a)

            def f2(t):
                return 1 - f1(1 - t)

            return (t < 0.5) * f1(t) + (t >= 0.5) * f2(t)

        return old_duration * _f((t / new_duration) ** soonness)

    def apply(self, clip):
        """Apply the effect to the clip."""
        if self.new_duration is None:
            self.new_duration = clip.duration

        if self.soonness < 0:
            raise ValueError("'sooness' should be a positive number")

        return clip.time_transform(
            lambda t: self._f_accel_decel(
                t=t,
                old_duration=clip.duration,
                new_duration=self.new_duration,
                abruptness=self.abruptness,
                soonness=self.soonness,
            )
        ).with_duration(self.new_duration)
