#!/usr/bin/python
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
from __future__ import annotations

'''Tests for static methos conflicts with class methods'''

import os
import sys
import unittest

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

from PySide6.QtCore import (QCoreApplication, QFile, QMetaMethod, QMetaObject,
                            QObject, QModelIndex, QPoint, QTimer, QSemaphore,
                            QStringListModel, Qt, Signal, Slot,
                            SIGNAL, Q_ARG, Q_RETURN_ARG)


class Foo(QFile):
    pass


class DynObject(QObject):
    def slot(self):
        pass


class InvokeTester(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)

    @Slot(int, int, result=int)
    def add(self, a, b):
        return a + b

    @Slot(str, str, result=str)
    def concatenate(self, a, b):
        return a + b

    @Slot(QPoint, result=int)
    def manhattan_length(self, point):
        return abs(point.x()) + abs(point.y())

    @Slot(QPoint, QPoint, result=QPoint)
    def add_points(self, point1, point2):
        return point1 + point2

    @Slot(QObject, result=str)
    def object_name(self, o):
        return o.objectName()

    @Slot(QObject, result=QObject)
    def first_child(self, o):
        return o.children()[0]


class SemaphoreSender(QObject):
    signal = Signal(QSemaphore)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.semaphore = QSemaphore()

    def emitSignal(self):
        self.signal.emit(self.semaphore)


class SemaphoreReceiver(QObject):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.semaphore = None

    @Slot(QSemaphore)
    def receiverSlot(self, semaphore):
        self.semaphore = semaphore


class qmetaobject_test(unittest.TestCase):
    """
    def test_QMetaObject(self):
        qobj = QObject()
        qobj_metaobj = qobj.metaObject()
        self.assertEqual(qobj_metaobj.className(), "QObject")

        obj = QFile()
        m = obj.metaObject()
        self.assertEqual(m.className(), "QFile")
        self.assertNotEqual(m.methodCount(), qobj_metaobj.methodCount())

        obj = Foo()
        m = obj.metaObject()
        self.assertEqual(m.className(), "Foo")
        f = QFile()
        fm = f.metaObject()
        self.assertEqual(m.methodCount(), fm.methodCount())
    """

    def test_DynamicSlotSignal(self):
        o = DynObject()
        o2 = QObject()

        o.connect(o2, SIGNAL("bars()"), o.slot)
        self.assertTrue(o2.metaObject().indexOfMethod("bars()") > -1)

    # PYSIDE-784, plain Qt objects should not have intermediary
    # metaObjects.
    def test_PlainQObject(self):
        timer = QTimer()
        self.assertEqual(timer.metaObject().superClass().className(),
                         "QObject")

    # PYSIDE-1827, slots with non-QObject object types should work
    # (metatypes are registered)
    def test_ObjectSlotSignal(self):
        app = QCoreApplication()  # noqa: F841
        sender = SemaphoreSender()
        receiver = SemaphoreReceiver()
        sender.signal.connect(receiver.receiverSlot, Qt.ConnectionType.QueuedConnection)
        sender.emitSignal()
        while not receiver.semaphore:
            QCoreApplication.processEvents()
        self.assertEqual(sender.semaphore, receiver.semaphore)

    # PYSIDE-1898,
    def test_Invoke(self):
        tester = InvokeTester()

        # Primitive types
        sum = QMetaObject.invokeMethod(tester, "add", Q_RETURN_ARG(int),
                                       Q_ARG(int, 2), Q_ARG(int, 3))
        self.assertEqual(sum, 5)

        # Same with QMetaMethod
        mo = tester.metaObject()
        method = mo.method(mo.indexOfMethod("add(int,int)"))
        self.assertTrue(method.isValid())
        sum = method.invoke(tester, Qt.ConnectionType.AutoConnection,
                            Q_RETURN_ARG(int), Q_ARG(int, 2), Q_ARG(int, 3))
        self.assertEqual(sum, 5)
        sum = method.invoke(tester, Q_RETURN_ARG(int), Q_ARG(int, 2),
                            Q_ARG(int, 3))
        self.assertEqual(sum, 5)

        concatenated = QMetaObject.invokeMethod(tester, "concatenate",
                                                Q_RETURN_ARG(str),
                                                Q_ARG(str, "bla"),
                                                Q_ARG(str, "bla"))
        self.assertEqual(concatenated, "blabla")

        # Wrapped type as in-parameter
        point = QPoint(10, 20)
        len = QMetaObject.invokeMethod(tester, "manhattan_length",
                                       Q_RETURN_ARG(int),
                                       Q_ARG(QPoint, point))
        self.assertEqual(len, point.manhattanLength())

        # Wrapped type as result
        point2 = QPoint(30, 30)
        point3 = QMetaObject.invokeMethod(tester, "add_points",
                                          Q_RETURN_ARG(QPoint),
                                          Q_ARG(QPoint, point),
                                          Q_ARG(QPoint, point2))
        self.assertEqual(point + point2, point3)

        # Pass an object
        o = QObject()
        o.setObjectName("Foo")
        name = QMetaObject.invokeMethod(tester, "object_name",
                                        Q_RETURN_ARG(str),
                                        Q_ARG(QObject, o))
        self.assertEqual(name, o.objectName())

        # Return an object
        child = QObject(o)
        child.setObjectName("Child")
        c = QMetaObject.invokeMethod(tester, "first_child",
                                     Q_RETURN_ARG(QObject),
                                     Q_ARG(QObject, o))
        self.assertTrue(c)
        self.assertEqual(c, child)

    def test_InvokeTypeString(self):
        strings = ["item1", "item2"]
        model = QStringListModel(strings)
        index = model.index(1, 0)
        QMetaObject.invokeMethod(model, "setData",
                                 Q_ARG(QModelIndex, index),
                                 Q_ARG("QVariant", "bla"))
        self.assertEqual(model.data(index), "bla")

        # Same with QMetaMethod
        mo = model.metaObject()
        method = mo.method(mo.indexOfMethod("setData(QModelIndex,QVariant)"))
        self.assertTrue(method.isValid())
        method.invoke(model, Qt.ConnectionType.AutoConnection,
                      Q_ARG(QModelIndex, index), Q_ARG("QVariant", "blub"))
        self.assertEqual(model.data(index), "blub")
        method.invoke(model, Q_ARG(QModelIndex, index), Q_ARG("QVariant", "blip"))
        self.assertEqual(model.data(index), "blip")

    def test_QMetaMethod(self):
        o = QObject()
        m = QMetaMethod.fromSignal(o.destroyed)
        self.assertTrue(m.isValid)
        self.assertEqual(m.methodSignature(), b"destroyed()")


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