#!/usr/bin/env python
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial

'''Test cases for multiple inheritance'''

import os
import sys
import unittest

from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
from shiboken_paths import init_paths
init_paths()

from sample import Base1, Base2
from sample import MDerived1, MDerived2, MDerived3, MDerived4, MDerived5, SonOfMDerived1


class ExtMDerived1(MDerived1):
    def __init__(self):
        MDerived1.__init__(self)
        self.multiplier = 20
        self.base2Method_called = False

    def base2Method(self):
        return Base2.base2Method(self) * self.multiplier


class MultipleDerivedTest(unittest.TestCase):
    '''Test cases for multiple inheritance'''

    def testIsInstance(self):
        '''MDerived1 is instance of its parents Base1 and Base2.'''
        a = MDerived1()
        self.assertTrue(isinstance(a, MDerived1))
        self.assertTrue(isinstance(a, Base1))
        self.assertTrue(isinstance(a, Base2))

    def testIsSubclass(self):
        '''MDerived1 is subclass of its parents Base1 and Base2.'''
        self.assertTrue(issubclass(MDerived1, Base1))
        self.assertTrue(issubclass(MDerived1, Base2))

    def testCallToFunctionWithBase1ArgumentThatCastsBackToMDerived1(self):
        '''MDerived1 is passed as an Base1 argument to a method that returns
           it casted back to MDerived1.'''
        a = MDerived1()
        b = MDerived1.transformFromBase1(a)
        self.assertEqual(a, b)

    def testCallToFunctionWithBase2ArgumentThatCastsBackToMDerived1(self):
        '''MDerived1 is passed as an Base2 argument to a method that returns
           it casted back to MDerived1.'''
        a = MDerived1()
        b = MDerived1.transformFromBase2(a)
        self.assertEqual(a, b)

    def testPythonClassIsInstance(self):
        '''Python defined class ExtMDerived1 is instance of its parents
           MDerived1, Base1 and Base2.'''
        a = ExtMDerived1()
        self.assertTrue(isinstance(a, ExtMDerived1))
        self.assertTrue(isinstance(a, MDerived1))
        self.assertTrue(isinstance(a, Base1))
        self.assertTrue(isinstance(a, Base2))

    def testPythonClassIsSubclass(self):
        '''Python defined class ExtMDerived1 is subclass of its parents
           MDerived1, Base1 and Base2.'''
        self.assertTrue(issubclass(ExtMDerived1, MDerived1))
        self.assertTrue(issubclass(ExtMDerived1, Base1))
        self.assertTrue(issubclass(ExtMDerived1, Base2))

    @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
    def testCastFromMDerived1ToBases(self):
        '''MDerived1 is casted by C++ to its parents and the binding must return the
           MDerived1 wrapper.'''
        a = MDerived1()
        refcnt = sys.getrefcount(a)
        b1 = a.castToBase1()
        b2 = a.castToBase2()
        self.assertTrue(isinstance(b1, MDerived1))
        self.assertTrue(isinstance(b2, MDerived1))
        self.assertEqual(a, b1)
        self.assertEqual(a, b2)
        self.assertEqual(sys.getrefcount(a), refcnt + 2)

    @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
    def testCastFromExtMDerived1ToMDerived1Bases(self):
        '''Python defined class ExtMDerived1 is casted by C++ to MDerived1 parents
           and the binding must return the correct ExtMDerived1 instance.'''
        a = ExtMDerived1()
        refcnt = sys.getrefcount(a)
        b1 = a.castToBase1()
        self.assertTrue(isinstance(b1, MDerived1))
        self.assertTrue(isinstance(b1, ExtMDerived1))
        b2 = a.castToBase2()
        self.assertTrue(isinstance(b2, MDerived1))
        self.assertTrue(isinstance(b2, ExtMDerived1))
        self.assertEqual(a, b1)
        self.assertEqual(a, b2)
        self.assertEqual(sys.getrefcount(a), refcnt + 2)

    @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
    def testCastFromSonOfMDerived1ToBases(self):
        '''SonOfMDerived1 is casted by C++ to its parents and the binding must return
           the SonOfMDerived1 wrapper.'''
        a = SonOfMDerived1()
        refcnt = sys.getrefcount(a)
        md1 = a.castToMDerived1()
        b1 = a.castToBase1()
        b2 = a.castToBase2()
        self.assertTrue(isinstance(md1, SonOfMDerived1))
        self.assertTrue(isinstance(b2, SonOfMDerived1))
        self.assertTrue(isinstance(b2, SonOfMDerived1))
        self.assertEqual(a, md1)
        self.assertEqual(a, b1)
        self.assertEqual(a, b2)
        self.assertEqual(sys.getrefcount(a), refcnt + 3)

    def testReimplementedBase2VirtualMethodOnClassInheritingFromMDerived1(self):
        a = ExtMDerived1()
        value = a.base2Method()
        self.assertTrue(value, Base2.base2Method(a) * a.multiplier)

    @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
    def testCastFromMDerived2ToBases(self):
        '''MDerived2 is casted by C++ to its parents and the binding must
           return the MDerived2 wrapper.'''
        a = MDerived2()
        refcnt = sys.getrefcount(a)
        b3 = a.castToBase3()
        b4 = a.castToBase4()
        b5 = a.castToBase5()
        b6 = a.castToBase6()
        self.assertTrue(isinstance(b3, MDerived2))
        self.assertTrue(isinstance(b4, MDerived2))
        self.assertTrue(isinstance(b5, MDerived2))
        self.assertTrue(isinstance(b6, MDerived2))
        self.assertEqual(a, b3)
        self.assertEqual(a, b4)
        self.assertEqual(a, b5)
        self.assertEqual(a, b6)
        self.assertEqual(sys.getrefcount(a), refcnt + 4)

    @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
    def testCastFromMDerived3ToBases(self):
        '''MDerived3 is casted by C++ to its parents and the binding must
           return the MDerived3 wrapper.'''
        a = MDerived3()
        refcnt = sys.getrefcount(a)
        md1 = a.castToMDerived1()
        md2 = a.castToMDerived2()
        b1 = a.castToBase1()
        b2 = a.castToBase2()
        b3 = a.castToBase3()
        b4 = a.castToBase4()
        b5 = a.castToBase5()
        b6 = a.castToBase6()
        self.assertTrue(isinstance(md1, MDerived3))
        self.assertTrue(isinstance(md2, MDerived3))
        self.assertTrue(isinstance(b1, MDerived3))
        self.assertTrue(isinstance(b2, MDerived3))
        self.assertTrue(isinstance(b3, MDerived3))
        self.assertTrue(isinstance(b4, MDerived3))
        self.assertTrue(isinstance(b5, MDerived3))
        self.assertTrue(isinstance(b6, MDerived3))
        self.assertEqual(a, md1)
        self.assertEqual(a, md2)
        self.assertEqual(a, b1)
        self.assertEqual(a, b2)
        self.assertEqual(a, b3)
        self.assertEqual(a, b4)
        self.assertEqual(a, b5)
        self.assertEqual(a, b6)
        self.assertEqual(sys.getrefcount(a), refcnt + 8)

    @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
    def testCastFromMDerived4ToBases(self):
        '''MDerived4 is casted by C++ to its parents and the binding must
           return the MDerived4 wrapper.'''
        a = MDerived4()
        refcnt = sys.getrefcount(a)
        b3 = a.castToBase3()
        b4 = a.castToBase4()
        self.assertTrue(isinstance(b3, MDerived4))
        self.assertTrue(isinstance(b4, MDerived4))
        self.assertEqual(a, b3)
        self.assertEqual(a, b4)
        self.assertEqual(sys.getrefcount(a), refcnt + 2)

    @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
    def testCastFromMDerived5ToBases(self):
        '''MDerived5 is casted by C++ to its parents and the binding must
           return the MDerived5 wrapper.'''
        a = MDerived5()
        refcnt = sys.getrefcount(a)
        b3 = a.castToBase3()
        b4 = a.castToBase4()
        self.assertTrue(isinstance(b3, MDerived5))
        self.assertTrue(isinstance(b4, MDerived5))
        self.assertEqual(a, b3)
        self.assertEqual(a, b4)
        self.assertEqual(sys.getrefcount(a), refcnt + 2)

    @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
    def testCastFromMDerived3ToBase3(self):
        '''MDerived3 is casted by C++ to Base3 grandparent using both the inherited
           and reimplement castToBase3 methods.'''
        a = MDerived3()
        refcnt = sys.getrefcount(a)
        b3_reimplemented = a.castToBase3()
        b3_inherited = MDerived2.castToBase3(a)
        self.assertTrue(isinstance(b3_reimplemented, MDerived3))
        self.assertTrue(isinstance(b3_inherited, MDerived3))
        self.assertEqual(a, b3_reimplemented)
        self.assertEqual(a, b3_inherited)
        self.assertEqual(sys.getrefcount(a), refcnt + 2)


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