import numpy as np
from .errors import NotImplementedYet

# https://towardsdatascience.com/activation-functions-neural-networks-1cbd9f8d91d6


class ActivationFunction():
    '''All activation functions need to be subclasses of this class. This class can be used to define newer activation functions.
    '''

    def __init__(self, func_name: str) -> None:
        self.name = func_name

    def __str__(self) -> str:
        return "{} Activation Function Object".format(self.name)

    def forward(self, *args: object, **kwargs: dict):
        raise NotImplementedYet

    def backward(self, *args: object, **kwargs: dict):
        raise NotImplementedYet


class Relu(ActivationFunction):
    '''The Rectified Linear Unit Activation Function'''

    def __init__(self) -> None:
        super().__init__(func_name="relu")

    def forward(self, inputs: np.ndarray) -> np.ndarray:
        return np.maximum(inputs, 0)


class Sigmoid(ActivationFunction):
    '''Sigmoid Activation Function'''

    def __init__(self) -> None:
        super().__init__(func_name="sigmoid")

    def forward(self, inputs: np.ndarray) -> np.ndarray:
        return 1/(1+np.exp(-inputs))


class Softmax(ActivationFunction):
    '''Softmax Activation Function for multiclass classificiation'''

    def __init__(self) -> None:
        super().__init__(func_name="softmax")

    def forward(self, inputs: np.ndarray) -> np.ndarray:
        expo = np.exp(inputs)
        return expo / expo.sum(axis=-1)


class Tanh(ActivationFunction):
    '''TanH activation function'''

    def __init__(self) -> None:
        super().__init__(func_name="tanh")

    def forward(self, inputs: np.ndarray) -> np.ndarray:
        return np.tanh(inputs)


class Arctan(ActivationFunction):
    '''TanH activation function'''

    def __init__(self) -> None:
        super().__init__(func_name="arctan")

    def forward(self, inputs: np.ndarray) -> np.ndarray:
        return np.arctan(inputs)
