/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtScript module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "config.h"
#include "qscriptqobject_p.h"

#include <QtCore/qmetaobject.h>
#include <QtCore/qvarlengtharray.h>
#include <QtCore/qdebug.h>
#include <QtScript/qscriptable.h>
#include "../api/qscriptengine_p.h"
#include "../api/qscriptable_p.h"
#include "../api/qscriptcontext_p.h"
#include "qscriptfunction_p.h"
#include "qscriptactivationobject_p.h"
#include "qscriptvariant_p.h"

#include "Error.h"
#include "PrototypeFunction.h"
#include "NativeFunctionWrapper.h"
#include "PropertyNameArray.h"
#include "JSFunction.h"
#include "JSString.h"
#include "JSValue.h"
#include "JSArray.h"
#include "RegExpObject.h"
#include "RegExpConstructor.h"

namespace JSC
{
QT_USE_NAMESPACE
ASSERT_CLASS_FITS_IN_CELL(QScript::QObjectPrototype);
ASSERT_CLASS_FITS_IN_CELL(QScript::QMetaObjectWrapperObject);
ASSERT_CLASS_FITS_IN_CELL(QScript::QMetaObjectPrototype);
ASSERT_CLASS_FITS_IN_CELL(QScript::QtFunction);
ASSERT_CLASS_FITS_IN_CELL(QScript::QtPropertyFunction);
}

QT_BEGIN_NAMESPACE

namespace QScript
{

struct QObjectConnection
{
    uint marked:1;
    uint slotIndex:31;
    JSC::JSValue receiver;
    JSC::JSValue slot;
    JSC::JSValue senderWrapper;

    QObjectConnection(int i, JSC::JSValue r, JSC::JSValue s,
                      JSC::JSValue sw)
        : marked(false), slotIndex(i), receiver(r), slot(s), senderWrapper(sw) {}
    QObjectConnection() : marked(false), slotIndex(0) {}

    bool hasTarget(JSC::JSValue r, JSC::JSValue s) const
    {
        if ((r && r.isObject()) != (receiver && receiver.isObject()))
            return false;
        if (((r && r.isObject()) && (receiver && receiver.isObject()))
            && (r != receiver)) {
            return false;
        }
        return (s == slot);
    }

    bool hasWeaklyReferencedSender() const
    {
        if (senderWrapper) {
            Q_ASSERT(senderWrapper.inherits(&QScriptObject::info));
            QScriptObject *scriptObject = static_cast<QScriptObject*>(JSC::asObject(senderWrapper));
            if (!JSC::Heap::isCellMarked(scriptObject)) {
                QScriptObjectDelegate *delegate = scriptObject->delegate();
                Q_ASSERT(delegate && (delegate->type() == QScriptObjectDelegate::QtObject));
                QObjectDelegate *inst = static_cast<QObjectDelegate*>(delegate);
                if ((inst->ownership() == QScriptEngine::ScriptOwnership)
                    || ((inst->ownership() == QScriptEngine::AutoOwnership)
                        && !inst->hasParent())) {
                    return true;
                }
            }
        }
        return false;
    }

    void mark(JSC::MarkStack& markStack)
    {
        Q_ASSERT(!marked);
        if (senderWrapper)
            markStack.append(senderWrapper);
        if (receiver)
            markStack.append(receiver);
        if (slot)
            markStack.append(slot);
        marked = true;
    }
};

class QObjectConnectionManager: public QObject
{
    Q_OBJECT_FAKE
public:
    QObjectConnectionManager(QScriptEnginePrivate *engine);
    ~QObjectConnectionManager();

    bool addSignalHandler(QObject *sender, int signalIndex,
                          JSC::JSValue receiver,
                          JSC::JSValue slot,
                          JSC::JSValue senderWrapper,
                          Qt::ConnectionType type);
    bool removeSignalHandler(QObject *sender, int signalIndex,
                             JSC::JSValue receiver,
                             JSC::JSValue slot);

    void execute(int slotIndex, void **argv);

    void clearMarkBits();
    int mark(JSC::MarkStack&);

private:
    QScriptEnginePrivate *engine;
    int slotCounter;
    QVector<QVector<QObjectConnection> > connections;
};

static bool hasMethodAccess(const QMetaMethod &method, int index, const QScriptEngine::QObjectWrapOptions &opt)
{
    static const int deleteLaterIndex = QObject::staticMetaObject.indexOfMethod("deleteLater()");
    return (method.access() != QMetaMethod::Private)
        && ((index != deleteLaterIndex) || !(opt & QScriptEngine::ExcludeDeleteLater))
        && (!(opt & QScriptEngine::ExcludeSlots) || (method.methodType() != QMetaMethod::Slot));
}

static bool isEnumerableMetaProperty(const QMetaProperty &prop,
                                     const QMetaObject *mo, int index)
{
    return prop.isScriptable() && prop.isValid()
        // the following lookup is to ensure that we have the
        // "most derived" occurrence of the property with this name
        && (mo->indexOfProperty(prop.name()) == index);
}

static const bool GeneratePropertyFunctions = true;

static unsigned flagsForMetaProperty(const QMetaProperty &prop)
{
    return (JSC::DontDelete
            | (!prop.isWritable() ? unsigned(JSC::ReadOnly) : unsigned(0))
            | (GeneratePropertyFunctions
               ? unsigned(JSC::Getter | JSC::Setter)
                  : unsigned(0))
            | QObjectMemberAttribute);
}

static int indexOfMetaEnum(const QMetaObject *meta, const QByteArray &str)
{
    QByteArray scope;
    QByteArray name;
    int scopeIdx = str.lastIndexOf("::");
    if (scopeIdx != -1) {
        scope = str.left(scopeIdx);
        name = str.mid(scopeIdx + 2);
    } else {
        name = str;
    }
    for (int i = meta->enumeratorCount() - 1; i >= 0; --i) {
        QMetaEnum m = meta->enumerator(i);
        if ((m.name() == name) && (scope.isEmpty() || (m.scope() == scope)))
            return i;
    }
    return -1;
}

static inline QScriptable *scriptableFromQObject(QObject *qobj)
{
    void *ptr = qobj->qt_metacast("QScriptable");
    return reinterpret_cast<QScriptable*>(ptr);
}

QtFunction::QtFunction(JSC::JSValue object, int initialIndex, bool maybeOverloaded,
                       JSC::JSGlobalData *data, WTF::PassRefPtr<JSC::Structure> sid,
                       const JSC::Identifier &ident)
    : JSC::InternalFunction(data, sid, ident),
      data(new Data(object, initialIndex, maybeOverloaded))
{
}

QtFunction::~QtFunction()
{
    delete data;
}

JSC::CallType QtFunction::getCallData(JSC::CallData &callData)
{
    callData.native.function = call;
    return JSC::CallTypeHost;
}

void QtFunction::markChildren(JSC::MarkStack& markStack)
{
    if (data->object)
        markStack.append(data->object);
    JSC::InternalFunction::markChildren(markStack);
}

QScriptObject *QtFunction::wrapperObject() const
{
    Q_ASSERT(JSC::asObject(data->object)->inherits(&QScriptObject::info));
    return static_cast<QScriptObject*>(JSC::asObject(data->object));
}

QObject *QtFunction::qobject() const
{
    QScriptObject *scriptObject = wrapperObject();
    QScriptObjectDelegate *delegate = scriptObject->delegate();
    Q_ASSERT(delegate && (delegate->type() == QScriptObjectDelegate::QtObject));
    return static_cast<QScript::QObjectDelegate*>(delegate)->value();
}

const QMetaObject *QtFunction::metaObject() const
{
    QObject *qobj = qobject();
    if (!qobj)
        return 0;
    return qobj->metaObject();
}

int QtFunction::initialIndex() const
{
    return data->initialIndex;
}

bool QtFunction::maybeOverloaded() const
{
    return data->maybeOverloaded;
}

int QtFunction::mostGeneralMethod(QMetaMethod *out) const
{
    const QMetaObject *meta = metaObject();
    if (!meta)
        return -1;
    int index = initialIndex();
    QMetaMethod method = meta->method(index);
    if (maybeOverloaded() && (method.attributes() & QMetaMethod::Cloned)) {
        // find the most general method
        do {
            method = meta->method(--index);
        } while (method.attributes() & QMetaMethod::Cloned);
    }
    if (out)
        *out = method;
    return index;
}

QList<int> QScript::QtFunction::overloadedIndexes() const
{
    if (!maybeOverloaded())
        return QList<int>();
    QList<int> result;
    const QMetaObject *meta = metaObject();
    QMetaMethod method = meta->method(initialIndex());
    QByteArray name = method.name();
    for (int index = mostGeneralMethod() - 1; index >= 0; --index) {
        if (meta->method(index).name() == name)
            result.append(index);
    }
    return result;
}

class QScriptMetaType
{
public:
    enum Kind {
        Invalid,
        Variant,
        MetaType,
        Unresolved,
        MetaEnum
    };

    inline QScriptMetaType()
        : m_kind(Invalid) { }

    inline Kind kind() const
    { return m_kind; }

    int typeId() const;

    inline bool isValid() const
    { return (m_kind != Invalid); }

    inline bool isVariant() const
    { return (m_kind == Variant); }

    inline bool isMetaType() const
    { return (m_kind == MetaType); }

    inline bool isUnresolved() const
    { return (m_kind == Unresolved); }

    inline bool isMetaEnum() const
    { return (m_kind == MetaEnum); }

    QByteArray name() const;

    inline int enumeratorIndex() const
    { Q_ASSERT(isMetaEnum()); return m_typeId; }

    inline bool operator==(const QScriptMetaType &other) const
    {
        return (m_kind == other.m_kind) && (m_typeId == other.m_typeId);
    }

    static inline QScriptMetaType variant()
    { return QScriptMetaType(Variant); }

    static inline QScriptMetaType metaType(int typeId, const QByteArray &name)
    { return QScriptMetaType(MetaType, typeId, name); }

    static inline QScriptMetaType metaEnum(int enumIndex, const QByteArray &name)
    { return QScriptMetaType(MetaEnum, enumIndex, name); }

    static inline QScriptMetaType unresolved(const QByteArray &name)
    { return QScriptMetaType(Unresolved, /*typeId=*/0, name); }

private:
    inline QScriptMetaType(Kind kind, int typeId = 0, const QByteArray &name = QByteArray())
        : m_kind(kind), m_typeId(typeId), m_name(name) { }

    Kind m_kind;
    int m_typeId;
    QByteArray m_name;
};

int QScriptMetaType::typeId() const
{
    if (isVariant())
        return QMetaType::QVariant;
    return isMetaEnum() ? QMetaType::Int : m_typeId;
}

QByteArray QScriptMetaType::name() const
{
    if (!m_name.isEmpty())
        return m_name;
    else if (m_kind == Variant)
        return "QVariant";
    return QMetaType::typeName(typeId());
}

class QScriptMetaMethod
{
public:
    inline QScriptMetaMethod()
        { }
    inline QScriptMetaMethod(const QVector<QScriptMetaType> &types)
        : m_types(types), m_firstUnresolvedIndex(-1)
    {
        QVector<QScriptMetaType>::const_iterator it;
        for (it = m_types.constBegin(); it != m_types.constEnd(); ++it) {
            if ((*it).kind() == QScriptMetaType::Unresolved) {
                m_firstUnresolvedIndex = it - m_types.constBegin();
                break;
            }
        }
    }
    inline bool isValid() const
    { return !m_types.isEmpty(); }

    inline QScriptMetaType returnType() const
    { return m_types.at(0); }

    inline int argumentCount() const
    { return m_types.count() - 1; }

    inline QScriptMetaType argumentType(int arg) const
    { return m_types.at(arg + 1); }

    inline bool fullyResolved() const
    { return m_firstUnresolvedIndex == -1; }

    inline bool hasUnresolvedReturnType() const
    { return (m_firstUnresolvedIndex == 0); }

    inline int firstUnresolvedIndex() const
    { return m_firstUnresolvedIndex; }

    inline int count() const
    { return m_types.count(); }

    inline QScriptMetaType type(int index) const
    { return m_types.at(index); }

    inline QVector<QScriptMetaType> types() const
    { return m_types; }

private:
    QVector<QScriptMetaType> m_types;
    int m_firstUnresolvedIndex;
};

struct QScriptMetaArguments
{
    int matchDistance;
    int index;
    QScriptMetaMethod method;
    QVarLengthArray<QVariant, 9> args;

    inline QScriptMetaArguments(int dist, int idx, const QScriptMetaMethod &mtd,
                                const QVarLengthArray<QVariant, 9> &as)
        : matchDistance(dist), index(idx), method(mtd), args(as) { }
    inline QScriptMetaArguments()
        : index(-1) { }

    inline bool isValid() const
    { return (index != -1); }
};

static QMetaMethod metaMethod(const QMetaObject *meta,
                              QMetaMethod::MethodType type,
                              int index)
{
    if (type != QMetaMethod::Constructor)
        return meta->method(index);
    else
        return meta->constructor(index);
}

/*!
  \internal
  Derives the actual method to call based on the script arguments,
  \a scriptArgs, and delegates it to the given \a delegate.
*/
template <class Delegate>
static JSC::JSValue delegateQtMethod(JSC::ExecState *exec, QMetaMethod::MethodType callType,
                                     const JSC::ArgList &scriptArgs, const QMetaObject *meta,
                                     int initialIndex, bool maybeOverloaded, Delegate &delegate)
{
    QScriptMetaMethod chosenMethod;
    int chosenIndex = -1;
    QVarLengthArray<QVariant, 9> args;
    QVector<QScriptMetaArguments> candidates;
    QVector<QScriptMetaArguments> unresolved;
    QVector<int> tooFewArgs;
    QVector<int> conversionFailed;
    int index;
    QByteArray methodName;
    exec->clearException();
    for (index = initialIndex; index >= 0; --index) {
        QMetaMethod method = metaMethod(meta, callType, index);

        if (index == initialIndex)
            methodName = method.name();
        else if (method.name() != methodName)
            continue;

        QList<QByteArray> parameterTypeNames = method.parameterTypes();

        QVector<QScriptMetaType> types;
        types.resize(1 + parameterTypeNames.size());
        QScriptMetaType *typesData = types.data();
        // resolve return type
        QByteArray returnTypeName = method.typeName();
        int rtype = method.returnType();
        if ((rtype == QMetaType::UnknownType) && !returnTypeName.isEmpty()) {
            int enumIndex = indexOfMetaEnum(meta, returnTypeName);
            if (enumIndex != -1)
                typesData[0] = QScriptMetaType::metaEnum(enumIndex, returnTypeName);
            else
                typesData[0] = QScriptMetaType::unresolved(returnTypeName);
        } else {
            if (callType == QMetaMethod::Constructor)
                typesData[0] = QScriptMetaType::metaType(QMetaType::QObjectStar, "QObject*");
            else if (rtype == QMetaType::QVariant)
                typesData[0] = QScriptMetaType::variant();
            else
                typesData[0] = QScriptMetaType::metaType(rtype, returnTypeName);
        }

        // resolve argument types
        for (int i = 0; i < parameterTypeNames.count(); ++i) {
            QByteArray argTypeName = parameterTypeNames.at(i);
            int atype = QMetaType::type(argTypeName);
            if (atype == QMetaType::UnknownType) {
                int enumIndex = indexOfMetaEnum(meta, argTypeName);
                if (enumIndex != -1)
                    typesData[1 + i] = QScriptMetaType::metaEnum(enumIndex, argTypeName);
                else
                    typesData[1 + i] = QScriptMetaType::unresolved(argTypeName);
            } else if (atype == QMetaType::QVariant) {
                typesData[1 + i] = QScriptMetaType::variant();
            } else {
                typesData[1 + i] = QScriptMetaType::metaType(atype, argTypeName);
            }
        }

        QScriptMetaMethod mtd = QScriptMetaMethod(types);

        if (int(scriptArgs.size()) < mtd.argumentCount()) {
            tooFewArgs.append(index);
            continue;
        }

        if (!mtd.fullyResolved()) {
            // remember it so we can give an error message later, if necessary
            unresolved.append(QScriptMetaArguments(/*matchDistance=*/INT_MAX, index,
                                                   mtd, QVarLengthArray<QVariant, 9>()));
            if (mtd.hasUnresolvedReturnType())
                continue;
        }

        if (args.count() != mtd.count())
            args.resize(mtd.count());

        if (rtype != QMetaType::Void) {
            // initialize the result
            args[0] = createQVariant(rtype, nullptr);
        }

        // try to convert arguments
        bool converted = true;
        int matchDistance = 0;
        for (int i = 0; converted && i < mtd.argumentCount(); ++i) {
            JSC::JSValue actual;
            if (i < (int)scriptArgs.size())
                actual = scriptArgs.at(i);
            else
                actual = JSC::jsUndefined();
            QScriptMetaType argType = mtd.argumentType(i);
            int tid = -1;
            QVariant v;
            if (argType.isUnresolved()) {
                v = createQVariant(QMetaType::QObjectStar, nullptr);
                converted = QScriptEnginePrivate::convertToNativeQObject(
                    exec, actual, argType.name(), reinterpret_cast<void* *>(v.data()));
            } else if (argType.isVariant()) {
                if (QScriptEnginePrivate::isVariant(actual)) {
                    v = QScriptEnginePrivate::variantValue(actual);
                } else {
                    v = QScriptEnginePrivate::toVariant(exec, actual);
                    converted = v.isValid() || actual.isUndefined() || actual.isNull();
                }
            } else {
                tid = argType.typeId();
                v = createQVariant(tid, nullptr);
                converted = QScriptEnginePrivate::convertValue(exec, actual, tid, v.data());
                if (exec->hadException())
                    return exec->exception();
            }

            if (!converted) {
                if (QScriptEnginePrivate::isVariant(actual)) {
                    if (tid == -1)
                        tid = argType.typeId();
                    QVariant vv = QScriptEnginePrivate::variantValue(actual);
                    if (vv.canConvert(tid)) {
                        v = vv;
                        converted = v.convert(tid);
                        if (converted && (vv.userType() != tid))
                            matchDistance += 10;
                    } else {
                        QByteArray vvTypeName = vv.typeName();
                        if (vvTypeName.endsWith('*')
                            && (vvTypeName.left(vvTypeName.size()-1) == argType.name())) {
                            v = createQVariant(tid, *reinterpret_cast<void* *>(vv.data()));
                            converted = true;
                            matchDistance += 10;
                        }
                    }
                } else if (actual.isNumber() || actual.isString()) {
                    // see if it's an enum value
                    QMetaEnum m;
                    if (argType.isMetaEnum()) {
                        m = meta->enumerator(argType.enumeratorIndex());
                    } else {
                        int mi = indexOfMetaEnum(meta, argType.name());
                        if (mi != -1)
                            m = meta->enumerator(mi);
                    }
                    if (m.isValid()) {
                        if (actual.isNumber()) {
                            int ival = QScriptEnginePrivate::toInt32(exec, actual);
                            if (m.valueToKey(ival) != 0) {
                                v.setValue(ival);
                                converted = true;
                                matchDistance += 10;
                            }
                        } else {
                            JSC::UString sval = QScriptEnginePrivate::toString(exec, actual);
                            int ival = m.keyToValue(convertToLatin1(sval));
                            if (ival != -1) {
                                v.setValue(ival);
                                converted = true;
                                matchDistance += 10;
                            }
                        }
                    }
                }
            } else {
                // determine how well the conversion matched
                if (actual.isNumber()) {
                    switch (tid) {
                    case QMetaType::Double:
                        // perfect
                        break;
                    case QMetaType::Float:
                        matchDistance += 1;
                        break;
                    case QMetaType::LongLong:
                    case QMetaType::ULongLong:
                        matchDistance += 2;
                        break;
                    case QMetaType::Long:
                    case QMetaType::ULong:
                        matchDistance += 3;
                        break;
                    case QMetaType::Int:
                    case QMetaType::UInt:
                        matchDistance += 4;
                        break;
                    case QMetaType::Short:
                    case QMetaType::UShort:
                        matchDistance += 5;
                        break;
                    case QMetaType::Char:
                    case QMetaType::UChar:
                        matchDistance += 6;
                        break;
                    default:
                        matchDistance += 10;
                        break;
                    }
                } else if (actual.isString()) {
                    switch (tid) {
                    case QMetaType::QString:
                        // perfect
                        break;
                    default:
                        matchDistance += 10;
                        break;
                    }
                } else if (actual.isBoolean()) {
                    switch (tid) {
                    case QMetaType::Bool:
                        // perfect
                        break;
                    default:
                        matchDistance += 10;
                        break;
                    }
                } else if (QScriptEnginePrivate::isDate(actual)) {
                    switch (tid) {
                    case QMetaType::QDateTime:
                        // perfect
                        break;
                    case QMetaType::QDate:
                        matchDistance += 1;
                        break;
                    case QMetaType::QTime:
                        matchDistance += 2;
                        break;
                    default:
                        matchDistance += 10;
                        break;
                    }
                } else if (QScriptEnginePrivate::isRegExp(actual)) {
                    if (tid == qMetaTypeId<QRegExp>()) {
                        // perfect
                    } else {
                        matchDistance += 10;
                    }
                } else if (QScriptEnginePrivate::isVariant(actual)) {
                    if (argType.isVariant()
                        || (QScriptEnginePrivate::toVariant(exec, actual).userType() == tid)) {
                        // perfect
                    } else {
                        matchDistance += 10;
                    }
                } else if (QScriptEnginePrivate::isArray(actual)) {
                    switch (tid) {
                    case QMetaType::QStringList:
                    case QMetaType::QVariantList:
                        matchDistance += 5;
                        break;
                    default:
                        matchDistance += 10;
                        break;
                    }
                } else if (QScriptEnginePrivate::isQObject(actual)) {
                    switch (tid) {
                    case QMetaType::QObjectStar:
                        // perfect
                        break;
                    default:
                        if (!(QMetaType::typeFlags(tid) & QMetaType::PointerToQObject))
                            matchDistance += 10;
                        break;
                    }
                } else if (actual.isNull()) {
                    switch (tid) {
                    case QMetaType::VoidStar:
                    case QMetaType::QObjectStar:
                        // perfect
                        break;
                    default:
                        if (!argType.name().endsWith('*'))
                            matchDistance += 10;
                        break;
                    }
                } else {
                    matchDistance += 10;
                }
            }

            if (converted)
                args[i+1] = v;
        }

        if (converted) {
            if ((scriptArgs.size() == (size_t)mtd.argumentCount())
                && (matchDistance == 0)) {
                // perfect match, use this one
                chosenMethod = mtd;
                chosenIndex = index;
                break;
            } else {
                bool redundant = false;
                if ((callType != QMetaMethod::Constructor)
                    && (index < meta->methodOffset())) {
                    // it is possible that a virtual method is redeclared in a subclass,
                    // in which case we want to ignore the superclass declaration
                    for (int i = 0; i < candidates.size(); ++i) {
                        const QScriptMetaArguments &other = candidates.at(i);
                        if (mtd.types() == other.method.types()) {
                            redundant = true;
                            break;
                        }
                    }
                }
                if (!redundant) {
                    QScriptMetaArguments metaArgs(matchDistance, index, mtd, args);
                    if (candidates.isEmpty()) {
                        candidates.append(metaArgs);
                    } else {
                        const QScriptMetaArguments &otherArgs = candidates.at(0);
                        if ((args.count() > otherArgs.args.count())
                            || ((args.count() == otherArgs.args.count())
                                && (matchDistance <= otherArgs.matchDistance))) {
                            candidates.prepend(metaArgs);
                        } else {
                            candidates.append(metaArgs);
                        }
                    }
                }
            }
        } else if (mtd.fullyResolved()) {
            conversionFailed.append(index);
        }

        if (!maybeOverloaded)
            break;
    }

    JSC::JSValue result;
    if ((chosenIndex == -1) && candidates.isEmpty()) {
//        context->calleeMetaIndex = initialIndex;
//#ifndef Q_SCRIPT_NO_EVENT_NOTIFY
//        engine->notifyFunctionEntry(context);
//#endif
        QString funName = QString::fromLatin1(methodName);
        if (!conversionFailed.isEmpty()) {
            QString message = QString::fromLatin1("incompatible type of argument(s) in call to %0(); candidates were\n")
                              .arg(funName);
            for (int i = 0; i < conversionFailed.size(); ++i) {
                if (i > 0)
                    message += QLatin1String("\n");
                QMetaMethod mtd = metaMethod(meta, callType, conversionFailed.at(i));
                message += QString::fromLatin1("    %0").arg(QString::fromLatin1(mtd.methodSignature().constData()));
            }
            result = JSC::throwError(exec, JSC::TypeError, message);
        } else if (!unresolved.isEmpty()) {
            QScriptMetaArguments argsInstance = unresolved.first();
            int unresolvedIndex = argsInstance.method.firstUnresolvedIndex();
            Q_ASSERT(unresolvedIndex != -1);
            QScriptMetaType unresolvedType = argsInstance.method.type(unresolvedIndex);
            QString unresolvedTypeName = QString::fromLatin1(unresolvedType.name());
            QString message = QString::fromLatin1("cannot call %0(): ")
                              .arg(funName);
            if (unresolvedIndex > 0) {
                message.append(QString::fromLatin1("argument %0 has unknown type `%1'").
                               arg(unresolvedIndex).arg(unresolvedTypeName));
            } else {
                message.append(QString::fromLatin1("unknown return type `%0'")
                               .arg(unresolvedTypeName));
            }
            message.append(QString::fromLatin1(" (register the type with qScriptRegisterMetaType())"));
            result = JSC::throwError(exec, JSC::TypeError, message);
        } else {
            QString message = QString::fromLatin1("too few arguments in call to %0(); candidates are\n")
                              .arg(funName);
            for (int i = 0; i < tooFewArgs.size(); ++i) {
                if (i > 0)
                    message += QLatin1String("\n");
                QMetaMethod mtd = metaMethod(meta, callType, tooFewArgs.at(i));
                message += QString::fromLatin1("    %0").arg(QString::fromLatin1(mtd.methodSignature().constData()));
            }
            result = JSC::throwError(exec, JSC::SyntaxError, message);
        }
    } else {
        if (chosenIndex == -1) {
            QScriptMetaArguments metaArgs = candidates.at(0);
            if ((candidates.size() > 1)
                && (metaArgs.args.count() == candidates.at(1).args.count())
                && (metaArgs.matchDistance == candidates.at(1).matchDistance)) {
                // ambiguous call
                QString message = QString::fromLatin1("ambiguous call of overloaded function %0(); candidates were\n")
                                  .arg(QLatin1String(methodName));
                for (int i = 0; i < candidates.size(); ++i) {
                    if (i > 0)
                        message += QLatin1String("\n");
                    QMetaMethod mtd = metaMethod(meta, callType, candidates.at(i).index);
                    message += QString::fromLatin1("    %0").arg(QString::fromLatin1(mtd.methodSignature().constData()));
                }
                result = JSC::throwError(exec, JSC::TypeError, message);
            } else {
                chosenMethod = metaArgs.method;
                chosenIndex = metaArgs.index;
                args = metaArgs.args;
            }
        }

        if (chosenIndex != -1)
            result = delegate(exec, callType, meta, chosenMethod, chosenIndex, args);
    }

    return result;
}

struct QtMethodCaller
{
    QtMethodCaller(QObject *o)
        : thisQObject(o)
    {}
    JSC::JSValue operator()(JSC::ExecState *exec, QMetaMethod::MethodType callType,
                            const QMetaObject *meta, const QScriptMetaMethod &chosenMethod,
                            int chosenIndex, const QVarLengthArray<QVariant, 9> &args)
    {
        JSC::JSValue result;

        QVarLengthArray<void*, 9> array(args.count());
        void **params = array.data();
        for (int i = 0; i < args.count(); ++i) {
            const QVariant &v = args[i];
            switch (chosenMethod.type(i).kind()) {
            case QScriptMetaType::Variant:
                params[i] = const_cast<QVariant*>(&v);
                break;
            case QScriptMetaType::MetaType:
            case QScriptMetaType::MetaEnum:
            case QScriptMetaType::Unresolved:
                params[i] = const_cast<void*>(v.constData());
                break;
            default:
                Q_ASSERT(0);
            }
        }

        QScriptable *scriptable = 0;
        if (thisQObject)
            scriptable = scriptableFromQObject(thisQObject);
        QScriptEngine *oldEngine = 0;
        QScriptEnginePrivate *engine = QScript::scriptEngineFromExec(exec);
        if (scriptable) {
            oldEngine = QScriptablePrivate::get(scriptable)->engine;
            QScriptablePrivate::get(scriptable)->engine = QScriptEnginePrivate::get(engine);
        }

    // ### fixme
    //#ifndef Q_SCRIPT_NO_EVENT_NOTIFY
    //            engine->notifyFunctionEntry(context);
    //#endif

        if (callType == QMetaMethod::Constructor) {
            Q_ASSERT(meta != 0);
            meta->static_metacall(QMetaObject::CreateInstance, chosenIndex, params);
        } else {
            QMetaObject::metacall(thisQObject, QMetaObject::InvokeMetaMethod, chosenIndex, params);
        }

        if (scriptable)
            QScriptablePrivate::get(scriptable)->engine = oldEngine;

        if (exec->hadException()) {
            result = exec->exception() ; // propagate
        } else {
            QScriptMetaType retType = chosenMethod.returnType();
            if (retType.isVariant()) {
                result = QScriptEnginePrivate::jscValueFromVariant(exec, *(QVariant *)params[0]);
            } else if (retType.typeId() != QMetaType::Void) {
                result = QScriptEnginePrivate::create(exec, retType.typeId(), params[0]);
                if (!result)
                    result = engine->newVariant(createQVariant((retType.typeId()), params[0]));
            } else {
                result = JSC::jsUndefined();
            }
        }

        return result;
    }

private:
    QObject *thisQObject;
};

static JSC::JSValue callQtMethod(JSC::ExecState *exec, QMetaMethod::MethodType callType,
                                 QObject *thisQObject, const JSC::ArgList &scriptArgs,
                                 const QMetaObject *meta, int initialIndex,
                                 bool maybeOverloaded)
{
    QtMethodCaller caller(thisQObject);
    return delegateQtMethod<QtMethodCaller>(exec, callType, scriptArgs, meta,
                                            initialIndex, maybeOverloaded, caller);
}

JSC::JSValue QtFunction::execute(JSC::ExecState *exec, JSC::JSValue thisValue,
                                 const JSC::ArgList &scriptArgs)
{
    Q_ASSERT(data->object.inherits(&QScriptObject::info));
    QScriptObject *scriptObject = static_cast<QScriptObject*>(JSC::asObject(data->object));
    QScriptObjectDelegate *delegate = scriptObject->delegate();
    Q_ASSERT(delegate && (delegate->type() == QScriptObjectDelegate::QtObject));
    QObject *qobj = static_cast<QScript::QObjectDelegate*>(delegate)->value();
    if (!qobj)
        return JSC::throwError(exec, JSC::GeneralError, QString::fromLatin1("cannot call function of deleted QObject"));
    QScriptEnginePrivate *engine = scriptEngineFromExec(exec);

    const QMetaObject *meta = qobj->metaObject();
    QObject *thisQObject = 0;
    thisValue = engine->toUsableValue(thisValue);
    if (thisValue.inherits(&QScriptObject::info)) {
        delegate = static_cast<QScriptObject*>(JSC::asObject(thisValue))->delegate();
        if (delegate && (delegate->type() == QScriptObjectDelegate::QtObject))
            thisQObject = static_cast<QScript::QObjectDelegate*>(delegate)->value();
    }
    if (!thisQObject)
        thisQObject = qobj; // ### TypeError

    if (!meta->cast(thisQObject)) {
        // invoking a function in the prototype
        thisQObject = qobj;
    }

    return callQtMethod(exec, QMetaMethod::Method, thisQObject, scriptArgs,
                        meta, data->initialIndex, data->maybeOverloaded);
}

const JSC::ClassInfo QtFunction::info = { "QtFunction", &InternalFunction::info, 0, 0 };

JSC::JSValue JSC_HOST_CALL QtFunction::call(JSC::ExecState *exec, JSC::JSObject *callee,
                                            JSC::JSValue thisValue, const JSC::ArgList &args)
{
    if (!callee->inherits(&QtFunction::info))
        return throwError(exec, JSC::TypeError, "callee is not a QtFunction object");
    QtFunction *qfun =  static_cast<QtFunction*>(callee);
    QScriptEnginePrivate *eng_p = scriptEngineFromExec(exec);
    JSC::ExecState *previousFrame = eng_p->currentFrame;
    eng_p->currentFrame = exec;
    eng_p->pushContext(exec, thisValue, args, callee);
    JSC::JSValue result = qfun->execute(eng_p->currentFrame, thisValue, args);
    eng_p->popContext();
    eng_p->currentFrame = previousFrame;
    return result;
}

struct QtMethodIndexReturner
{
    JSC::JSValue operator()(JSC::ExecState *exec, QMetaMethod::MethodType,
                            const QMetaObject *, const QScriptMetaMethod &,
                            int chosenIndex, const QVarLengthArray<QVariant, 9> &)
    {
        return JSC::jsNumber(exec, chosenIndex);
    }
};

/*!
  \internal
  Returns the specific index of the meta-method that was used in the
  function call represented by the given \a context. If the method is
  overloaded, the actual parameters that were passed to the function
  are used to derive the selected index, matching the behavior of
  callQtMethod().
*/
int QtFunction::specificIndex(const QScriptContext *context) const
{
    if (!maybeOverloaded())
        return initialIndex();
    JSC::ExecState *exec = const_cast<JSC::ExecState *>(QScriptEnginePrivate::frameForContext(context));
    int argCount = exec->argumentCount();

    // Create arguments list wrapper; this logic must match
    // JITStubs.cpp op_call_NotJSFunction, and Interpreter.cpp op_call
    JSC::Register* argv = exec->registers() - JSC::RegisterFile::CallFrameHeaderSize - argCount;
    JSC::ArgList args(argv + 1, argCount - 1);

    QtMethodIndexReturner returner;
    JSC::JSValue result =  delegateQtMethod<QtMethodIndexReturner>(
                exec, QMetaMethod::Method, args, metaObject(),
                initialIndex(), maybeOverloaded(), returner);
    if (exec->hadException() || !result || !result.isInt32())
        return initialIndex();
    return result.asInt32();
}

const JSC::ClassInfo QtPropertyFunction::info = { "QtPropertyFunction", &InternalFunction::info, 0, 0 };

QtPropertyFunction::QtPropertyFunction(const QMetaObject *meta, int index,
                                       JSC::JSGlobalData *data,
                                       WTF::PassRefPtr<JSC::Structure> sid,
                                       const JSC::Identifier &ident)
    : JSC::InternalFunction(data, sid, ident),
      data(new Data(meta, index))
{
}

QtPropertyFunction::~QtPropertyFunction()
{
    delete data;
}

JSC::CallType QtPropertyFunction::getCallData(JSC::CallData &callData)
{
    callData.native.function = call;
    return JSC::CallTypeHost;
}

JSC::JSValue JSC_HOST_CALL QtPropertyFunction::call(
    JSC::ExecState *exec, JSC::JSObject *callee,
    JSC::JSValue thisValue, const JSC::ArgList &args)
{
    if (!callee->inherits(&QtPropertyFunction::info))
        return throwError(exec, JSC::TypeError, "callee is not a QtPropertyFunction object");
    QtPropertyFunction *qfun =  static_cast<QtPropertyFunction*>(callee);
    return qfun->execute(exec, thisValue, args);
}

JSC::JSValue QtPropertyFunction::execute(JSC::ExecState *exec,
                                         JSC::JSValue thisValue,
                                         const JSC::ArgList &args)
{
    JSC::JSValue result = JSC::jsUndefined();

    QScriptEnginePrivate *engine = scriptEngineFromExec(exec);
    JSC::ExecState *previousFrame = engine->currentFrame;
    engine->currentFrame = exec;

    JSC::JSValue qobjectValue = engine->toUsableValue(thisValue);
    QObject *qobject = QScriptEnginePrivate::toQObject(exec, qobjectValue);
    while ((!qobject || (qobject->metaObject() != data->meta))
        && JSC::asObject(qobjectValue)->prototype().isObject()) {
        qobjectValue = JSC::asObject(qobjectValue)->prototype();
        qobject = QScriptEnginePrivate::toQObject(exec, qobjectValue);
    }
    Q_ASSERT_X(qobject, Q_FUNC_INFO, "this-object must be a QObject");

    QMetaProperty prop = data->meta->property(data->index);
    Q_ASSERT(prop.isScriptable());
    if (args.size() == 0) {
        // get
        if (prop.isValid()) {
            QScriptable *scriptable = scriptableFromQObject(qobject);
            QScriptEngine *oldEngine = 0;
            if (scriptable) {
                engine->pushContext(exec, thisValue, args, this);
                oldEngine = QScriptablePrivate::get(scriptable)->engine;
                QScriptablePrivate::get(scriptable)->engine = QScriptEnginePrivate::get(engine);
            }

            QVariant v = prop.read(qobject);

            if (scriptable) {
                QScriptablePrivate::get(scriptable)->engine = oldEngine;
                engine->popContext();
            }

            result = QScriptEnginePrivate::jscValueFromVariant(exec, v);
        }
    } else {
        // set
        JSC::JSValue arg = args.at(0);
        QVariant v;
        if (prop.isEnumType() && arg.isString()
            && !engine->hasDemarshalFunction(prop.userType())) {
            // give QMetaProperty::write() a chance to convert from
            // string to enum value
            v = (QString)arg.toString(exec);
        } else {
            v = QScriptEnginePrivate::jscValueToVariant(exec, arg, prop.userType());
        }

        QScriptable *scriptable = scriptableFromQObject(qobject);
        QScriptEngine *oldEngine = 0;
        if (scriptable) {
            engine->pushContext(exec, thisValue, args, this);
            oldEngine = QScriptablePrivate::get(scriptable)->engine;
            QScriptablePrivate::get(scriptable)->engine = QScriptEnginePrivate::get(engine);
        }

        prop.write(qobject, v);

        if (scriptable) {
            QScriptablePrivate::get(scriptable)->engine = oldEngine;
            engine->popContext();
        }

        result = arg;
    }
    engine->currentFrame = previousFrame;
    return result;
}

const QMetaObject *QtPropertyFunction::metaObject() const
{
    return data->meta;
}

int QtPropertyFunction::propertyIndex() const
{
    return data->index;
}


QObjectDelegate::QObjectDelegate(
    QObject *object, QScriptEngine::ValueOwnership ownership,
    const QScriptEngine::QObjectWrapOptions &options)
    : data(new Data(object, ownership, options))
{
}

QObjectDelegate::~QObjectDelegate()
{
    switch (data->ownership) {
    case QScriptEngine::QtOwnership:
        break;
    case QScriptEngine::ScriptOwnership:
        if (data->value)
            delete data->value; // ### fixme
//            eng->disposeQObject(value);
        break;
    case QScriptEngine::AutoOwnership:
        if (data->value && !data->value->parent())
            delete data->value; // ### fixme
//            eng->disposeQObject(value);
        break;
    }
    delete data;
}

QScriptObjectDelegate::Type QObjectDelegate::type() const
{
    return QtObject;
}

bool QObjectDelegate::getOwnPropertySlot(QScriptObject *object, JSC::ExecState *exec,
                                         const JSC::Identifier &propertyName,
                                         JSC::PropertySlot &slot)
{
    //Note: this has to be kept in sync with getOwnPropertyDescriptor
#ifndef QT_NO_PROPERTIES
    QByteArray name = convertToLatin1(propertyName.ustring());
    QObject *qobject = data->value;
    if (!qobject) {
        QString message = QString::fromLatin1("cannot access member `%0' of deleted QObject")
                          .arg(QString::fromLatin1(name));
        slot.setValue(JSC::throwError(exec, JSC::GeneralError, message));
        return true;
    }

    const QMetaObject *meta = qobject->metaObject();
    {
        QHash<QByteArray, JSC::JSValue>::const_iterator it = data->cachedMembers.constFind(name);
        if (it != data->cachedMembers.constEnd()) {
            if (GeneratePropertyFunctions && (meta->indexOfProperty(name) != -1))
                slot.setGetterSlot(JSC::asObject(it.value()));
            else
                slot.setValue(it.value());
            return true;
        }
    }

    const QScriptEngine::QObjectWrapOptions &opt = data->options;
    QScriptEnginePrivate *eng = scriptEngineFromExec(exec);
    int index = -1;
    if (name.contains('(')) {
        QByteArray normalized = QMetaObject::normalizedSignature(name);
        if (-1 != (index = meta->indexOfMethod(normalized))) {
            QMetaMethod method = meta->method(index);
            if (hasMethodAccess(method, index, opt)) {
                if (!(opt & QScriptEngine::ExcludeSuperClassMethods)
                    || (index >= meta->methodOffset())) {
                    QtFunction *fun = new (exec)QtFunction(
                        object, index, /*maybeOverloaded=*/false,
                        &exec->globalData(), eng->originalGlobalObject()->functionStructure(),
                        propertyName);
                    slot.setValue(fun);
                    data->cachedMembers.insert(name, fun);
                    return true;
                }
            }
        }
    }

    index = meta->indexOfProperty(name);
    if (index != -1) {
        QMetaProperty prop = meta->property(index);
        if (prop.isScriptable()) {
            if (!(opt & QScriptEngine::ExcludeSuperClassProperties)
                || (index >= meta->propertyOffset())) {
                if (GeneratePropertyFunctions) {
                    QtPropertyFunction *fun = new (exec)QtPropertyFunction(
                        meta, index, &exec->globalData(),
                        eng->originalGlobalObject()->functionStructure(),
                        propertyName);
                    data->cachedMembers.insert(name, fun);
                    slot.setGetterSlot(fun);
                } else {
                    JSC::JSValue val;
                    if (!prop.isValid())
                        val = JSC::jsUndefined();
                    else
                        val = QScriptEnginePrivate::jscValueFromVariant(exec, prop.read(qobject));
                    slot.setValue(val);
                }
                return true;
            }
        }
    }

    index = qobject->dynamicPropertyNames().indexOf(name);
    if (index != -1) {
        JSC::JSValue val = QScriptEnginePrivate::jscValueFromVariant(exec, qobject->property(name));
        slot.setValue(val);
        return true;
    }

    const int offset = (opt & QScriptEngine::ExcludeSuperClassMethods)
                       ? meta->methodOffset() : 0;
    for (index = meta->methodCount() - 1; index >= offset; --index) {
        QMetaMethod method = meta->method(index);
        if (hasMethodAccess(method, index, opt) && (method.name() == name)) {
            QtFunction *fun = new (exec)QtFunction(
                object, index, /*maybeOverloaded=*/true,
                &exec->globalData(), eng->originalGlobalObject()->functionStructure(),
                propertyName);
            slot.setValue(fun);
            data->cachedMembers.insert(name, fun);
            return true;
        }
    }

    if (!(opt & QScriptEngine::ExcludeChildObjects)) {
        QList<QObject*> children = qobject->children();
        for (index = 0; index < children.count(); ++index) {
            QObject *child = children.at(index);
            if (child->objectName() == QString(propertyName.ustring())) {
                QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject;
                slot.setValue(eng->newQObject(child, QScriptEngine::QtOwnership, opt));
                return true;
            }
        }
    }

    return QScriptObjectDelegate::getOwnPropertySlot(object, exec, propertyName, slot);
#else //QT_NO_PROPERTIES
    return false;
#endif //QT_NO_PROPERTIES
}


bool QObjectDelegate::getOwnPropertyDescriptor(QScriptObject *object, JSC::ExecState *exec,
                                         const JSC::Identifier &propertyName,
                                         JSC::PropertyDescriptor &descriptor)
{
    //Note: this has to be kept in sync with getOwnPropertySlot
#ifndef QT_NO_PROPERTIES
    QByteArray name = convertToLatin1(propertyName.ustring());
    QObject *qobject = data->value;
    if (!qobject) {
        QString message = QString::fromLatin1("cannot access member `%0' of deleted QObject")
                          .arg(QString::fromLatin1(name));
        descriptor.setValue(JSC::throwError(exec, JSC::GeneralError, message));
        return true;
    }

    const QScriptEngine::QObjectWrapOptions &opt = data->options;

    const QMetaObject *meta = qobject->metaObject();
    {
        QHash<QByteArray, JSC::JSValue>::const_iterator it = data->cachedMembers.constFind(name);
        if (it != data->cachedMembers.constEnd()) {
            int index;
            if (GeneratePropertyFunctions && ((index = meta->indexOfProperty(name)) != -1)) {
                QMetaProperty prop = meta->property(index);
                descriptor.setAccessorDescriptor(it.value(), it.value(), flagsForMetaProperty(prop));
                if (!prop.isWritable())
                    descriptor.setWritable(false);
            } else {
                unsigned attributes = QObjectMemberAttribute;
                if (opt & QScriptEngine::SkipMethodsInEnumeration)
                    attributes |= JSC::DontEnum;
                descriptor.setDescriptor(it.value(), attributes);
            }
            return true;
        }
    }

    QScriptEnginePrivate *eng = scriptEngineFromExec(exec);
    int index = -1;
    if (name.contains('(')) {
        QByteArray normalized = QMetaObject::normalizedSignature(name);
        if (-1 != (index = meta->indexOfMethod(normalized))) {
            QMetaMethod method = meta->method(index);
            if (hasMethodAccess(method, index, opt)) {
                if (!(opt & QScriptEngine::ExcludeSuperClassMethods)
                    || (index >= meta->methodOffset())) {
                    QtFunction *fun = new (exec)QtFunction(
                        object, index, /*maybeOverloaded=*/false,
                        &exec->globalData(), eng->originalGlobalObject()->functionStructure(),
                        propertyName);
                    data->cachedMembers.insert(name, fun);
                    unsigned attributes = QObjectMemberAttribute;
                    if (opt & QScriptEngine::SkipMethodsInEnumeration)
                        attributes |= JSC::DontEnum;
                    descriptor.setDescriptor(fun, attributes);
                    return true;
                }
            }
        }
    }

    index = meta->indexOfProperty(name);
    if (index != -1) {
        QMetaProperty prop = meta->property(index);
        if (prop.isScriptable()) {
            if (!(opt & QScriptEngine::ExcludeSuperClassProperties)
                || (index >= meta->propertyOffset())) {
                unsigned attributes = flagsForMetaProperty(prop);
                if (GeneratePropertyFunctions) {
                    QtPropertyFunction *fun = new (exec)QtPropertyFunction(
                        meta, index, &exec->globalData(),
                        eng->originalGlobalObject()->functionStructure(),
                        propertyName);
                    data->cachedMembers.insert(name, fun);
                    descriptor.setAccessorDescriptor(fun, fun, attributes);
                    if (attributes & JSC::ReadOnly)
                        descriptor.setWritable(false);
                } else {
                    JSC::JSValue val;
                    if (!prop.isValid())
                        val = JSC::jsUndefined();
                    else
                        val = QScriptEnginePrivate::jscValueFromVariant(exec, prop.read(qobject));
                    descriptor.setDescriptor(val, attributes);
                }
                return true;
            }
        }
    }

    index = qobject->dynamicPropertyNames().indexOf(name);
    if (index != -1) {
        JSC::JSValue val = QScriptEnginePrivate::jscValueFromVariant(exec, qobject->property(name));
        descriptor.setDescriptor(val, QObjectMemberAttribute);
        return true;
    }

    const int offset = (opt & QScriptEngine::ExcludeSuperClassMethods)
                       ? meta->methodOffset() : 0;
    for (index = meta->methodCount() - 1; index >= offset; --index) {
        QMetaMethod method = meta->method(index);
        if (hasMethodAccess(method, index, opt) && (method.name() == name)) {
            QtFunction *fun = new (exec)QtFunction(
                object, index, /*maybeOverloaded=*/true,
                &exec->globalData(), eng->originalGlobalObject()->functionStructure(),
                propertyName);
            unsigned attributes = QObjectMemberAttribute;
            if (opt & QScriptEngine::SkipMethodsInEnumeration)
                attributes |= JSC::DontEnum;
            descriptor.setDescriptor(fun, attributes);
            data->cachedMembers.insert(name, fun);
            return true;
        }
    }

    if (!(opt & QScriptEngine::ExcludeChildObjects)) {
        QList<QObject*> children = qobject->children();
        for (index = 0; index < children.count(); ++index) {
            QObject *child = children.at(index);
            if (child->objectName() == QString(propertyName.ustring())) {
                QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject;
                descriptor.setDescriptor(eng->newQObject(child, QScriptEngine::QtOwnership, opt),
                                         JSC::ReadOnly | JSC::DontDelete | JSC::DontEnum);
                return true;
            }
        }
    }

    return QScriptObjectDelegate::getOwnPropertyDescriptor(object, exec, propertyName, descriptor);
#else //QT_NO_PROPERTIES
    return false;
#endif //QT_NO_PROPERTIES
}

void QObjectDelegate::put(QScriptObject *object, JSC::ExecState* exec,
                          const JSC::Identifier& propertyName,
                          JSC::JSValue value, JSC::PutPropertySlot &slot)
{
#ifndef QT_NO_PROPERTIES
    QByteArray name = convertToLatin1(propertyName.ustring());
    QObject *qobject = data->value;
    if (!qobject) {
        QString message = QString::fromLatin1("cannot access member `%0' of deleted QObject")
                          .arg(QString::fromLatin1(name));
        JSC::throwError(exec, JSC::GeneralError, message);
        return;
    }

    const QScriptEngine::QObjectWrapOptions &opt = data->options;
    const QMetaObject *meta = qobject->metaObject();
    QScriptEnginePrivate *eng = scriptEngineFromExec(exec);
    int index = -1;
    if (name.contains('(')) {
        QByteArray normalized = QMetaObject::normalizedSignature(name);
        if (-1 != (index = meta->indexOfMethod(normalized))) {
            QMetaMethod method = meta->method(index);
            if (hasMethodAccess(method, index, opt)) {
                if (!(opt & QScriptEngine::ExcludeSuperClassMethods)
                    || (index >= meta->methodOffset())) {
                    data->cachedMembers.insert(name, value);
                    return;
                }
            }
        }
    }

    index = meta->indexOfProperty(name);
    if (index != -1) {
        QMetaProperty prop = meta->property(index);
        if (prop.isScriptable()) {
            if (!(opt & QScriptEngine::ExcludeSuperClassProperties)
                || (index >= meta->propertyOffset())) {
                if (GeneratePropertyFunctions) {
                    // ### ideally JSC would do this for us already, i.e. find out
                    // that the property is a setter and call the setter.
                    // Maybe QtPropertyFunction needs to inherit JSC::GetterSetter.
                    JSC::JSValue fun;
                    QHash<QByteArray, JSC::JSValue>::const_iterator it;
                    it = data->cachedMembers.constFind(name);
                    if (it != data->cachedMembers.constEnd()) {
                        fun = it.value();
                    } else {
                        fun = new (exec)QtPropertyFunction(
                            meta, index, &exec->globalData(),
                            eng->originalGlobalObject()->functionStructure(),
                            propertyName);
                        data->cachedMembers.insert(name, fun);
                    }
                    JSC::CallData callData;
                    JSC::CallType callType = fun.getCallData(callData);
                    JSC::JSValue argv[1] = { value };
                    JSC::ArgList args(argv, 1);
                    (void)JSC::call(exec, fun, callType, callData, object, args);
                } else {
                    QVariant v;
                    if (prop.isEnumType() && value.isString()
                        && !eng->hasDemarshalFunction(prop.userType())) {
                        // give QMetaProperty::write() a chance to convert from
                        // string to enum value
                        v = (QString)value.toString(exec);
                    } else {
                        v = QScriptEnginePrivate::jscValueToVariant(exec, value, prop.userType());
                    }
                    (void)prop.write(qobject, v);
                }
                return;
            }
        }
    }

    const int offset = (opt & QScriptEngine::ExcludeSuperClassMethods)
                       ? meta->methodOffset() : 0;
    for (index = meta->methodCount() - 1; index >= offset; --index) {
        QMetaMethod method = meta->method(index);
        if (hasMethodAccess(method, index, opt) && (method.name() == name)) {
            data->cachedMembers.insert(name, value);
            return;
        }
    }

    index = qobject->dynamicPropertyNames().indexOf(name);
    if ((index != -1) || (opt & QScriptEngine::AutoCreateDynamicProperties)) {
        QVariant v = QScriptEnginePrivate::toVariant(exec, value);
        (void)qobject->setProperty(name, v);
        return;
    }

    QScriptObjectDelegate::put(object, exec, propertyName, value, slot);
#endif //QT_NO_PROPERTIES
}

bool QObjectDelegate::deleteProperty(QScriptObject *object, JSC::ExecState *exec,
                                     const JSC::Identifier& propertyName)
{
#ifndef QT_NO_PROPERTIES
    QByteArray name = convertToLatin1(propertyName.ustring());
    QObject *qobject = data->value;
    if (!qobject) {
        QString message = QString::fromLatin1("cannot access member `%0' of deleted QObject")
                          .arg(QString::fromLatin1(name));
        JSC::throwError(exec, JSC::GeneralError, message);
        return false;
    }

    const QMetaObject *meta = qobject->metaObject();
    {
        QHash<QByteArray, JSC::JSValue>::iterator it = data->cachedMembers.find(name);
        if (it != data->cachedMembers.end()) {
            if (GeneratePropertyFunctions && (meta->indexOfProperty(name) != -1))
                return false;
            data->cachedMembers.erase(it);
            return true;
        }
    }

    const QScriptEngine::QObjectWrapOptions &opt = data->options;
    int index = meta->indexOfProperty(name);
    if (index != -1) {
        QMetaProperty prop = meta->property(index);
        if (prop.isScriptable() &&
            (!(opt & QScriptEngine::ExcludeSuperClassProperties)
             || (index >= meta->propertyOffset()))) {
                return false;
        }
    }

    index = qobject->dynamicPropertyNames().indexOf(name);
    if (index != -1) {
        (void)qobject->setProperty(name, QVariant());
        return true;
    }

    return QScriptObjectDelegate::deleteProperty(object, exec, propertyName);
#else //QT_NO_PROPERTIES
    return false;
#endif //QT_NO_PROPERTIES
}

void QObjectDelegate::getOwnPropertyNames(QScriptObject *object, JSC::ExecState *exec,
                                          JSC::PropertyNameArray &propertyNames,
                                          JSC::EnumerationMode mode)
{
#ifndef QT_NO_PROPERTIES
    QObject *qobject = data->value;
    if (!qobject) {
        QString message = QString::fromLatin1("cannot get property names of deleted QObject");
        JSC::throwError(exec, JSC::GeneralError, message);
        return;
    }

    const QScriptEngine::QObjectWrapOptions &opt = data->options;
    const QMetaObject *meta = qobject->metaObject();
    {
        int i = (opt & QScriptEngine::ExcludeSuperClassProperties)
                    ? meta->propertyOffset() : 0;
        for ( ; i < meta->propertyCount(); ++i) {
            QMetaProperty prop = meta->property(i);
            if (isEnumerableMetaProperty(prop, meta, i)) {
                QString name = QString::fromLatin1(prop.name());
                propertyNames.add(JSC::Identifier(exec, name));
            }
        }
    }

    {
        QList<QByteArray> dpNames = qobject->dynamicPropertyNames();
        for (int i = 0; i < dpNames.size(); ++i) {
            QString name = QString::fromLatin1(dpNames.at(i));
            propertyNames.add(JSC::Identifier(exec, name));
        }
    }

    if (!(opt & QScriptEngine::SkipMethodsInEnumeration)) {
        int i = (opt & QScriptEngine::ExcludeSuperClassMethods)
                    ? meta->methodOffset() : 0;
        for ( ; i < meta->methodCount(); ++i) {
            QMetaMethod method = meta->method(i);
            if (hasMethodAccess(method, i, opt)) {
                QMetaMethod method = meta->method(i);
                QString sig = QString::fromLatin1(method.methodSignature().constData());
                propertyNames.add(JSC::Identifier(exec, sig));
            }
        }
    }

    QScriptObjectDelegate::getOwnPropertyNames(object, exec, propertyNames, mode);
#endif //QT_NO_PROPERTIES
}

void QObjectDelegate::markChildren(QScriptObject *object, JSC::MarkStack& markStack)
{
    QHash<QByteArray, JSC::JSValue>::const_iterator it;
    for (it = data->cachedMembers.constBegin(); it != data->cachedMembers.constEnd(); ++it) {
        JSC::JSValue val = it.value();
        if (val)
            markStack.append(val);
    }

    QScriptObjectDelegate::markChildren(object, markStack);
}

bool QObjectDelegate::compareToObject(QScriptObject *, JSC::ExecState *exec, JSC::JSObject *o2)
{
    if (!o2->inherits(&QScriptObject::info))
        return false;
    QScriptObject *object = static_cast<QScriptObject*>(o2);
    QScriptObjectDelegate *delegate = object->delegate();
    if (!delegate || (delegate->type() != QScriptObjectDelegate::QtObject))
        return false;
    return value() == static_cast<QObjectDelegate *>(delegate)->value();
}

static JSC::JSValue JSC_HOST_CALL qobjectProtoFuncFindChild(JSC::ExecState *exec, JSC::JSObject*,
                                                            JSC::JSValue thisValue, const JSC::ArgList &args)
{
    QScriptEnginePrivate *engine = scriptEngineFromExec(exec);
    thisValue = engine->toUsableValue(thisValue);
    if (!thisValue.inherits(&QScriptObject::info))
        return throwError(exec, JSC::TypeError, "this object is not a QObject");
    QScriptObject *scriptObject = static_cast<QScriptObject*>(JSC::asObject(thisValue));
    QScriptObjectDelegate *delegate = scriptObject->delegate();
    if (!delegate || (delegate->type() != QScriptObjectDelegate::QtObject))
        return throwError(exec, JSC::TypeError, "this object is not a QObject");
    QObject *obj = static_cast<QObjectDelegate*>(delegate)->value();
    QString name;
    if (args.size() != 0)
        name = args.at(0).toString(exec);
    QObject *child = obj->findChild<QObject*>(name);
    QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject;
    return engine->newQObject(child, QScriptEngine::QtOwnership, opt);
}

static JSC::JSValue JSC_HOST_CALL qobjectProtoFuncFindChildren(JSC::ExecState *exec, JSC::JSObject*,
                                                               JSC::JSValue thisValue, const JSC::ArgList &args)
{
    QScriptEnginePrivate *engine = scriptEngineFromExec(exec);
    thisValue = engine->toUsableValue(thisValue);
    // extract the QObject
    if (!thisValue.inherits(&QScriptObject::info))
        return throwError(exec, JSC::TypeError, "this object is not a QObject");
    QScriptObject *scriptObject = static_cast<QScriptObject*>(JSC::asObject(thisValue));
    QScriptObjectDelegate *delegate = scriptObject->delegate();
    if (!delegate || (delegate->type() != QScriptObjectDelegate::QtObject))
        return throwError(exec, JSC::TypeError, "this object is not a QObject");
    const QObject *const obj = static_cast<QObjectDelegate*>(delegate)->value();

    // find the children
    QList<QObject *> children;
    if (args.size() != 0) {
        const JSC::JSValue arg = args.at(0);
        if (arg.inherits(&JSC::RegExpObject::info)) {
            const QObjectList allChildren= obj->children();

            JSC::RegExpObject *const regexp = JSC::asRegExpObject(arg);

            const int allChildrenCount = allChildren.size();
            for (int i = 0; i < allChildrenCount; ++i) {
                QObject *const child = allChildren.at(i);
                const JSC::UString childName = child->objectName();
                JSC::RegExpConstructor* regExpConstructor = engine->originalGlobalObject()->regExpConstructor();
                int position;
                int length;
                regExpConstructor->performMatch(regexp->regExp(), childName, 0, position, length);
                if (position >= 0)
                    children.append(child);
            }
        } else {
            const QString name(args.at(0).toString(exec));
            children = obj->findChildren<QObject*>(name);
        }
    } else {
        children = obj->findChildren<QObject*>(QString());
    }
    // create the result array with the children
    const int length = children.size();
    JSC::JSArray *const result = JSC::constructEmptyArray(exec, length);

    QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject;
    for (int i = 0; i < length; ++i) {
        QObject *const child = children.at(i);
        result->put(exec, i, engine->newQObject(child, QScriptEngine::QtOwnership, opt));
    }
    return JSC::JSValue(result);
}

static JSC::JSValue JSC_HOST_CALL qobjectProtoFuncToString(JSC::ExecState *exec, JSC::JSObject*,
                                                           JSC::JSValue thisValue, const JSC::ArgList&)
{
    QScriptEnginePrivate *engine = scriptEngineFromExec(exec);
    thisValue = engine->toUsableValue(thisValue);
    if (!thisValue.inherits(&QScriptObject::info))
        return JSC::jsUndefined();
    QScriptObject *scriptObject = static_cast<QScriptObject*>(JSC::asObject(thisValue));
    QScriptObjectDelegate *delegate = scriptObject->delegate();
    if (!delegate || (delegate->type() != QScriptObjectDelegate::QtObject))
        return JSC::jsUndefined();
    QObject *obj = static_cast<QObjectDelegate*>(delegate)->value();
    const QMetaObject *meta = obj ? obj->metaObject() : &QObject::staticMetaObject;
    QString name = obj ? obj->objectName() : QString::fromUtf8("unnamed");
    QString str = QString::fromUtf8("%0(name = \"%1\")")
                  .arg(QLatin1String(meta->className())).arg(name);
    return JSC::jsString(exec, str);
}

QObjectPrototype::QObjectPrototype(JSC::ExecState* exec, WTF::PassRefPtr<JSC::Structure> structure,
                                   JSC::Structure* prototypeFunctionStructure)
    : QScriptObject(structure)
{
    setDelegate(new QObjectDelegate(new QObjectPrototypeObject(), QScriptEngine::AutoOwnership,
                                    QScriptEngine::ExcludeSuperClassMethods
                                    | QScriptEngine::ExcludeSuperClassProperties
                                    | QScriptEngine::ExcludeChildObjects));

    putDirectFunction(exec, new (exec) JSC::NativeFunctionWrapper(exec, prototypeFunctionStructure, /*length=*/0, exec->propertyNames().toString, qobjectProtoFuncToString), JSC::DontEnum);
    putDirectFunction(exec, new (exec) JSC::NativeFunctionWrapper(exec, prototypeFunctionStructure, /*length=*/1, JSC::Identifier(exec, "findChild"), qobjectProtoFuncFindChild), JSC::DontEnum);
    putDirectFunction(exec, new (exec) JSC::NativeFunctionWrapper(exec, prototypeFunctionStructure, /*length=*/1, JSC::Identifier(exec, "findChildren"), qobjectProtoFuncFindChildren), JSC::DontEnum);
    this->structure()->setHasGetterSetterProperties(true);
}

const JSC::ClassInfo QMetaObjectWrapperObject::info = { "QMetaObject", 0, 0, 0 };

QMetaObjectWrapperObject::QMetaObjectWrapperObject(
    JSC::ExecState *exec, const QMetaObject *metaObject, JSC::JSValue ctor,
    WTF::PassRefPtr<JSC::Structure> sid)
    : JSC::JSObject(sid),
      data(new Data(metaObject, ctor))
{
    if (!ctor)
        data->prototype = new (exec)JSC::JSObject(exec->lexicalGlobalObject()->emptyObjectStructure());
}

QMetaObjectWrapperObject::~QMetaObjectWrapperObject()
{
    delete data;
}

bool QMetaObjectWrapperObject::getOwnPropertySlot(
    JSC::ExecState *exec, const JSC::Identifier& propertyName,
    JSC::PropertySlot &slot)
{
    const QMetaObject *meta = data->value;
    if (!meta)
        return false;

    if (propertyName == exec->propertyNames().prototype) {
        if (data->ctor)
            slot.setValue(data->ctor.get(exec, propertyName));
        else
            slot.setValue(data->prototype);
        return true;
    }

    QByteArray name = convertToLatin1(propertyName.ustring());

    for (int i = 0; i < meta->enumeratorCount(); ++i) {
        QMetaEnum e = meta->enumerator(i);
        for (int j = 0; j < e.keyCount(); ++j) {
            const char *key = e.key(j);
            if (!qstrcmp(key, name.constData())) {
                slot.setValue(JSC::JSValue(exec, e.value(j)));
                return true;
            }
        }
    }

    return JSC::JSObject::getOwnPropertySlot(exec, propertyName, slot);
}

bool QMetaObjectWrapperObject::getOwnPropertyDescriptor(
    JSC::ExecState* exec, const JSC::Identifier& propertyName,
    JSC::PropertyDescriptor& descriptor)
{
    const QMetaObject *meta = data->value;
    if (!meta)
        return false;

    if (propertyName == exec->propertyNames().prototype) {
        descriptor.setDescriptor(data->ctor
                                 ? data->ctor.get(exec, propertyName)
                                 : data->prototype,
                                 JSC::DontDelete | JSC::DontEnum);
        return true;
    }

    QByteArray name = QString(propertyName.ustring()).toLatin1();

    for (int i = 0; i < meta->enumeratorCount(); ++i) {
        QMetaEnum e = meta->enumerator(i);
        for (int j = 0; j < e.keyCount(); ++j) {
            const char *key = e.key(j);
            if (!qstrcmp(key, name.constData())) {
                descriptor.setDescriptor(JSC::JSValue(exec, e.value(j)),
                                         JSC::ReadOnly | JSC::DontDelete);
                return true;
            }
        }
    }

    return JSC::JSObject::getOwnPropertyDescriptor(exec, propertyName, descriptor);
}

void QMetaObjectWrapperObject::put(JSC::ExecState* exec, const JSC::Identifier& propertyName,
                                   JSC::JSValue value, JSC::PutPropertySlot &slot)
{
    if (propertyName == exec->propertyNames().prototype) {
        if (data->ctor)
            data->ctor.put(exec, propertyName, value, slot);
        else
            data->prototype = value;
        return;
    }
    const QMetaObject *meta = data->value;
    if (meta) {
        QByteArray name = convertToLatin1(propertyName.ustring());
        for (int i = 0; i < meta->enumeratorCount(); ++i) {
            QMetaEnum e = meta->enumerator(i);
            for (int j = 0; j < e.keyCount(); ++j) {
                if (!qstrcmp(e.key(j), name.constData()))
                    return;
            }
        }
    }
    JSC::JSObject::put(exec, propertyName, value, slot);
}

bool QMetaObjectWrapperObject::deleteProperty(
    JSC::ExecState *exec, const JSC::Identifier& propertyName)
{
    if (propertyName == exec->propertyNames().prototype)
        return false;
    const QMetaObject *meta = data->value;
    if (meta) {
        QByteArray name = convertToLatin1(propertyName.ustring());
        for (int i = 0; i < meta->enumeratorCount(); ++i) {
            QMetaEnum e = meta->enumerator(i);
            for (int j = 0; j < e.keyCount(); ++j) {
                if (!qstrcmp(e.key(j), name.constData()))
                    return false;
            }
        }
    }
    return JSC::JSObject::deleteProperty(exec, propertyName);
}

void QMetaObjectWrapperObject::getOwnPropertyNames(JSC::ExecState *exec,
                                                   JSC::PropertyNameArray &propertyNames,
                                                   JSC::EnumerationMode mode)
{
    const QMetaObject *meta = data->value;
    if (!meta)
        return;
    for (int i = 0; i < meta->enumeratorCount(); ++i) {
        QMetaEnum e = meta->enumerator(i);
        for (int j = 0; j < e.keyCount(); ++j)
            propertyNames.add(JSC::Identifier(exec, e.key(j)));
    }
    JSC::JSObject::getOwnPropertyNames(exec, propertyNames, mode);
}

void QMetaObjectWrapperObject::markChildren(JSC::MarkStack& markStack)
{
    if (data->ctor)
        markStack.append(data->ctor);
    if (data->prototype)
        markStack.append(data->prototype);
    JSC::JSObject::markChildren(markStack);
}

JSC::CallType QMetaObjectWrapperObject::getCallData(JSC::CallData& callData)
{
    callData.native.function = call;
    return JSC::CallTypeHost;
}

JSC::ConstructType QMetaObjectWrapperObject::getConstructData(JSC::ConstructData& constructData)
{
    constructData.native.function = construct;
    return JSC::ConstructTypeHost;
}

JSC::JSValue JSC_HOST_CALL QMetaObjectWrapperObject::call(
    JSC::ExecState *exec, JSC::JSObject *callee,
    JSC::JSValue thisValue, const JSC::ArgList &args)
{
    QScriptEnginePrivate *eng_p = scriptEngineFromExec(exec);
    thisValue = eng_p->toUsableValue(thisValue);
    if (!callee->inherits(&QMetaObjectWrapperObject::info))
        return throwError(exec, JSC::TypeError, "callee is not a QMetaObject");
    QMetaObjectWrapperObject *self =  static_cast<QMetaObjectWrapperObject*>(callee);
    JSC::ExecState *previousFrame = eng_p->currentFrame;
    eng_p->pushContext(exec, thisValue, args, callee);
    JSC::JSValue result = self->execute(eng_p->currentFrame, args);
    eng_p->popContext();
    eng_p->currentFrame = previousFrame;
    return result;
}

JSC::JSObject* QMetaObjectWrapperObject::construct(JSC::ExecState *exec, JSC::JSObject *callee, const JSC::ArgList &args)
{
    QMetaObjectWrapperObject *self = static_cast<QMetaObjectWrapperObject*>(callee);
    QScriptEnginePrivate *eng_p = scriptEngineFromExec(exec);
    JSC::ExecState *previousFrame = eng_p->currentFrame;
    eng_p->pushContext(exec, JSC::JSValue(), args, callee, true);
    JSC::JSValue result = self->execute(eng_p->currentFrame, args);
    eng_p->popContext();
    eng_p->currentFrame = previousFrame;
    if (!result || !result.isObject())
        return 0;
    return JSC::asObject(result);
}

JSC::JSValue QMetaObjectWrapperObject::execute(JSC::ExecState *exec,
                                               const JSC::ArgList &args)
{
    if (data->ctor) {
        QScriptEnginePrivate *eng_p = QScript::scriptEngineFromExec(exec);
        QScriptContext *ctx = eng_p->contextForFrame(exec);
        JSC::CallData callData;
        JSC::CallType callType = data->ctor.getCallData(callData);
        Q_UNUSED(callType);
        Q_ASSERT_X(callType == JSC::CallTypeHost, Q_FUNC_INFO, "script constructors not supported");
        if (data->ctor.inherits(&FunctionWithArgWrapper::info)) {
            FunctionWithArgWrapper *wrapper = static_cast<FunctionWithArgWrapper*>(JSC::asObject(data->ctor));
            QScriptValue result = wrapper->function()(ctx, QScriptEnginePrivate::get(eng_p), wrapper->arg());
            return eng_p->scriptValueToJSCValue(result);
        } else {
            Q_ASSERT(data->ctor.inherits(&FunctionWrapper::info));
            FunctionWrapper *wrapper = static_cast<FunctionWrapper*>(JSC::asObject(data->ctor));
            QScriptValue result = wrapper->function()(ctx, QScriptEnginePrivate::get(eng_p));
            return eng_p->scriptValueToJSCValue(result);
        }
    } else {
        const QMetaObject *meta = data->value;
        if (meta->constructorCount() > 0) {
            JSC::JSValue result = callQtMethod(exec, QMetaMethod::Constructor, /*thisQObject=*/0,
                                               args, meta, meta->constructorCount()-1, /*maybeOverloaded=*/true);
            if (!exec->hadException()) {
                Q_ASSERT(result && result.inherits(&QScriptObject::info));
                QScriptObject *object = static_cast<QScriptObject*>(JSC::asObject(result));
                QScript::QObjectDelegate *delegate = static_cast<QScript::QObjectDelegate*>(object->delegate());
                delegate->setOwnership(QScriptEngine::AutoOwnership);
                if (data->prototype)
                    object->setPrototype(data->prototype);
            }
            return result;
        } else {
            QString message = QString::fromLatin1("no constructor for %0")
                              .arg(QLatin1String(meta->className()));
            return JSC::throwError(exec, JSC::TypeError, message);
        }
    }
}

struct StaticQtMetaObject : public QObject
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
    static const QMetaObject *get() { return &Qt::staticMetaObject; }
#else
    static const QMetaObject *get()
        { return &static_cast<StaticQtMetaObject*> (0)->staticQtMetaObject; }
#endif
};

static JSC::JSValue JSC_HOST_CALL qmetaobjectProtoFuncClassName(
    JSC::ExecState *exec, JSC::JSObject*, JSC::JSValue thisValue, const JSC::ArgList&)
{
    QScriptEnginePrivate *engine = scriptEngineFromExec(exec);
    thisValue = engine->toUsableValue(thisValue);
    if (!thisValue.inherits(&QMetaObjectWrapperObject::info))
        return throwError(exec, JSC::TypeError, "this object is not a QMetaObject");
    const QMetaObject *meta = static_cast<QMetaObjectWrapperObject*>(JSC::asObject(thisValue))->value();
    return JSC::jsString(exec, meta->className());
}

QMetaObjectPrototype::QMetaObjectPrototype(
    JSC::ExecState *exec, WTF::PassRefPtr<JSC::Structure> structure,
    JSC::Structure* prototypeFunctionStructure)
    : QMetaObjectWrapperObject(exec, StaticQtMetaObject::get(), /*ctor=*/JSC::JSValue(), structure)
{
    putDirectFunction(exec, new (exec) JSC::NativeFunctionWrapper(exec, prototypeFunctionStructure, /*length=*/0, JSC::Identifier(exec, "className"), qmetaobjectProtoFuncClassName), JSC::DontEnum);
}

// Begin moc-generated code -- modify with care! Check "HAND EDIT" parts
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
struct qt_meta_stringdata_QObjectConnectionManager_t {
    const uint offsetsAndSize[6];
    char stringdata[44];
};
#define QT_MOC_LITERAL(ofs, len) \
    uint(offsetof(qt_meta_stringdata_QObjectConnectionManager_t, stringdata) + ofs), len
#else
struct qt_meta_stringdata_QObjectConnectionManager_t {
    QByteArrayData data[3];
    char stringdata[44];
};
#define QT_MOC_LITERAL(idx, ofs, len) { \
    Q_REFCOUNT_INITIALIZE_STATIC, len, 0, 0, \
        offsetof(qt_meta_stringdata_QObjectConnectionManager_t, stringdata) + ofs \
        - idx * sizeof(QByteArrayData) \
    }
#endif
static const qt_meta_stringdata_QObjectConnectionManager_t qt_meta_stringdata_QObjectConnectionManager = {
    {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QT_MOC_LITERAL(0, 33), // "QScript::QObjectConnectionManager..."
QT_MOC_LITERAL(34, 7), // "execute"
QT_MOC_LITERAL(42, 0) // ""
#else
QT_MOC_LITERAL(0, 0, 33),
QT_MOC_LITERAL(1, 34, 7),
QT_MOC_LITERAL(2, 42, 0)
#endif
    },
    "QScript::QObjectConnectionManager\0"
    "execute\0\0"
};
#undef QT_MOC_LITERAL

static const uint qt_meta_data_QObjectConnectionManager[] = {

 // content:
       9,       // revision
       0,       // classname
       0,    0, // classinfo
       1,   14, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       0,       // signalCount

 // slots: name, argc, parameters, tag, flags
       1,    0,   19,    2, 0x0a,

 // slots: parameters
    QMetaType::Void,

       0        // eod
};

void QObjectConnectionManager::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        Q_ASSERT(staticMetaObject.cast(_o));
        QObjectConnectionManager *_t = static_cast<QObjectConnectionManager *>(_o);
        // HAND EDIT: remove switch (_id), add the _id and _a parameters
        _t->execute(_id, _a);
    }
}

const QMetaObject QObjectConnectionManager::staticMetaObject = {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
    {
        QMetaObject::SuperData::link<QObject::staticMetaObject>(),
        qt_meta_stringdata_QObjectConnectionManager.offsetsAndSize,
        qt_meta_data_QObjectConnectionManager,
        qt_static_metacall,
        nullptr,
        nullptr,
        nullptr
    }
#else
    { &QObject::staticMetaObject, qt_meta_stringdata_QObjectConnectionManager.data,
      qt_meta_data_QObjectConnectionManager, qt_static_metacall, 0, 0 }
#endif
};

const QMetaObject *QObjectConnectionManager::metaObject() const
{
    return &staticMetaObject;
}

void *QObjectConnectionManager::qt_metacast(const char *_clname)
{
    if (!_clname) return 0;
    if (!strcmp(_clname, qt_meta_stringdata_QObjectConnectionManager.stringdata))
        return static_cast<void*>(const_cast<QObjectConnectionManager*>(this));
    return QObject::qt_metacast(_clname);
}

int QObjectConnectionManager::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 < slotCounter) // HAND EDIT
            qt_static_metacall(this, _c, _id, _a);
        _id -= slotCounter; // HAND EDIT
    }
    return _id;
}
// End moc-generated code

void QObjectConnectionManager::execute(int slotIndex, void **argv)
{
    JSC::JSValue receiver;
    JSC::JSValue slot;
    JSC::JSValue senderWrapper;
    int signalIndex = -1;
    QScript::APIShim shim(engine);
    for (int i = 0; i < connections.size(); ++i) {
        const QVector<QObjectConnection> &cs = connections.at(i);
        for (int j = 0; j < cs.size(); ++j) {
            const QObjectConnection &c = cs.at(j);
            if (c.slotIndex == slotIndex) {
                receiver = c.receiver;
                slot = c.slot;
                senderWrapper = c.senderWrapper;
                signalIndex = i;
                break;
            }
        }
    }
    if (!slot) {
        // This connection no longer exists (can happen if the signal is
        // emitted from another thread and the call gets queued, but the
        // connection is removed before the QMetaCallEvent gets processed).
        return;
    }
    Q_ASSERT(slot.isObject());

    if (engine->isCollecting()) {
        qWarning("QtScript: can't execute signal handler during GC");
        // we can't do a script function call during GC,
        // so we're forced to ignore this signal
        return;
    }

#if 0
    QScriptFunction *fun = engine->convertToNativeFunction(slot);
    if (fun == 0) {
        // the signal handler has been GC'ed. This can only happen when
        // a QObject is owned by the engine, the engine is destroyed, and
        // there is a script function connected to the destroyed() signal
        Q_ASSERT(signalIndex <= 1); // destroyed(QObject*)
        return;
    }
#endif

    const QMetaObject *meta = sender()->metaObject();
    const QMetaMethod method = meta->method(signalIndex);

    QList<QByteArray> parameterTypes = method.parameterTypes();
    int argc = parameterTypes.count();

    JSC::ExecState *exec = engine->currentFrame;
    QVarLengthArray<JSC::JSValue, 8> argsVector(argc);
    for (int i = 0; i < argc; ++i) {
        JSC::JSValue actual;
        void *arg = argv[i + 1];
        QByteArray typeName = parameterTypes.at(i);
        int argType = QMetaType::type(parameterTypes.at(i));
        if (!argType) {
            qWarning("QScriptEngine: Unable to handle unregistered datatype '%s' "
                        "when invoking handler of signal %s::%s",
                        typeName.constData(), meta->className(), method.methodSignature().constData());
            actual = JSC::jsUndefined();
        } else if (argType == QMetaType::QVariant) {
            actual = QScriptEnginePrivate::jscValueFromVariant(exec, *reinterpret_cast<QVariant*>(arg));
        } else {
            actual = QScriptEnginePrivate::create(exec, argType, arg);
        }
        argsVector[i] = actual;
    }
    JSC::ArgList jscArgs(argsVector.data(), argsVector.size());

    JSC::JSValue senderObject;
    if (senderWrapper && senderWrapper.inherits(&QScriptObject::info)) // ### check if it's actually a QObject wrapper
        senderObject = senderWrapper;
    else {
        QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject;
        senderObject = engine->newQObject(sender(), QScriptEngine::QtOwnership, opt);
    }

    JSC::JSValue thisObject;
    if (receiver && receiver.isObject())
        thisObject = receiver;
    else
        thisObject = engine->globalObject();

    JSC::CallData callData;
    JSC::CallType callType = slot.getCallData(callData);
    if (exec->hadException())
        exec->clearException(); // ### otherwise JSC asserts
    JSC::call(exec, slot, callType, callData, thisObject, jscArgs);

    if (exec->hadException()) {
        if (slot.inherits(&QtFunction::info) && !static_cast<QtFunction*>(JSC::asObject(slot))->qobject()) {
            // The function threw an error because the target QObject has been deleted.
            // The connections list is stale; remove the signal handler and ignore the exception.
            removeSignalHandler(sender(), signalIndex, receiver, slot);
            exec->clearException();
        } else {
            engine->emitSignalHandlerException();
        }
    }
}

QObjectConnectionManager::QObjectConnectionManager(QScriptEnginePrivate *eng)
    : engine(eng), slotCounter(0)
{
}

QObjectConnectionManager::~QObjectConnectionManager()
{
}

void QObjectConnectionManager::clearMarkBits()
{
    for (int i = 0; i < connections.size(); ++i) {
        QVector<QObjectConnection> &cs = connections[i];
        for (int j = 0; j < cs.size(); ++j)
            cs[j].marked = false;
    }
}

/*!
  \internal

  Marks connections owned by this manager.
  Returns the number of connections that were marked by this pass
  (i.e., excluding connections that were already marked).
*/
int QObjectConnectionManager::mark(JSC::MarkStack& markStack)
{
    int markedCount = 0;
    for (int i = 0; i < connections.size(); ++i) {
        QVector<QObjectConnection> &cs = connections[i];
        for (int j = 0; j < cs.size(); ++j) {
            QObjectConnection &c = cs[j];
            if (!c.marked) {
                if (c.hasWeaklyReferencedSender()) {
                    // Don't mark the connection; we don't want the script-owned
                    // sender object to stay alive merely due to a connection.
                } else {
                    c.mark(markStack);
                    ++markedCount;
                }
            }
        }
    }
    return markedCount;
}

bool QObjectConnectionManager::addSignalHandler(
    QObject *sender, int signalIndex, JSC::JSValue receiver,
    JSC::JSValue function, JSC::JSValue senderWrapper,
    Qt::ConnectionType type)
{
    if (connections.size() <= signalIndex)
        connections.resize(signalIndex+1);
    QVector<QObjectConnection> &cs = connections[signalIndex];
    int absSlotIndex = slotCounter + metaObject()->methodOffset();
    bool ok = QMetaObject::connect(sender, signalIndex, this, absSlotIndex, type);
    if (ok)
        cs.append(QObjectConnection(slotCounter++, receiver, function, senderWrapper));
    return ok;
}

bool QObjectConnectionManager::removeSignalHandler(
    QObject *sender, int signalIndex,
    JSC::JSValue receiver, JSC::JSValue slot)
{
    if (connections.size() <= signalIndex)
        return false;
    QVector<QObjectConnection> &cs = connections[signalIndex];
    for (int i = 0; i < cs.size(); ++i) {
        const QObjectConnection &c = cs.at(i);
        if (c.hasTarget(receiver, slot)) {
            int absSlotIndex = c.slotIndex + metaObject()->methodOffset();
            bool ok = QMetaObject::disconnect(sender, signalIndex, this, absSlotIndex);
            if (ok)
                cs.remove(i);
            return ok;
        }
    }
    return false;
}

QObjectData::QObjectData(QScriptEnginePrivate *eng)
    : engine(eng), connectionManager(0)
{
}

QObjectData::~QObjectData()
{
    if (connectionManager) {
        delete connectionManager;
        connectionManager = 0;
    }
}

void QObjectData::clearConnectionMarkBits()
{
    if (connectionManager)
        connectionManager->clearMarkBits();
}

int QObjectData::markConnections(JSC::MarkStack& markStack)
{
    if (connectionManager)
        return connectionManager->mark(markStack);
    return 0;
}

// This function assumes all objects reachable elsewhere in the JS environment
// (stack, heap) have been marked already (see QScriptEnginePrivate::mark()).
// This determines whether any of Qt Script's internal QObject wrappers are only
// weakly referenced and can be discarded.
void QObjectData::markWrappers(JSC::MarkStack& markStack)
{
    QList<QScript::QObjectWrapperInfo>::iterator it;
    for (it = wrappers.begin(); it != wrappers.end(); ) {
        const QScript::QObjectWrapperInfo &info = *it;
        if (JSC::Heap::isCellMarked(info.object)) {
            ++it;
        } else if (info.isCollectableWhenWeaklyReferenced()) {
            it = wrappers.erase(it);
        } else {
            markStack.append(info.object);
            ++it;
        }
    }
}

bool QObjectData::addSignalHandler(QObject *sender,
                                   int signalIndex,
                                   JSC::JSValue receiver,
                                   JSC::JSValue slot,
                                   JSC::JSValue senderWrapper,
                                   Qt::ConnectionType type)
{
    if (!connectionManager)
        connectionManager = new QObjectConnectionManager(engine);
    return connectionManager->addSignalHandler(
        sender, signalIndex, receiver, slot, senderWrapper, type);
}

bool QObjectData::removeSignalHandler(QObject *sender,
                                      int signalIndex,
                                      JSC::JSValue receiver,
                                      JSC::JSValue slot)
{
    if (!connectionManager)
        return false;
    return connectionManager->removeSignalHandler(
        sender, signalIndex, receiver, slot);
}

QScriptObject *QObjectData::findWrapper(QScriptEngine::ValueOwnership ownership,
                                        const QScriptEngine::QObjectWrapOptions &options) const
{
    for (int i = 0; i < wrappers.size(); ++i) {
        const QObjectWrapperInfo &info = wrappers.at(i);
        if ((info.ownership == ownership) && (info.options == options))
            return info.object;
    }
    return 0;
}

void QObjectData::registerWrapper(QScriptObject *wrapper,
                                  QScriptEngine::ValueOwnership ownership,
                                  const QScriptEngine::QObjectWrapOptions &options)
{
    wrappers.append(QObjectWrapperInfo(wrapper, ownership, options));
}

} // namespace QScript

QT_END_NAMESPACE

namespace JSC
{
    ASSERT_CLASS_FITS_IN_CELL(QScript::QtFunction);
}

#include "moc_qscriptqobject_p.cpp"

