// Copyright (C) 2017 Crimson AS <info@crimson.no>
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only


#include "qv4objectproto_p.h"
#include "qv4argumentsobject_p.h"
#include <private/qv4mm_p.h>
#include "qv4scopedvalue_p.h"
#include "qv4objectiterator_p.h"
#include "qv4symbol_p.h"
#include "qv4propertykey_p.h"

#include <QtCore/QDateTime>
#include <QtCore/QStringList>

using namespace QV4;


DEFINE_OBJECT_VTABLE(ObjectCtor);

void Heap::ObjectCtor::init(QV4::ExecutionEngine *engine)
{
    Heap::FunctionObject::init(engine, QStringLiteral("Object"));
}

ReturnedValue ObjectCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget)
{
    ExecutionEngine *v4 = f->engine();
    const ObjectCtor *nt = static_cast<const ObjectCtor *>(newTarget);
    if (!argc || argv[0].isUndefined() || argv[0].isNull()) {
        Scope scope(v4);
        ScopedObject obj(scope, scope.engine->newObject());
        ScopedObject proto(scope, nt->get(scope.engine->id_prototype()));
        if (!!proto)
            obj->setPrototypeOf(proto);
        return obj.asReturnedValue();
    } else {
        return argv[0].toObject(v4)->asReturnedValue();
    }
}

ReturnedValue ObjectCtor::virtualCall(const FunctionObject *m, const Value *, const Value *argv, int argc)
{
    ExecutionEngine *v4 = m->engine();
    if (!argc || argv[0].isUndefined() || argv[0].isNull()) {
        return v4->newObject()->asReturnedValue();
    } else {
        return argv[0].toObject(v4)->asReturnedValue();
    }
}

void ObjectPrototype::init(ExecutionEngine *v4, Object *ctor)
{
    Scope scope(v4);
    ScopedObject o(scope, this);

    ctor->defineReadonlyProperty(v4->id_prototype(), o);
    ctor->defineReadonlyConfigurableProperty(v4->id_length(), Value::fromInt32(1));
    ctor->defineDefaultProperty(QStringLiteral("getPrototypeOf"), method_getPrototypeOf, 1);
    ctor->defineDefaultProperty(QStringLiteral("getOwnPropertyDescriptor"), method_getOwnPropertyDescriptor, 2);
    ctor->defineDefaultProperty(QStringLiteral("getOwnPropertyDescriptors"), method_getOwnPropertyDescriptors, 1);
    ctor->defineDefaultProperty(QStringLiteral("getOwnPropertyNames"), method_getOwnPropertyNames, 1);
    ctor->defineDefaultProperty(QStringLiteral("getOwnPropertySymbols"), method_getOwnPropertySymbols, 1);
    ctor->defineDefaultProperty(QStringLiteral("assign"), method_assign, 2);
    ctor->defineDefaultProperty(QStringLiteral("create"), method_create, 2);
    ctor->defineDefaultProperty(QStringLiteral("defineProperty"), method_defineProperty, 3);
    ctor->defineDefaultProperty(QStringLiteral("defineProperties"), method_defineProperties, 2);
    ctor->defineDefaultProperty(QStringLiteral("entries"), method_entries, 1);
    ctor->defineDefaultProperty(QStringLiteral("seal"), method_seal, 1);
    ctor->defineDefaultProperty(QStringLiteral("freeze"), method_freeze, 1);
    ctor->defineDefaultProperty(QStringLiteral("preventExtensions"), method_preventExtensions, 1);
    ctor->defineDefaultProperty(QStringLiteral("is"), method_is, 2);
    ctor->defineDefaultProperty(QStringLiteral("isSealed"), method_isSealed, 1);
    ctor->defineDefaultProperty(QStringLiteral("isFrozen"), method_isFrozen, 1);
    ctor->defineDefaultProperty(QStringLiteral("isExtensible"), method_isExtensible, 1);
    ctor->defineDefaultProperty(QStringLiteral("keys"), method_keys, 1);
    ctor->defineDefaultProperty(QStringLiteral("setPrototypeOf"), method_setPrototypeOf, 2);
    ctor->defineDefaultProperty(QStringLiteral("values"), method_values, 1);

    defineDefaultProperty(QStringLiteral("constructor"), (o = ctor));
    defineDefaultProperty(v4->id_toString(), method_toString, 0);
    defineDefaultProperty(v4->id_toLocaleString(), method_toLocaleString, 0);
    defineDefaultProperty(v4->id_valueOf(), method_valueOf, 0);
    defineDefaultProperty(QStringLiteral("hasOwnProperty"), method_hasOwnProperty, 1);
    defineDefaultProperty(QStringLiteral("isPrototypeOf"), method_isPrototypeOf, 1);
    defineDefaultProperty(QStringLiteral("propertyIsEnumerable"), method_propertyIsEnumerable, 1);
    defineDefaultProperty(QStringLiteral("__defineGetter__"), method_defineGetter, 2);
    defineDefaultProperty(QStringLiteral("__defineSetter__"), method_defineSetter, 2);

    defineAccessorProperty(v4->id___proto__(), method_get_proto, method_set_proto);
}

ReturnedValue ObjectPrototype::method_getPrototypeOf(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    Scope scope(b);
    if (argc < 1)
        return scope.engine->throwTypeError();

    ScopedObject o(scope, argv[0].toObject(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();

    ScopedObject p(scope, o->getPrototypeOf());
    return (!!p ? p->asReturnedValue() : Encode::null());
}

ReturnedValue ObjectPrototype::method_is(const FunctionObject *, const Value *, const Value *argv, int argc)
{
    if (!argc)
        return Encode(true);
    if (argc == 1)
        return Encode((argv[0].isUndefined() ? true : false));
    return Encode(argv[0].sameValue(argv[1]));
}

ReturnedValue ObjectPrototype::method_getOwnPropertyDescriptor(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    Scope scope(b);
    if (argc < 1)
        return scope.engine->throwTypeError();

    ScopedObject O(scope, argv[0].toObject(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();

    if (ArgumentsObject::isNonStrictArgumentsObject(O))
        static_cast<ArgumentsObject *>(O.getPointer())->fullyCreate();

    ScopedValue v(scope, argc > 1 ? argv[1] : Value::undefinedValue());
    ScopedPropertyKey name(scope, v->toPropertyKey(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();

    ScopedProperty desc(scope);
    PropertyAttributes attrs = O->getOwnProperty(name, desc);
    return fromPropertyDescriptor(scope.engine, desc, attrs);
}

ReturnedValue ObjectPrototype::method_getOwnPropertyDescriptors(const FunctionObject *f, const Value *, const Value *argv, int argc)
{
    Scope scope(f);
    if (!argc)
        return scope.engine->throwTypeError();

    ScopedObject o(scope, argv[0].toObject(scope.engine));
    if (scope.hasException())
        return Encode::undefined();

    ScopedObject descriptors(scope, scope.engine->newObject());

    ObjectIterator it(scope, o, ObjectIterator::WithSymbols);
    ScopedProperty pd(scope);
    PropertyAttributes attrs;
    ScopedPropertyKey key(scope);
    ScopedObject entry(scope);
    while (1) {
        key = it.next(pd, &attrs);
        if (!key->isValid())
            break;
        entry = fromPropertyDescriptor(scope.engine, pd, attrs);
        descriptors->put(key, entry);
    }

    return descriptors.asReturnedValue();

}

ReturnedValue ObjectPrototype::method_getOwnPropertyNames(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    Scope scope(b);
    if (argc < 1)
        return scope.engine->throwTypeError();

    ScopedObject O(scope, argv[0].toObject(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();

    return Encode(getOwnPropertyNames(scope.engine, argv[0]));
}

ReturnedValue ObjectPrototype::method_getOwnPropertySymbols(const FunctionObject *f, const Value *, const Value *argv, int argc)
{
    Scope scope(f);
    if (!argc)
        return scope.engine->throwTypeError();

    ScopedObject O(scope, argv[0].toObject(scope.engine));
    if (!O)
        return Encode::undefined();

    ScopedArrayObject array(scope, scope.engine->newArrayObject());
    if (O) {
        ObjectIterator it(scope, O, ObjectIterator::WithSymbols);
        ScopedValue name(scope);
        while (1) {
            name = it.nextPropertyNameAsString();
            if (name->isNull())
                break;
            if (!name->isSymbol())
                continue;
            array->push_back(name);
        }
    }
    return array->asReturnedValue();
}

// 19.1.2.1
ReturnedValue ObjectPrototype::method_assign(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    Scope scope(b);
    if (argc < 1)
        return scope.engine->throwTypeError();

    ScopedObject to(scope, argv[0].toObject(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();

    if (argc == 1)
        return to.asReturnedValue();

    for (int i = 1, ei = argc; i < ei; ++i) {
        if (argv[i].isUndefined() || argv[i].isNull())
            continue;

        ScopedObject from(scope, argv[i].toObject(scope.engine));
        if (scope.hasException())
        return QV4::Encode::undefined();
        QV4::ScopedArrayObject keys(scope, QV4::ObjectPrototype::getOwnPropertyNames(scope.engine, from));
        quint32 length = keys->getLength();

        ScopedString nextKey(scope);
        ScopedValue propValue(scope);
        for (quint32 i = 0; i < length; ++i) {
            nextKey = Value::fromReturnedValue(keys->get(i)).toString(scope.engine);

            ScopedProperty prop(scope);
            PropertyAttributes attrs = from->getOwnProperty(nextKey->toPropertyKey(), prop);

            if (attrs == PropertyFlag::Attr_Invalid)
                continue;

            if (!attrs.isEnumerable())
                continue;

            propValue = from->get(nextKey);
            to->set(nextKey, propValue, Object::DoThrowOnRejection);
            if (scope.hasException())
        return QV4::Encode::undefined();
        }
    }

    return to.asReturnedValue();
}

ReturnedValue ObjectPrototype::method_create(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(builtin);
    if (!argc || (!argv[0].isObject() && !argv[0].isNull()))
        return scope.engine->throwTypeError();

    ScopedObject O(scope, argv[0]);

    ScopedObject newObject(scope, scope.engine->newObject());
    newObject->setPrototypeOf(O);


    if (argc > 1 && !argv[1].isUndefined()) {
        Value *arguments = scope.alloc(argc);
        arguments[0] = newObject;
        memcpy(arguments + 1, argv + 1, (argc - 1)*sizeof(Value));
        return method_defineProperties(builtin, thisObject, arguments, argc);
    }

    return newObject.asReturnedValue();
}

ReturnedValue ObjectPrototype::method_defineProperty(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    Scope scope(b);
    if (!argc || !argv[0].isObject())
        return scope.engine->throwTypeError();

    ScopedObject O(scope, argv[0]);
    ScopedPropertyKey name(scope, (argc > 1 ? argv[1] : Value::undefinedValue()).toPropertyKey(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();

    ScopedValue attributes(scope, argc > 2 ? argv[2] : Value::undefinedValue());
    ScopedProperty pd(scope);
    PropertyAttributes attrs;
    toPropertyDescriptor(scope.engine, attributes, pd, &attrs);
    if (scope.hasException())
        return QV4::Encode::undefined();

    if (!O->defineOwnProperty(name, pd, attrs))
        THROW_TYPE_ERROR();

    return O.asReturnedValue();
}

ReturnedValue ObjectPrototype::method_defineProperties(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    Scope scope(b);
    if (argc < 2 || !argv[0].isObject())
        return scope.engine->throwTypeError();

    ScopedObject O(scope, argv[0]);

    ScopedObject o(scope, argv[1].toObject(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();

    ScopedValue val(scope);

    ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly);
    ScopedProperty pd(scope);
    ScopedProperty n(scope);
    ScopedPropertyKey key(scope);
    while (1) {
        PropertyAttributes attrs;
        key = it.next(pd, &attrs);
        if (!key->isValid())
            break;
        PropertyAttributes nattrs;
        val = o->getValue(pd->value, attrs);
        toPropertyDescriptor(scope.engine, val, n, &nattrs);
        if (scope.hasException())
        return QV4::Encode::undefined();
        bool ok = O->defineOwnProperty(key, n, nattrs);
        if (!ok)
            THROW_TYPE_ERROR();
    }

    return O.asReturnedValue();
}

ReturnedValue ObjectPrototype::method_entries(const FunctionObject *f, const Value *, const Value *argv, int argc)
{
    Scope scope(f);
    if (!argc)
        return scope.engine->throwTypeError();

    ScopedObject o(scope, argv[0].toObject(scope.engine));
    if (scope.hasException())
        return Encode::undefined();

    ScopedArrayObject a(scope, scope.engine->newArrayObject());

    ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly);
    ScopedString name(scope);
    ScopedArrayObject entry(scope);
    while (1) {
        name = it.nextPropertyNameAsString();
        if (!name)
            break;
        entry = scope.engine->newArrayObject();
        entry->push_back(name);
        a->push_back(entry);
    }

    // now add values, do this after the loop above as reading out the values can have side effects
    uint len = a->getLength();
    ScopedValue value(scope);
    for (uint i = 0; i < len; ++i) {
        entry = a->get(PropertyKey::fromArrayIndex(i));
        name = entry->get(PropertyKey::fromArrayIndex(0));
        value = o->get(name->toPropertyKey());
        if (scope.hasException())
            return Encode::undefined();
        entry->push_back(value);
    }

    return a.asReturnedValue();
}

ReturnedValue ObjectPrototype::method_seal(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    const Value a = argc ? argv[0] : Value::undefinedValue();
    if (!a.isObject())
        // 19.1.2.17, 1
        return a.asReturnedValue();

    Scope scope(b);
    ScopedObject o(scope, a);
    o->setInternalClass(o->internalClass()->canned());

    if (o->arrayData()) {
        ArrayData::ensureAttributes(o);
        for (uint i = 0; i < o->d()->arrayData->values.alloc; ++i) {
            if (!o->arrayData()->isEmpty(i))
                o->d()->arrayData->attrs[i].setConfigurable(false);
        }
    }

    return o.asReturnedValue();
}

ReturnedValue ObjectPrototype::method_freeze(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    const Value a = argc ? argv[0] : Value::undefinedValue();
    if (!a.isObject())
        // 19.1.2.5, 1
        return a.asReturnedValue();

    Scope scope(b);
    ScopedObject o(scope, a);

    if (ArgumentsObject::isNonStrictArgumentsObject(o))
        static_cast<ArgumentsObject *>(o.getPointer())->fullyCreate();

    o->setInternalClass(o->internalClass()->cryopreserved());

    if (o->arrayData()) {
        ArrayData::ensureAttributes(o);
        for (uint i = 0; i < o->arrayData()->values.alloc; ++i) {
            if (!o->arrayData()->isEmpty(i))
                o->arrayData()->attrs[i].setConfigurable(false);
            if (o->arrayData()->attrs[i].isData())
                o->arrayData()->attrs[i].setWritable(false);
        }
    }
    return o.asReturnedValue();
}

ReturnedValue ObjectPrototype::method_preventExtensions(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    Scope scope(b);
    if (!argc)
        return Encode::undefined();

    ScopedObject o(scope, argv[0]);
    if (!o)
        return argv[0].asReturnedValue();

    o->preventExtensions();
    return o.asReturnedValue();
}

ReturnedValue ObjectPrototype::method_isSealed(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    Scope scope(b);
    if (!argc)
        return Encode(true);

    ScopedObject o(scope, argv[0]);
    if (!o)
        return Encode(true);

    if (o->isExtensible())
        return  Encode(false);

    if (o->internalClass() != o->internalClass()->canned())
        return Encode(false);

    if (!o->arrayData() || !o->arrayData()->length())
        return Encode(true);

    Q_ASSERT(o->arrayData() && o->arrayData()->length());
    if (!o->arrayData()->attrs)
        return Encode(false);

    for (uint i = 0; i < o->arrayData()->values.alloc; ++i) {
        if (!o->arrayData()->isEmpty(i))
            if (o->arrayData()->attributes(i).isConfigurable())
                return Encode(false);
    }

    return Encode(true);
}

ReturnedValue ObjectPrototype::method_isFrozen(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    Scope scope(b);
    if (!argc)
        return Encode(true);

    ScopedObject o(scope, argv[0]);
    if (!o)
        return Encode(true);

    if (o->isExtensible())
        return Encode(false);

    if (!o->internalClass()->isImplicitlyFrozen())
        return Encode(false);

    if (!o->arrayData() || !o->arrayData()->length())
        return Encode(true);

    Q_ASSERT(o->arrayData() && o->arrayData()->length());
    if (!o->arrayData()->attrs)
        return Encode(false);

    for (uint i = 0; i < o->arrayData()->values.alloc; ++i) {
        if (!o->arrayData()->isEmpty(i))
            if (o->arrayData()->attributes(i).isConfigurable() || o->arrayData()->attributes(i).isWritable())
                return Encode(false);
    }

    return Encode(true);
}

ReturnedValue ObjectPrototype::method_isExtensible(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    Scope scope(b);
    if (!argc)
        return Encode(false);

    ScopedObject o(scope, argv[0]);
    if (!o)
        return Encode(false);

    return Encode((bool)o->isExtensible());
}

ReturnedValue ObjectPrototype::method_keys(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
    Scope scope(b);
    if (!argc)
        return scope.engine->throwTypeError();

    ScopedObject o(scope, argv[0].toObject(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();

    ScopedArrayObject a(scope, scope.engine->newArrayObject());

    ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly);
    ScopedValue name(scope);
    ScopedValue value(scope);
    while (1) {
        name = it.nextPropertyNameAsString(value);
        if (name->isNull())
            break;
        a->push_back(name);
    }

    return a.asReturnedValue();
}

ReturnedValue ObjectPrototype::method_setPrototypeOf(const FunctionObject *f, const Value *, const Value *argv, int argc)
{
    Scope scope(f->engine());
    if (argc < 2 || argv[0].isNullOrUndefined() || !(argv[1].isObject() || argv[1].isNull()))
        return scope.engine->throwTypeError();

    if (!argv[0].isObject())
        return argv[0].asReturnedValue();

    ScopedObject o(scope, argv[0]);
    const Object *p = argv[1].isNull() ? nullptr : static_cast<const Object *>(argv + 1);
    bool ok = o->setPrototypeOf(p);
    if (!ok)
        return scope.engine->throwTypeError(QStringLiteral("Could not change prototype."));
    return o->asReturnedValue();
}

ReturnedValue ObjectPrototype::method_values(const FunctionObject *f, const Value *, const Value *argv, int argc)
{
    Scope scope(f);
    if (!argc)
        return scope.engine->throwTypeError();

    ScopedObject o(scope, argv[0].toObject(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();

    ScopedArrayObject a(scope, scope.engine->newArrayObject());

    ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly);
    ScopedPropertyKey key(scope);
    ScopedProperty pd(scope);
    ScopedValue value(scope);
    PropertyAttributes attrs;
    while (1) {
        key = it.next(pd, &attrs);
        if (!key->isValid())
            break;
        value = o->getValue(pd->value, attrs);
        a->push_back(value);
    }

    return a.asReturnedValue();
}

ReturnedValue ObjectPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    QString string;
    if (thisObject->isUndefined()) {
        string = QStringLiteral("[object Undefined]");
    } else if (thisObject->isNull()) {
        string = QStringLiteral("[object Null]");
    } else {
        const Object *o = thisObject->as<Object>();
        if (!o) {
            // primitive, get the proper prototype
            if (thisObject->isBoolean())
                o = v4->booleanPrototype();
            else if (thisObject->isNumber())
                o = v4->numberPrototype();
            else if (thisObject->isString())
                o = v4->stringPrototype();
            else if (thisObject->isSymbol())
                o = v4->symbolPrototype();
            Q_ASSERT(o);
        }
        QString name = o->className();
        Scope scope(v4);
        ScopedString toStringTag(scope, o->get(v4->symbol_toStringTag()));
        if (toStringTag)
            name = toStringTag->toQString();
        string = QStringLiteral("[object %1]").arg(name);
    }
    return Encode(v4->newString(string));
}

ReturnedValue ObjectPrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    CHECK_STACK_LIMITS(scope.engine)
    ScopedObject o(scope, thisObject->toObject(scope.engine));
    if (!o)
        RETURN_UNDEFINED();

    ScopedFunctionObject f(scope, o->get(scope.engine->id_toString()));
    if (!f)
        THROW_TYPE_ERROR();

    return checkedResult(scope.engine, f->call(thisObject, argv, argc));
}

ReturnedValue ObjectPrototype::method_valueOf(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    return Encode(thisObject->toObject(b->engine()));
}

ReturnedValue ObjectPrototype::method_hasOwnProperty(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    ScopedPropertyKey P(scope, (argc ? argv[0] : Value::undefinedValue()).toPropertyKey(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();
    ScopedObject O(scope, thisObject->toObject(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();
    bool r = O->getOwnProperty(P) != Attr_Invalid;
    return Encode(r);
}

ReturnedValue ObjectPrototype::method_isPrototypeOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    if (!argc || !argv[0].isObject())
        return Encode(false);

    ScopedObject V(scope, argv[0]);
    ScopedObject O(scope, thisObject->toObject(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();
    ScopedObject proto(scope, V->getPrototypeOf());
    while (proto) {
        if (O->d() == proto->d())
            return Encode(true);
        proto = proto->getPrototypeOf();
    }
    return Encode(false);
}

ReturnedValue ObjectPrototype::method_propertyIsEnumerable(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    ScopedPropertyKey p(scope, (argc ? argv[0] : Value::undefinedValue()).toPropertyKey(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();

    ScopedObject o(scope, thisObject->toObject(scope.engine));
    if (scope.hasException())
        return QV4::Encode::undefined();
    PropertyAttributes attrs = o->getOwnProperty(p);
    return Encode(attrs.isEnumerable());
}

ReturnedValue ObjectPrototype::method_defineGetter(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    if (argc < 2)
        THROW_TYPE_ERROR();

    ScopedFunctionObject f(scope, argv[1]);
    if (!f)
        THROW_TYPE_ERROR();

    ScopedString prop(scope, argv[0], ScopedString::Convert);
    if (scope.hasException())
        return QV4::Encode::undefined();

    ScopedObject o(scope, thisObject);
    if (!o) {
        if (!thisObject->isUndefined())
            RETURN_UNDEFINED();
        o = scope.engine->globalObject;
    }

    ScopedProperty pd(scope);
    pd->value = f;
    pd->set = Value::emptyValue();
    bool ok = o->defineOwnProperty(prop->toPropertyKey(), pd, Attr_Accessor);
    if (!ok)
        THROW_TYPE_ERROR();
    RETURN_UNDEFINED();
}

ReturnedValue ObjectPrototype::method_defineSetter(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    if (argc < 2)
        THROW_TYPE_ERROR();

    ScopedFunctionObject f(scope, argv[1]);
    if (!f)
        THROW_TYPE_ERROR();

    ScopedString prop(scope, argv[0], ScopedString::Convert);
    if (scope.hasException())
        return QV4::Encode::undefined();

    ScopedObject o(scope, thisObject);
    if (!o) {
        if (!thisObject->isUndefined())
            RETURN_UNDEFINED();
        o = scope.engine->globalObject;
    }

    ScopedProperty pd(scope);
    pd->value = Value::emptyValue();
    pd->set = f;
    bool ok = o->defineOwnProperty(prop->toPropertyKey(), pd, Attr_Accessor);
    if (!ok)
        THROW_TYPE_ERROR();
    RETURN_UNDEFINED();
}

ReturnedValue ObjectPrototype::method_get_proto(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    Scope scope(b);
    ScopedObject o(scope, thisObject->as<Object>());
    if (!o)
        THROW_TYPE_ERROR();

    return Encode(o->getPrototypeOf());
}

ReturnedValue ObjectPrototype::method_set_proto(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    ScopedObject o(scope, thisObject);
    if (!o || !argc || (!argv[0].isObject() && !argv[0].isNull()))
        THROW_TYPE_ERROR();

    const Object *p = argv[0].isNull() ? nullptr : static_cast<const Object *>(argv);
    bool ok = o->setPrototypeOf(p);
    if (!ok)
        return scope.engine->throwTypeError(QStringLiteral("Could not change prototype."));
    return Encode::undefined();
    RETURN_UNDEFINED();
}

void ObjectPrototype::toPropertyDescriptor(ExecutionEngine *engine, const Value &v, Property *desc, PropertyAttributes *attrs)
{
    Scope scope(engine);
    ScopedObject o(scope, v);
    if (!o) {
        engine->throwTypeError();
        return;
    }

    attrs->clear();
    desc->value = Value::emptyValue();
    desc->set = Value::emptyValue();
    ScopedValue tmp(scope);

    if (o->hasProperty(engine->id_enumerable()->toPropertyKey()))
        attrs->setEnumerable((tmp = o->get(engine->id_enumerable()))->toBoolean());

    if (o->hasProperty(engine->id_configurable()->toPropertyKey()))
        attrs->setConfigurable((tmp = o->get(engine->id_configurable()))->toBoolean());

    if (o->hasProperty(engine->id_get()->toPropertyKey())) {
        ScopedValue get(scope, o->get(engine->id_get()));
        FunctionObject *f = get->as<FunctionObject>();
        if (f || get->isUndefined()) {
            desc->value = get;
        } else {
            engine->throwTypeError();
            return;
        }
        attrs->setType(PropertyAttributes::Accessor);
    }

    if (o->hasProperty(engine->id_set()->toPropertyKey())) {
        ScopedValue set(scope, o->get(engine->id_set()));
        FunctionObject *f = set->as<FunctionObject>();
        if (f || set->isUndefined()) {
            desc->set = set;
        } else {
            engine->throwTypeError();
            return;
        }
        attrs->setType(PropertyAttributes::Accessor);
    }

    if (o->hasProperty(engine->id_writable()->toPropertyKey())) {
        if (attrs->isAccessor()) {
            engine->throwTypeError();
            return;
        }
        attrs->setWritable((tmp = o->get(engine->id_writable()))->toBoolean());
    }

    if (o->hasProperty(engine->id_value()->toPropertyKey())) {
        if (attrs->isAccessor()) {
            engine->throwTypeError();
            return;
        }
        desc->value = o->get(engine->id_value());
        attrs->setType(PropertyAttributes::Data);
    }

    if (attrs->isGeneric())
        desc->value = Value::emptyValue();
}


ReturnedValue ObjectPrototype::fromPropertyDescriptor(ExecutionEngine *engine, const Property *desc, PropertyAttributes attrs)
{
    if (attrs.isEmpty())
        return Encode::undefined();

    Scope scope(engine);
    // Let obj be the result of creating a new object as if by the expression new Object() where Object
    // is the standard built-in constructor with that name.
    ScopedObject o(scope, engine->newObject());
    ScopedString s(scope);
    ScopedValue v(scope);

    if (attrs.isData()) {
        s = engine->newString(QStringLiteral("value"));
        o->put(s, desc->value);
        v = Value::fromBoolean(attrs.isWritable());
        s = engine->newString(QStringLiteral("writable"));
        o->put(s, v);
    } else {
        v = desc->getter() ? desc->getter()->asReturnedValue() : Encode::undefined();
        s = engine->newString(QStringLiteral("get"));
        o->put(s, v);
        v = desc->setter() ? desc->setter()->asReturnedValue() : Encode::undefined();
        s = engine->newString(QStringLiteral("set"));
        o->put(s, v);
    }
    v = Value::fromBoolean(attrs.isEnumerable());
    s = engine->newString(QStringLiteral("enumerable"));
    o->put(s, v);
    v = Value::fromBoolean(attrs.isConfigurable());
    s = engine->newString(QStringLiteral("configurable"));
    o->put(s, v);

    return o.asReturnedValue();
}

// es6: GetOwnPropertyKeys
Heap::ArrayObject *ObjectPrototype::getOwnPropertyNames(ExecutionEngine *v4, const Value &o)
{
    Scope scope(v4);
    ScopedArrayObject array(scope, v4->newArrayObject());
    ScopedObject O(scope, o.toObject(v4));
    if (O) {
        ObjectIterator it(scope, O, ObjectIterator::NoFlags);
        ScopedValue name(scope);
        while (1) {
            name = it.nextPropertyNameAsString();
            if (name->isNull())
                break;
            if (name->isSymbol())
                continue;
            array->push_back(name);
        }
    }
    return array->d();
}
