import abc
import functools
from .exceptions import ClassifierNotSupportException

class Classifier(metaclass=abc.ABCMeta):
    def __init__(self, *args, **kwargs):
        pass

    def get_pred(self, input_, meta):
        """
        :param list input_: A list of sentences which we want to predict.
        :param dict meta: Meta of data (**Optional parameter**).
        :return: The result is a 1-dim numpy.array, *[pred1, pred2, ...]*.
        :rtype: numpy.array(dtype=numpy.long)

        This is used to get the predictions of a batch of sentences.
        """
        return self.get_prob(input_, meta).argmax(axis=1)

    def get_prob(self, input_, meta):
        """
        :param list input_: A list of sentences of which we want to get the probabilities.
        :param dict meta: Meta of data (**Optional parameter**).
        :return: The result is a 2-dim numpy.array. The first dimension represents sentences and the second dimension represents probabilities of every type.
        :rtype: numpy.array(dtype=numpy.float)

        This is used to get the probabilities of a batch of sentences on every type.
        """
        from .text_processors import DefaultTextProcessor
        processor = DefaultTextProcessor()
        x_batch = [ list(map(lambda x:x[0], processor.get_tokens(sent))) for sent in input_ ]
        return self.get_grad(x_batch, [0] * len(x_batch), meta)[0]

    def get_grad(self, input_, labels, meta):
        """
        :param list input_: A list of lists of tokens of which we want to get the gradients.
        :param list labels: A list of types based on which we want to get the gradients.
        :param dict meta: Meta of DataInstance (**Optional parameter**).
        :return: The result is a tuple of 2 numpy.array. The first numpy.array is same as the return of get_prob and the second numpy.array is gradients on the model's input, not the input\_. So the shape of the second numpy.array is the same model input.
        :rtype: tuple

        This is used to get the gradients of a batch of sentences(presented as lists of tokens) on the appointed types.
        """
        raise ClassifierNotSupportException()


    def __hook(self, func, name, *args, **kwargs):
        fuclen = func.__code__.co_argcount - 1
        reqlen = 2 if name == "get_grad" else 1
        if len(args) > reqlen + 1:
            raise TypeError("Classifier.%s get too many arguments" % name)
        if len(args) < reqlen:
            raise TypeError("Classifier.%s missing %d required positional argument" % (name, reqlen - len(args)))
        if not isinstance(args[0], list):
            raise TypeError("input_ should be a list of %s" % ("tokens" if name == "get_grad" else "str"))
        if fuclen == reqlen:
            args = args[:reqlen]
        elif len(args) < fuclen:
            args = tuple([*args] + [{}])

        return func(*args, **kwargs)

    
    def __getattribute__(self, name):
        ret = super().__getattribute__(name)
        if name in ["get_pred", "get_prob", "get_grad"]:
            return functools.partial(self.__hook, ret, name)
        return ret

