import unittest

import wrapt

from compat import PYXY


@wrapt.synchronized
def function():
    print("function")


class C1:

    @wrapt.synchronized
    def function1(self):
        print("function1")

    @wrapt.synchronized
    @classmethod
    def function2(cls):
        print("function2")

    @wrapt.synchronized
    @staticmethod
    def function3():
        print("function3")


c1 = C1()


@wrapt.synchronized
class C2:
    pass


@wrapt.synchronized
class C3:
    pass


class C4:

    # Prior to Python 3.9, this yields undesirable results due to how
    # class method is implemented. The classmethod doesn't bind the
    # method to the class before calling. As a consequence, the
    # decorator wrapper function sees the instance as None with the
    # class being explicitly passed as the first argument. It isn't
    # possible to detect and correct this. For more details see:
    # https://bugs.python.org/issue19072

    @classmethod
    @wrapt.synchronized
    def function2(cls):
        print("function2")

    @staticmethod
    @wrapt.synchronized
    def function3():
        print("function3")


c4 = C4()


class C5:

    def __bool__(self):
        return False

    __nonzero__ = __bool__

    @wrapt.synchronized
    def function1(self):
        print("function1")


c5 = C5()


class TestSynchronized(unittest.TestCase):

    def test_synchronized_function(self):
        _lock0 = getattr(function, "_synchronized_lock", None)
        self.assertEqual(_lock0, None)

        function()

        _lock1 = getattr(function, "_synchronized_lock", None)
        self.assertNotEqual(_lock1, None)

        function()

        _lock2 = getattr(function, "_synchronized_lock", None)
        self.assertNotEqual(_lock2, None)
        self.assertEqual(_lock2, _lock1)

        function()

        _lock3 = getattr(function, "_synchronized_lock", None)
        self.assertNotEqual(_lock3, None)
        self.assertEqual(_lock3, _lock2)

    def test_synchronized_inner_staticmethod(self):
        _lock0 = getattr(C1.function3, "_synchronized_lock", None)
        self.assertEqual(_lock0, None)

        c1.function3()

        _lock1 = getattr(C1.function3, "_synchronized_lock", None)
        self.assertNotEqual(_lock1, None)

        C1.function3()

        _lock2 = getattr(C1.function3, "_synchronized_lock", None)
        self.assertNotEqual(_lock2, None)
        self.assertEqual(_lock2, _lock1)

        C1.function3()

        _lock3 = getattr(C1.function3, "_synchronized_lock", None)
        self.assertNotEqual(_lock3, None)
        self.assertEqual(_lock3, _lock2)

    def test_synchronized_outer_staticmethod(self):
        _lock0 = getattr(C4.function3, "_synchronized_lock", None)
        self.assertEqual(_lock0, None)

        c4.function3()

        _lock1 = getattr(C4.function3, "_synchronized_lock", None)
        self.assertNotEqual(_lock1, None)

        C4.function3()

        _lock2 = getattr(C4.function3, "_synchronized_lock", None)
        self.assertNotEqual(_lock2, None)
        self.assertEqual(_lock2, _lock1)

        C4.function3()

        _lock3 = getattr(C4.function3, "_synchronized_lock", None)
        self.assertNotEqual(_lock3, None)
        self.assertEqual(_lock3, _lock2)

    def test_synchronized_inner_classmethod(self):
        if hasattr(C1, "_synchronized_lock"):
            del C1._synchronized_lock

        _lock0 = getattr(C1, "_synchronized_lock", None)
        self.assertEqual(_lock0, None)

        c1.function2()

        _lock1 = getattr(C1, "_synchronized_lock", None)
        self.assertNotEqual(_lock1, None)

        C1.function2()

        _lock2 = getattr(C1, "_synchronized_lock", None)
        self.assertNotEqual(_lock2, None)
        self.assertEqual(_lock2, _lock1)

        C1.function2()

        _lock3 = getattr(C1, "_synchronized_lock", None)
        self.assertNotEqual(_lock3, None)
        self.assertEqual(_lock3, _lock2)

    def test_synchronized_outer_classmethod(self):
        # Prior to Python 3.9 this isn't detected as a class method
        # call, as the classmethod decorator doesn't bind the wrapped
        # function to the class before calling and just calls it direct,
        # explicitly passing the class as first argument. For more
        # details see: https://bugs.python.org/issue19072
        # Starting with Python 3.13 the old behavior is back.
        # For more details see https://github.com/python/cpython/issues/89519

        if (3, 9) <= PYXY < (3, 13):
            _lock0 = getattr(C4, "_synchronized_lock", None)
        else:
            _lock0 = getattr(C4.function2, "_synchronized_lock", None)
        self.assertEqual(_lock0, None)

        c4.function2()

        if (3, 9) <= PYXY < (3, 13):
            _lock1 = getattr(C4, "_synchronized_lock", None)
        else:
            _lock1 = getattr(C4.function2, "_synchronized_lock", None)
        self.assertNotEqual(_lock1, None)

        C4.function2()

        if (3, 9) <= PYXY < (3, 13):
            _lock2 = getattr(C4, "_synchronized_lock", None)
        else:
            _lock2 = getattr(C4.function2, "_synchronized_lock", None)
        self.assertNotEqual(_lock2, None)
        self.assertEqual(_lock2, _lock1)

        C4.function2()

        if (3, 9) <= PYXY < (3, 13):
            _lock3 = getattr(C4, "_synchronized_lock", None)
        else:
            _lock3 = getattr(C4.function2, "_synchronized_lock", None)
        self.assertNotEqual(_lock3, None)
        self.assertEqual(_lock3, _lock2)

    def test_synchronized_instancemethod(self):
        if hasattr(C1, "_synchronized_lock"):
            del C1._synchronized_lock

        _lock0 = getattr(c1, "_synchronized_lock", None)
        self.assertEqual(_lock0, None)

        C1.function1(c1)

        _lock1 = getattr(c1, "_synchronized_lock", None)
        self.assertNotEqual(_lock1, None)

        c1.function1()

        _lock2 = getattr(c1, "_synchronized_lock", None)
        self.assertNotEqual(_lock2, None)
        self.assertEqual(_lock2, _lock1)

        c1.function1()

        _lock3 = getattr(c1, "_synchronized_lock", None)
        self.assertNotEqual(_lock3, None)
        self.assertEqual(_lock3, _lock2)

        del c1._synchronized_lock

        C1.function2()

        _lock4 = getattr(C1, "_synchronized_lock", None)
        self.assertNotEqual(_lock4, None)

        c1.function1()

        _lock5 = getattr(c1, "_synchronized_lock", None)
        self.assertNotEqual(_lock5, None)
        self.assertNotEqual(_lock5, _lock4)

    def test_synchronized_type_new_style(self):
        if hasattr(C2, "_synchronized_lock"):
            del C2._synchronized_lock

        _lock0 = getattr(C2, "_synchronized_lock", None)
        self.assertEqual(_lock0, None)

        c2 = C2()

        _lock1 = getattr(C2, "_synchronized_lock", None)
        self.assertNotEqual(_lock1, None)

        c2 = C2()

        _lock2 = getattr(C2, "_synchronized_lock", None)
        self.assertNotEqual(_lock2, None)
        self.assertEqual(_lock2, _lock1)

        c2 = C2()

        _lock3 = getattr(C2, "_synchronized_lock", None)
        self.assertNotEqual(_lock3, None)
        self.assertEqual(_lock3, _lock2)

    def test_synchronized_type_old_style(self):
        if hasattr(C3, "_synchronized_lock"):
            del C3._synchronized_lock

        _lock0 = getattr(C3, "_synchronized_lock", None)
        self.assertEqual(_lock0, None)

        c2 = C3()

        _lock1 = getattr(C3, "_synchronized_lock", None)
        self.assertNotEqual(_lock1, None)

        c2 = C3()

        _lock2 = getattr(C3, "_synchronized_lock", None)
        self.assertNotEqual(_lock2, None)
        self.assertEqual(_lock2, _lock1)

        c2 = C3()

        _lock3 = getattr(C3, "_synchronized_lock", None)
        self.assertNotEqual(_lock3, None)
        self.assertEqual(_lock3, _lock2)

    def test_synchronized_false_instance(self):
        c5.function1()

        self.assertEqual(bool(c5), False)

        _lock1 = getattr(C5, "_synchronized_lock", None)
        self.assertEqual(_lock1, None)

        _lock2 = getattr(c5, "_synchronized_lock", None)
        self.assertNotEqual(_lock2, None)


if __name__ == "__main__":
    unittest.main()
