/****************************************************************************
 **
 ** Copyright (C) Qxt Foundation. Some rights reserved.
 **
 ** This file is part of the QxtCore module of the Qxt library.
 **
 ** This library is free software; you can redistribute it and/or modify it
 ** under the terms of the Common Public License, version 1.0, as published
 ** by IBM, and/or under the terms of the GNU Lesser General Public License,
 ** version 2.1, as published by the Free Software Foundation.
 **
 ** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
 ** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
 ** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
 ** FITNESS FOR A PARTICULAR PURPOSE.
 **
 ** You should have received a copy of the CPL and the LGPL along with this
 ** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
 ** included with the source distribution for more information.
 ** If you did not receive a copy of the licenses, contact the Qxt Foundation.
 **
 ** <http://libqxt.org>  <foundation@libqxt.org>
 **
 ****************************************************************************/

/*!
\namespace QxtMetaObject

\inmodule QxtCore

\brief The QxtMetaObject namespace provides extensions to QMetaObject

including QxtMetaObject::bind

*/

#include "qxtmetaobject.h"
#include "qxtboundfunction.h"
#include "qxtboundcfunction.h"
#include "qxtmetatype.h"

#include <QByteArray>
#include <QMetaObject>
#include <QMetaMethod>
#include <QtDebug>

#ifndef QXT_DOXYGEN_RUN
class QxtBoundArgument
{
    // This class intentionally left blank
};
Q_DECLARE_METATYPE(QxtBoundArgument)

class QxtBoundFunctionBase;

QxtBoundFunction::QxtBoundFunction(QObject* parent) : QObject(parent)
{
    // initializer only
}
#endif

bool QxtBoundFunction::invoke(Qt::ConnectionType type, QXT_IMPL_10ARGS(QVariant))
{
    return invoke(type, QXT_VAR_ARG(1), QXT_VAR_ARG(2), QXT_VAR_ARG(3), QXT_VAR_ARG(4), QXT_VAR_ARG(5), QXT_VAR_ARG(6), QXT_VAR_ARG(7), QXT_VAR_ARG(8), QXT_VAR_ARG(9), QXT_VAR_ARG(10));
}

bool QxtBoundFunction::invoke(Qt::ConnectionType type, QGenericReturnArgument returnValue, QXT_IMPL_10ARGS(QVariant))
{
    return invoke(type, returnValue, QXT_VAR_ARG(1), QXT_VAR_ARG(2), QXT_VAR_ARG(3), QXT_VAR_ARG(4), QXT_VAR_ARG(5), QXT_VAR_ARG(6), QXT_VAR_ARG(7), QXT_VAR_ARG(8), QXT_VAR_ARG(9), QXT_VAR_ARG(10));
}

QxtBoundFunctionBase::QxtBoundFunctionBase(QObject* parent, QGenericArgument* params[10], QByteArray types[10]) : QxtBoundFunction(parent)
{
    for (int i = 0; i < 10; i++)
    {
        if (!params[i]) break;
        if (QByteArray(params[i]->name()) == "QxtBoundArgument")
        {
            arg[i] = QGenericArgument("QxtBoundArgument", params[i]->data());
        }
        else
        {
            data[i] = qxtConstructFromGenericArgument(*params[i]);
            arg[i] = p[i] = QGenericArgument(params[i]->name(), data[i]);
        }
        bindTypes[i] = types[i];
    }
}

QxtBoundFunctionBase::~QxtBoundFunctionBase()
{
    for (int i = 0; i < 10; i++)
    {
        if (arg[i].name() == 0) return;
        if (QByteArray(arg[i].name()) != "QxtBoundArgument") qxtDestroyFromGenericArgument(arg[i]);
    }
}

int QxtBoundFunctionBase::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod)
    {
        if (_id == 0)
        {
            for (int i = 0; i < 10; i++)
            {
                if (QByteArray(arg[i].name()) == "QxtBoundArgument")
                {
                    p[i] = QGenericArgument(bindTypes[i].constData(), _a[(quintptr)(arg[i].data())]);
                }
            }
            invokeImpl(Qt::DirectConnection, QGenericReturnArgument(), p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
        }
        _id = -1;
    }
    return _id;
}

bool QxtBoundFunctionBase::invokeBase(Qt::ConnectionType type, QGenericReturnArgument returnValue, QXT_IMPL_10ARGS(QGenericArgument))
{
    QGenericArgument* args[10] = { &p1, &p2, &p3, &p4, &p5, &p6, &p7, &p8, &p9, &p10 };
    for (int i = 0; i < 10; i++)
    {
        if (QByteArray(arg[i].name()) == "QxtBoundArgument")
        {
            p[i] = *args[(quintptr)(arg[i].data()) - 1];
        }
    }
    return invokeImpl(type, returnValue, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
}

bool QxtBoundFunction::invoke(Qt::ConnectionType type, QGenericReturnArgument returnValue, QXT_IMPL_10ARGS(QGenericArgument))
{
    return reinterpret_cast<QxtBoundFunctionBase*>(this)->invokeBase(type, returnValue, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);
}

#ifndef QXT_DOXYGEN_RUN
class QxtBoundSlot : public QxtBoundFunctionBase
{
public:
    QByteArray sig;

    QxtBoundSlot(QObject* receiver, const char* invokable, QGenericArgument* params[10], QByteArray types[10]) : QxtBoundFunctionBase(receiver, params, types), sig(invokable)
    {
        // initializers only
    }

    virtual bool invokeImpl(Qt::ConnectionType type, QGenericReturnArgument returnValue, QXT_IMPL_10ARGS(QGenericArgument))
    {
        if (!QMetaObject::invokeMethod(parent(), QxtMetaObject::methodName(sig.constData()), type, returnValue, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10))
        {
            qWarning() << "QxtBoundFunction: call to" << sig << "failed";
            return false;
        }
        return true;
    }
};
#endif

namespace QxtMetaObject
{

    /*!
        Returns the name of the given method.

        Example usage:
        \code
        QByteArray method = QxtMetaObject::methodName(" int foo ( int bar, double baz )");
        // method is now "foo"
        \endcode
     */
    QByteArray methodName(const char* method)
    {
        QByteArray name = methodSignature(method);
        const int idx = name.indexOf("(");
        if (idx != -1)
            name.truncate(idx);
        return name;
    }

    /*!
        Returns the signature of the given method.
     */
    QByteArray methodSignature(const char* method)
    {
        QByteArray name = QMetaObject::normalizedSignature(method);
        if(name[0] >= '0' && name[0] <= '9')
            return name.mid(1);
        return name;
    }

    /*!
        Checks if \a method contains parentheses and begins with 1 or 2.
     */
    bool isSignalOrSlot(const char* method)
    {
        QByteArray m(method);
        return (m.count() && (m[0] >= '0' && m[0] <= '9') && m.contains('(') && m.contains(')'));
    }

    /*!
     * Creates a binding to the provided signal, slot, or Q_INVOKABLE method using the
     * provided parameter list. The type of each argument is deduced from the type of
     * the QVariant. This function cannot bind positional arguments; see the
     * overload using QGenericArgument.
     *
     * If the provided QObject does not implement the requested method, or if the
     * argument list is incompatible with the method's function signature, this
     * function returns NULL.
     *
     * The returned QxtBoundFunction is created as a child of the receiver.
     * Changing the parent will result in undefined behavior.
     *
     * \sa QxtMetaObject::connect, QxtBoundFunction
     */
    QxtBoundFunction* bind(QObject* recv, const char* invokable, QXT_IMPL_10ARGS(QVariant))
    {
        if (!recv)
        {
            qWarning() << "QxtMetaObject::bind: cannot connect to null QObject";
            return 0;
        }

        QVariant* args[10] = { &p1, &p2, &p3, &p4, &p5, &p6, &p7, &p8, &p9, &p10 };
        QByteArray connSlot("2"), recvSlot(QMetaObject::normalizedSignature(invokable));
        const QMetaObject* meta = recv->metaObject();
        int methodID = meta->indexOfMethod(QxtMetaObject::methodSignature(recvSlot.constData()));
        if (methodID == -1)
        {
            qWarning() << "QxtMetaObject::bind: no such method " << recvSlot;
            return 0;
        }
        QMetaMethod method = meta->method(methodID);
        int argCount = method.parameterTypes().count();
        const QList<QByteArray> paramTypes = method.parameterTypes();

        for (int i = 0; i < argCount; i++)
        {
            if (paramTypes[i] == "QxtBoundArgument") continue;
            int type = QMetaType::type(paramTypes[i].constData());
            if (!args[i]->canConvert((QVariant::Type)type))
            {
                qWarning() << "QxtMetaObject::bind: incompatible parameter list for " << recvSlot;
                return 0;
            }
        }

        return QxtMetaObject::bind(recv, invokable, QXT_ARG(1), QXT_ARG(2), QXT_ARG(3), QXT_ARG(4), QXT_ARG(5), QXT_ARG(6), QXT_ARG(7), QXT_ARG(8), QXT_ARG(9), QXT_ARG(10));
    }

    /*!
     * Creates a binding to the provided signal, slot, or Q_INVOKABLE method using the
     * provided parameter list. Use the Q_ARG macro to specify constant parameters, or
     * use the QXT_BIND macro to relay a parameter from a connected signal or passed
     * via the QxtBoundFunction::invoke() method.
     *
     * If the provided QObject does not implement the requested method, or if the
     * argument list is incompatible with the method's function signature, this
     * function returns NULL.
     *
     * The returned QxtBoundFunction is created as a child of the receiver.
     * Changing the parent will result in undefined behavior.
     *
     * \sa QxtMetaObject::connect, QxtBoundFunction, QXT_BIND
     */
    QxtBoundFunction* bind(QObject* recv, const char* invokable, QXT_IMPL_10ARGS(QGenericArgument))
    {
        if (!recv)
        {
            qWarning() << "QxtMetaObject::bind: cannot connect to null QObject";
            return 0;
        }

        QGenericArgument* args[10] = { &p1, &p2, &p3, &p4, &p5, &p6, &p7, &p8, &p9, &p10 };
        QByteArray connSlot("2"), recvSlot(QMetaObject::normalizedSignature(invokable)), bindTypes[10];
        const QMetaObject* meta = recv->metaObject();
        int methodID = meta->indexOfMethod(QxtMetaObject::methodSignature(recvSlot.constData()).constData());
        if (methodID == -1)
        {
            qWarning() << "QxtMetaObject::bind: no such method " << recvSlot;
            return 0;
        }
        QMetaMethod method = meta->method(methodID);
        int argCount = method.parameterTypes().count();

        connSlot += QxtMetaObject::methodName(invokable) + '(';
        for (int i = 0; i < 10; i++)
        {
            if (args[i]->name() == 0) break;        // done
            if (i >= argCount)
            {
                qWarning() << "QxtMetaObject::bind: too many arguments passed to " << invokable;
                return 0;
            }
            if (i > 0) connSlot += ',';             // argument separator
            if (QByteArray(args[i]->name()) == "QxtBoundArgument")
            {
                Q_ASSERT_X((quintptr)(args[i]->data()) > 0 && (quintptr)(args[i]->data()) <= 10, "QXT_BIND", "invalid argument number");
                connSlot += method.parameterTypes()[i];
                bindTypes[i] = method.parameterTypes()[i];
            }
            else
            {
                connSlot += args[i]->name();        // type name
            }
        }
        connSlot = QMetaObject::normalizedSignature(connSlot += ')');

        if (!QMetaObject::checkConnectArgs(recvSlot.constData(), connSlot.constData()))
        {
            qWarning() << "QxtMetaObject::bind: provided parameters " << connSlot.mid(connSlot.indexOf('(')) << " is incompatible with " << invokable;
            return 0;
        }

        return new QxtBoundSlot(recv, invokable, args, bindTypes);
    }

    /*!
        Connects a signal to a QxtBoundFunction.
     */
    bool connect(QObject* sender, const char* signal, QxtBoundFunction* slot, Qt::ConnectionType type)
    {
        const QMetaObject* meta = sender->metaObject();
        int methodID = meta->indexOfMethod(meta->normalizedSignature(signal).mid(1).constData());
        if (methodID < 0)
        {
            qWarning() << "QxtMetaObject::connect: no such signal: " << QByteArray(signal).mid(1);
            return false;
        }

        return QMetaObject::connect(sender, methodID, slot, QObject::staticMetaObject.methodCount(), (int)(type));
    }

    /*!
        \relates QxtMetaObject
        This overload always invokes the member using the connection type Qt::AutoConnection.

        \sa QMetaObject::invokeMethod()
     */
    bool invokeMethod(QObject* object, const char* member, const QVariant& arg0,
                      const QVariant& arg1, const QVariant& arg2, const QVariant& arg3,
                      const QVariant& arg4, const QVariant& arg5, const QVariant& arg6,
                      const QVariant& arg7, const QVariant& arg8, const QVariant& arg9)
    {
        return invokeMethod(object, member, Qt::AutoConnection,
                            arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
    }

    /*!
        \relates QxtMetaObject

        Invokes the \a member (a signal or a slot name) on the \a object.
        Returns \c true if the member could be invoked. Returns \c false
        if there is no such member or the parameters did not match.

        \sa QMetaObject::invokeMethod()
     */
    bool invokeMethod(QObject* object, const char* member, Qt::ConnectionType type,
                      const QVariant& arg0, const QVariant& arg1, const QVariant& arg2,
                      const QVariant& arg3, const QVariant& arg4, const QVariant& arg5,
                      const QVariant& arg6, const QVariant& arg7, const QVariant& arg8, const QVariant& arg9)
    {
        #define QXT_MO_ARG(i) QGenericArgument(arg ## i.typeName(), arg ## i.constData())
        return QMetaObject::invokeMethod(object, methodName(member), type,
                                         QXT_MO_ARG(0), QXT_MO_ARG(1), QXT_MO_ARG(2), QXT_MO_ARG(3), QXT_MO_ARG(4),
                                         QXT_MO_ARG(5), QXT_MO_ARG(6), QXT_MO_ARG(7), QXT_MO_ARG(8), QXT_MO_ARG(9));
    }
}
