/*
 *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
 *  Copyright (C) 2003, 2007, 2008, 2011 Apple Inc. All rights reserved.
 *  Copyright (C) 2003 Peter Kelly (pmk@post.com)
 *  Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 *  USA
 *
 */

#include "config.h"
#include "ArrayConstructor.h"

#include "ArrayAllocationProfile.h"
#include "ArrayPrototype.h"
#include "ButterflyInlineMethods.h"
#include "CopiedSpaceInlineMethods.h"
#include "Error.h"
#include "ExceptionHelpers.h"
#include "GetterSetter.h"
#include "JSArray.h"
#include "JSFunction.h"
#include "Lookup.h"
#include "Operations.h"
#include "ProxyObject.h"

#include "ArrayConstructor.lut.h"

namespace JSC {

ASSERT_HAS_TRIVIAL_DESTRUCTOR(ArrayConstructor);

const ClassInfo ArrayConstructor::s_info = { "Function", &InternalFunction::s_info, &arrayConstructorTable, CREATE_METHOD_TABLE(ArrayConstructor) };

/* Source for ArrayConstructor.lut.h
@begin arrayConstructorTable
  of        arrayConstructorOf          DontEnum|Function 0
  from      arrayConstructorFrom        DontEnum|Function 0
@end
*/

static EncodedJSValue JSC_HOST_CALL callArrayConstructor(ExecState*);
static EncodedJSValue JSC_HOST_CALL constructWithArrayConstructor(ExecState*);

ArrayConstructor::ArrayConstructor(VM& vm, Structure* structure)
    : InternalFunction(vm, structure, callArrayConstructor, constructWithArrayConstructor)
{
}

void ArrayConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, ArrayPrototype* arrayPrototype, GetterSetter* speciesSymbol)
{
    Base::finishCreation(vm, vm.propertyNames->Array.string());
    putDirectWithoutTransition(vm, vm.propertyNames->prototype, arrayPrototype, DontEnum | DontDelete | ReadOnly);
    putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(1), ReadOnly | DontEnum);
    putDirectNonIndexAccessorWithoutTransition(vm, vm.propertyNames->speciesSymbol, speciesSymbol, Accessor | ReadOnly | DontEnum);
    JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->isArray, arrayConstructorIsArrayCodeGenerator, DontEnum);
}

// ------------------------------ Functions ---------------------------

JSArray* constructArrayWithSizeQuirk(VM& vm, ExecState* exec, ArrayAllocationProfile* profile, JSGlobalObject* globalObject, JSValue length, JSValue newTarget)
{
    if (!length.isNumber()) {
        return constructArrayNegativeIndexed(vm, exec, profile, globalObject, &length, 1, newTarget);
    }

    uint32_t n = length.toUInt32(exec);
    if (n != length.toNumber(exec)) {
        vm.throwException(exec, createRangeError(exec, ASCIILiteral("Array size is not a small enough positive integer.")));
        return nullptr;
    }
    return constructEmptyArray(exec, profile, globalObject, n, newTarget);
}

static inline JSArray* constructArrayWithSizeQuirk(VM& vm, ExecState* exec, const ArgList& args, JSValue newTarget)
{
    JSGlobalObject* globalObject = asInternalFunction(exec->callee())->globalObject();

    // a single numeric argument denotes the array size (!)
    if (args.size() == 1)
        return constructArrayWithSizeQuirk(vm, exec, nullptr, globalObject, args.at(0), newTarget);

    // otherwise the array is constructed with the arguments in it
    return constructArray(exec, nullptr, globalObject, args, newTarget);
}

static EncodedJSValue JSC_HOST_CALL constructWithArrayConstructor(ExecState* exec)
{
	VM& vm = exec->vm();
    ArgList args(exec);

    auto* jsArray = constructArrayWithSizeQuirk(vm, exec, args, exec->newTarget());
    RETURN_IF_EXCEPTION(vm, encodedJSUndefined());
	return JSValue::encode(jsArray);
}

static EncodedJSValue JSC_HOST_CALL callArrayConstructor(ExecState* exec)
{
	VM& vm = exec->vm();
    ArgList args(exec);

    auto* jsArray = constructArrayWithSizeQuirk(vm, exec, args, JSValue());
    RETURN_IF_EXCEPTION(vm, encodedJSUndefined());
	return JSValue::encode(jsArray);
}

static ALWAYS_INLINE bool isArraySlowInline(ExecState* exec, ProxyObject* proxy)
{
    VM& vm = exec->vm();
    //auto scope = DECLARE_THROW_SCOPE(vm);

    while (true) {
        if (UNLIKELY(proxy->isRevoked())) {
            JSObject* callee = exec->jsCallee();
            String calleeName = ASCIILiteral("Array.isArray");
            auto* function = callee ? jsDynamicCast<JSFunction*>(callee) : nullptr;
            // If this function is from a different globalObject than the one passed in above,
			// then this test will fail even if function is Object.prototype.toString. The only
			// way this test will be work everytime is if we check against the
			// Object.prototype.toString of the function's own globalObject.
			if (function && function == function->globalObject()->objectProtoToStringFunction())
				calleeName = ASCIILiteral("Object.prototype.toString");
            throwTypeError(exec, vm, tryMakeString(calleeName, " cannot be called on a Proxy that has been revoked"));
            return false;
        }
        JSObject* argument = proxy->target();

        if (argument->structure()->typeInfo().type() == ArrayType ||  argument->structure()->typeInfo().type() == DerivedArrayType)
            return true;

        if (argument->structure()->typeInfo().type() != ProxyObjectType)
            return false;

        proxy = jsCast<ProxyObject*>(argument);
    }

    ASSERT_NOT_REACHED();
    return false;
}

bool isArraySlow(ExecState* exec, ProxyObject* argument)
{
    return isArraySlowInline(exec, argument);
}

// ES6 7.2.2
// https://tc39.github.io/ecma262/#sec-isarray
EncodedJSValue JSC_HOST_CALL arrayConstructorPrivateFuncIsArraySlow(ExecState* exec)
{
    ASSERT(jsDynamicCast<ProxyObject*>(exec->argument(0)));
    return JSValue::encode(jsBoolean(isArraySlowInline(exec, jsCast<ProxyObject*>(exec->uncheckedArgument(0)))));
}

} // namespace JSC
