from yta_validation.parameter import ParameterValidator
from yta_programming.decorators.requires_dependency import requires_dependency
from abc import ABC, abstractmethod

import numpy as np


class EasingFunction(ABC):
    """
    *Abstract class*

    Abstract class to be inherited by the specific
    easing implementations.

    An easing function that is able to calculate a
    value according to the `n` parameter provided.
    """

    def func(
        self,
        t: float,
        **kwargs
    ) -> float:
        """
        Apply the easing to the `t` value provided, that must
        be a value in the `[0.0, 1.0]` range.
        """
        ParameterValidator.validate_mandatory_number_between(
            name = 't',
            value = t,
            lower_limit = 0.0,
            upper_limit = 1.0,
            do_include_lower_limit = True,
            do_include_upper_limit = True
        )

        return self._func(
            t = t,
            **kwargs
        )

    @abstractmethod
    def _func(
        self,
        t: float,
        **kwargs
    ) -> float:
        """
        *For internal use only*

        The proper calculation of the easing, that must be
        implemented for each specific class.
        """
        pass

    def __call__(
        self,
        t: float
    ) -> float:
        """
        Special method to allow us doing
        `EasingFunction.func(t)` directly.

        Apply the easing to the `t` value provided, that must
        be a value in the `[0.0, 1.0]` range.
        """
        return self.func(t)

    @requires_dependency('matplotlib', 'yta_math', 'matplotlib')
    def plot(
        self,
        number_of_samples: int = 100
    ):
        """
        Plot a graphic representing `number_of_samples`
        values, from `0.0` to `1.0`, to see how the easing
        function works.
        """
        import matplotlib.pyplot as plt
        
        # Limit and draw axis
        plt.xlim(0, 1)
        plt.ylim(0, 1)
        plt.axhline(0, color = 'black', linewidth = 1)
        plt.axvline(0, color = 'black', linewidth = 1)

        plt.grid(True)

        # We will draw 'n'' values between 0.0 and 1.1
        x_vals = np.linspace(0.0, 1.0, number_of_samples).tolist()
        y_vals = [
            self.get_n_value(x)
            for x in x_vals
        ]
        plt.scatter(x_vals, y_vals, color = 'white', edgecolors = 'black', s = 100)

        # TODO: I think we don't need to draw anything
        # else. Remove this below if it is ok.
        # # Draw points between nodes
        # for pair_of_node in self.pairs_of_nodes:
        #     positions = pair_of_node.get_n_xy_values_to_plot(100)
        #     t_xs, t_ys = zip(*positions)
        #     xs += t_xs
        #     ys += t_ys
       
        # plt.scatter(xs, ys, color = 'black', s = 1)
        
        plt.title('')
        plt.xlabel('X axis')
        plt.ylabel('Y axis')
        
        plt.show()
