"""
Testing the base decorator implementation
"""

import unittest
import rna.pattern.decorator


# pylint:disable=too-few-public-methods,invalid-name
class prepend_args_default_kwargs(rna.pattern.decorator.Decorator):
    """
    Example decorator on how to build the above easier.
    """

    def __init__(self, *args, **kwargs):
        self.args = args
        self.keywords = kwargs

    def _wrap(self, this, func, *args, **kwargs):
        args = self.args + args
        for k, v in self.keywords.items():
            kwargs.setdefault(k, v)
        assert this is None or isinstance(this, ExampleClass) or this is ExampleClass
        return func(*args, **kwargs)


# pylint:disable=too-few-public-methods,invalid-name
class set_attr(rna.pattern.decorator.Decorator):
    """
    Example decorator
    """

    def _wrap(self, this, func, *args, **kwargs):
        if this is not None:  # method
            this.new_attr = 700
        return func(*args, **kwargs)


def nested_deco(*def_args, **def_kwargs):
    """
    Example for how to combine two decorators
    """

    def wrap(fun):
        dec = set_attr(prepend_args_default_kwargs(*def_args, **def_kwargs)(fun))

        def inner(*args, **kwargs):
            return dec(*args, **kwargs)

        return inner

    return wrap


A_DECO = True
Z_DECO = False


class ExampleClass:
    """Methods and static methods that return (self,) args and kwargs"""

    @prepend_args_default_kwargs
    def example_method(self, *args, **kwargs):
        """
        My meth
        """
        return self, args, kwargs

    @prepend_args_default_kwargs(A_DECO, z=Z_DECO)
    def example_method_variant(self, *args, **kwargs):
        """
        My meth
        """
        return self, args, kwargs

    @staticmethod
    @prepend_args_default_kwargs
    def example_static(*args, **kwargs):
        """
        My smeth
        """
        return args, kwargs

    @staticmethod
    @prepend_args_default_kwargs(A_DECO, z=Z_DECO)
    def example_static_variant(*args, **kwargs):
        """
        My smeth
        """
        return args, kwargs

    @set_attr
    @prepend_args_default_kwargs(A_DECO, z=Z_DECO)
    def example_twice_decorated(self, *args, **kwargs):
        """
        My decoration with Decorator
        """
        return (self.__dict__, args, kwargs)

    @nested_deco(A_DECO, z=Z_DECO)
    def example_twice_decorated_but_nested(self, *args, **kwargs):
        """
        My twice decorated method
        """
        return (self.__dict__, args, kwargs)

    @classmethod
    @prepend_args_default_kwargs(A_DECO, z=Z_DECO)
    def example_classmethod_variant(cls, *args, **kwargs):
        """
        My test decorated classmethod
        """
        return (cls, args, kwargs)

    # pylint:disable=no-self-use
    def example_static_inside_mthd_variant(self, *args, **kwargs):
        """
        My undecorated method forwarding to decorated function
        """

        @prepend_args_default_kwargs(A_DECO, z=Z_DECO)
        def example_inner_func_variant(*args, **kwargs):
            """My example func variant"""
            return args, kwargs

        return example_inner_func_variant(*args, **kwargs)


@prepend_args_default_kwargs
def example_func(*args, **kwargs):
    """My example func"""
    return args, kwargs


@prepend_args_default_kwargs(A_DECO, z=Z_DECO)
def example_func_variant(*args, **kwargs):
    """My example func variant"""
    return args, kwargs


@prepend_args_default_kwargs(ExampleClass, ...)
def example_type_only_argument(*args, **kwargs):
    """My example func variant with only one argument"""
    return args, kwargs


@prepend_args_default_kwargs()
def example_no_argument(*args, **kwargs):
    """My example func variant without argument"""
    return args, kwargs


class DecoratorTest(unittest.TestCase):
    """
    Color test
    """

    def setUp(self):
        self.inst = ExampleClass()
        self.wrappers = [
            self.inst.example_static,
            self.inst.example_static_variant,
            self.inst.example_method,
            self.inst.example_method_variant,
            self.inst.example_static_inside_mthd_variant,
            example_func,
            example_func_variant,
            # ExampleClass.example_classmethod,
            ExampleClass.example_classmethod_variant,
        ]

    def test_decoration(self):
        """
        Test function and method decoration
        """
        for fun in self.wrappers:
            a = 21
            z = 0
            x = 1
            res = fun(a, x=x, z=z)
            if "classmethod" in fun.__name__:
                cls, args, kwargs = res
                self.assertIs(cls, ExampleClass)
            elif "method" in fun.__name__:
                inst, args, kwargs = res
                self.assertIs(inst, self.inst)
            else:
                args, kwargs = res
            if "variant" in fun.__name__:
                self.assertTupleEqual(
                    args,
                    (
                        A_DECO,
                        a,
                    ),
                )
                self.assertDictEqual(kwargs, dict(x=x, z=Z_DECO))
            else:
                self.assertTupleEqual(args, (a,))
                self.assertDictEqual(kwargs, dict(x=x, z=z))

    def test_docs(self):
        """
        Test docstrings of wrappers
        """
        for fun in self.wrappers:
            self.assertTrue("My" in fun.__doc__)

    def test_nested(self):
        """
        Test the joined decorator
        """
        self.assertTupleEqual(
            ExampleClass().example_twice_decorated(15, d=6),
            ExampleClass().example_twice_decorated_but_nested(15, d=6),
        )

    def test_type_only_argument(self):
        """
        Test if meta handles types
        """
        # pylint:disable=unused-variable
        args, kwargs = example_type_only_argument()
        self.assertIs(args[0], ExampleClass)
        self.assertEqual(len(args), 1)

    def test_no_argument(self):
        """
        Test if empty arguments breaks meta
        """
        # pylint:disable=unused-variable
        args, kwargs = example_no_argument()
        self.assertEqual(len(args), 0)


if __name__ == "__main__":
    pass
