#    Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import functools

from oslotest import base as test_base

from oslo_utils import reflection


RUNTIME_ERROR_CLASSES = [
    'RuntimeError',
    'Exception',
    'BaseException',
    'object',
]


def dummy_decorator(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        return f(*args, **kwargs)

    return wrapper


def mere_function(a, b):
    pass


def function_with_defs(a, b, optional=None):
    pass


def function_with_kwargs(a, b, **kwargs):
    pass


class TestObject:
    def _hello(self):
        pass

    def hi(self):
        pass


class Class:
    def method(self, c, d):
        pass

    @staticmethod
    def static_method(e, f):
        pass

    @classmethod
    def class_method(cls, g, h):
        pass


class BadClass:
    def do_something(self):
        pass

    def __nonzero__(self):
        return False


class CallableClass:
    def __call__(self, i, j):
        pass


class ClassWithInit:
    def __init__(self, k, lll):
        pass


class MemberGetTest(test_base.BaseTestCase):
    def test_get_members_exclude_hidden(self):
        obj = TestObject()
        members = list(reflection.get_members(obj, exclude_hidden=True))
        self.assertEqual(1, len(members))

    def test_get_members_no_exclude_hidden(self):
        obj = TestObject()
        members = list(reflection.get_members(obj, exclude_hidden=False))
        self.assertGreater(len(members), 1)

    def test_get_members_names_exclude_hidden(self):
        obj = TestObject()
        members = list(reflection.get_member_names(obj, exclude_hidden=True))
        self.assertEqual(["hi"], members)

    def test_get_members_names_no_exclude_hidden(self):
        obj = TestObject()
        members = list(reflection.get_member_names(obj, exclude_hidden=False))
        members = [member for member in members if not member.startswith("__")]
        self.assertEqual(["_hello", "hi"], sorted(members))


class CallbackEqualityTest(test_base.BaseTestCase):
    def test_different_simple_callbacks(self):
        def a():
            pass

        def b():
            pass

        self.assertFalse(reflection.is_same_callback(a, b))

    def test_static_instance_callbacks(self):
        class A:
            @staticmethod
            def b(a, b, c):
                pass

        a = A()
        b = A()

        self.assertTrue(reflection.is_same_callback(a.b, b.b))

    def test_different_instance_callbacks(self):
        class A:
            def b(self):
                pass

            def __eq__(self, other):
                return True

            def __ne__(self, other):
                return not self.__eq__(other)

        b = A()
        c = A()

        self.assertFalse(reflection.is_same_callback(b.b, c.b))
        self.assertTrue(reflection.is_same_callback(b.b, b.b))


class BoundMethodTest(test_base.BaseTestCase):
    def test_baddy(self):
        b = BadClass()
        self.assertTrue(reflection.is_bound_method(b.do_something))

    def test_static_method(self):
        self.assertFalse(reflection.is_bound_method(Class.static_method))


class GetCallableNameTest(test_base.BaseTestCase):
    def test_mere_function(self):
        name = reflection.get_callable_name(mere_function)
        self.assertEqual('.'.join((__name__, 'mere_function')), name)

    def test_method(self):
        name = reflection.get_callable_name(Class.method)
        self.assertEqual('.'.join((__name__, 'Class', 'method')), name)

    def test_instance_method(self):
        name = reflection.get_callable_name(Class().method)
        self.assertEqual('.'.join((__name__, 'Class', 'method')), name)

    def test_static_method(self):
        name = reflection.get_callable_name(Class.static_method)
        self.assertEqual('.'.join((__name__, 'Class', 'static_method')), name)

    def test_class_method(self):
        name = reflection.get_callable_name(Class.class_method)
        self.assertEqual('.'.join((__name__, 'Class', 'class_method')), name)

    def test_constructor(self):
        name = reflection.get_callable_name(Class)
        self.assertEqual('.'.join((__name__, 'Class')), name)

    def test_callable_class(self):
        name = reflection.get_callable_name(CallableClass())
        self.assertEqual('.'.join((__name__, 'CallableClass')), name)

    def test_callable_class_call(self):
        name = reflection.get_callable_name(CallableClass().__call__)
        self.assertEqual(
            '.'.join((__name__, 'CallableClass', '__call__')), name
        )


class GetCallableNameTestExtended(test_base.BaseTestCase):
    # Tests items in http://legacy.python.org/dev/peps/pep-3155/

    class InnerCallableClass:
        def __call__(self):
            pass

    def test_inner_callable_class(self):
        obj = self.InnerCallableClass()
        name = reflection.get_callable_name(obj.__call__)
        expected_name = '.'.join(
            (
                __name__,
                'GetCallableNameTestExtended',
                'InnerCallableClass',
                '__call__',
            )
        )
        self.assertEqual(expected_name, name)

    def test_inner_callable_function(self):
        def a():
            def b():
                pass

            return b

        name = reflection.get_callable_name(a())
        expected_name = '.'.join(
            (
                __name__,
                'GetCallableNameTestExtended',
                'test_inner_callable_function',
                '<locals>',
                'a',
                '<locals>',
                'b',
            )
        )
        self.assertEqual(expected_name, name)

    def test_inner_class(self):
        obj = self.InnerCallableClass()
        name = reflection.get_callable_name(obj)
        expected_name = '.'.join(
            (__name__, 'GetCallableNameTestExtended', 'InnerCallableClass')
        )
        self.assertEqual(expected_name, name)


class GetCallableArgsTest(test_base.BaseTestCase):
    def test_mere_function(self):
        result = reflection.get_callable_args(mere_function)
        self.assertEqual(['a', 'b'], result)

    def test_function_with_defaults(self):
        result = reflection.get_callable_args(function_with_defs)
        self.assertEqual(['a', 'b', 'optional'], result)

    def test_required_only(self):
        result = reflection.get_callable_args(
            function_with_defs, required_only=True
        )
        self.assertEqual(['a', 'b'], result)

    def test_method(self):
        result = reflection.get_callable_args(Class.method)
        self.assertEqual(['self', 'c', 'd'], result)

    def test_instance_method(self):
        result = reflection.get_callable_args(Class().method)
        self.assertEqual(['c', 'd'], result)

    def test_class_method(self):
        result = reflection.get_callable_args(Class.class_method)
        self.assertEqual(['g', 'h'], result)

    def test_class_constructor(self):
        result = reflection.get_callable_args(ClassWithInit)
        self.assertEqual(['k', 'lll'], result)

    def test_class_with_call(self):
        result = reflection.get_callable_args(CallableClass())
        self.assertEqual(['i', 'j'], result)

    def test_decorators_work(self):
        @dummy_decorator
        def special_fun(x, y):
            pass

        result = reflection.get_callable_args(special_fun)
        self.assertEqual(['x', 'y'], result)


class AcceptsKwargsTest(test_base.BaseTestCase):
    def test_no_kwargs(self):
        self.assertEqual(False, reflection.accepts_kwargs(mere_function))

    def test_with_kwargs(self):
        self.assertEqual(True, reflection.accepts_kwargs(function_with_kwargs))


class GetClassNameTest(test_base.BaseTestCase):
    def test_std_exception(self):
        name = reflection.get_class_name(RuntimeError)
        self.assertEqual('RuntimeError', name)

    def test_class(self):
        name = reflection.get_class_name(Class)
        self.assertEqual('.'.join((__name__, 'Class')), name)

    def test_qualified_class(self):
        class QualifiedClass:
            pass

        name = reflection.get_class_name(QualifiedClass)
        self.assertEqual('.'.join((__name__, 'QualifiedClass')), name)

    def test_instance(self):
        name = reflection.get_class_name(Class())
        self.assertEqual('.'.join((__name__, 'Class')), name)

    def test_int(self):
        name = reflection.get_class_name(42)
        self.assertEqual('int', name)

    def test_class_method(self):
        name = reflection.get_class_name(Class.class_method)
        self.assertEqual(f'{__name__}.Class', name)
        # test with fully_qualified=False
        name = reflection.get_class_name(
            Class.class_method, fully_qualified=False
        )
        self.assertEqual('Class', name)

    def test_static_method(self):
        self.assertRaises(
            TypeError, reflection.get_class_name, Class.static_method
        )

    def test_unbound_method(self):
        self.assertRaises(TypeError, reflection.get_class_name, mere_function)

    def test_bound_method(self):
        c = Class()
        name = reflection.get_class_name(c.method)
        self.assertEqual(f'{__name__}.Class', name)
        # test with fully_qualified=False
        name = reflection.get_class_name(c.method, fully_qualified=False)
        self.assertEqual('Class', name)


class GetAllClassNamesTest(test_base.BaseTestCase):
    def test_std_class(self):
        names = list(reflection.get_all_class_names(RuntimeError))
        self.assertEqual(RUNTIME_ERROR_CLASSES, names)

    def test_std_class_up_to(self):
        names = list(
            reflection.get_all_class_names(RuntimeError, up_to=Exception)
        )
        self.assertEqual(RUNTIME_ERROR_CLASSES[:-2], names)
