/*
 *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
 *  Copyright (C) 2003, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
 *
 *  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
 *
 */

#ifndef Lookup_h
#define Lookup_h

#include "BatchedTransitionOptimizer.h"
#include "CallFrame.h"
#include "CustomGetterSetter.h"
#include "Identifier.h"
#include "IdentifierInlines.h"
#include "Intrinsic.h"
#include "JSGlobalObject.h"
#include "PropertySlot.h"
#include "PutPropertySlot.h"
#include <wtf/Assertions.h>

namespace JSC {

struct CompactHashIndex {
    const int16_t value;
    const int16_t next;
};

// FIXME: There is no reason this get function can't be simpler.
// ie. typedef JSValue (*GetFunction)(ExecState*, JSObject* baseObject)
typedef PropertySlot::GetValueFunc GetFunction;
typedef PutPropertySlot::PutValueFunc PutFunction;
typedef FunctionExecutable* (*BuiltinGenerator)(VM&);

// Hash table generated by the create_hash_table script.
struct HashTableValue {
    const char* m_key; // property name
    unsigned m_attributes; // JSObject attributes
    Intrinsic m_intrinsic;
    union ValueStorage {
        constexpr ValueStorage(intptr_t value1, intptr_t value2)
            : value1(value1)
            , value2(value2)
        { }
        constexpr ValueStorage(long long constant)
            : constant(constant)
        { }

        struct {
            intptr_t value1;
            intptr_t value2;
        };
        long long constant;
    } m_values;

    unsigned attributes() const { return m_attributes; }

    Intrinsic intrinsic() const { ASSERT(m_attributes & Function); return m_intrinsic; }
    BuiltinGenerator builtinGenerator() const { ASSERT(m_attributes & Builtin); return reinterpret_cast<BuiltinGenerator>(m_values.value1); }
    NativeFunction function() const { ASSERT(m_attributes & Function); return reinterpret_cast<NativeFunction>(m_values.value1); }
    unsigned char functionLength() const { ASSERT(m_attributes & Function); return static_cast<unsigned char>(m_values.value2); }

    GetFunction propertyGetter() const { ASSERT(!(m_attributes & BuiltinOrFunctionOrAccessorOrConstant)); return reinterpret_cast<GetFunction>(m_values.value1); }
    PutFunction propertyPutter() const { ASSERT(!(m_attributes & BuiltinOrFunctionOrAccessorOrConstant)); return reinterpret_cast<PutFunction>(m_values.value2); }

    NativeFunction accessorGetter() const { ASSERT(m_attributes & Accessor); return reinterpret_cast<NativeFunction>(m_values.value1); }
    NativeFunction accessorSetter() const { ASSERT(m_attributes & Accessor); return reinterpret_cast<NativeFunction>(m_values.value2); }
    BuiltinGenerator builtinAccessorGetterGenerator() const;
    BuiltinGenerator builtinAccessorSetterGenerator() const;

    long long constantInteger() const { ASSERT(m_attributes & ConstantInteger); return m_values.constant; }

    intptr_t lexerValue() const { ASSERT(!m_attributes); return m_values.value1; }
};

struct HashTable {
    int numberOfValues;
    int indexMask;
    bool hasSetterOrReadonlyProperties;

    const HashTableValue* values; // Fixed values generated by script.
    const CompactHashIndex* index;

    // Find an entry in the table, and return the entry.
    ALWAYS_INLINE const HashTableValue* entry(PropertyName propertyName) const
    {
        if (propertyName.isSymbol())
            return nullptr;

        auto uid = propertyName.uid();
        if (!uid)
            return nullptr;

        int indexEntry = IdentifierRepHash::hash(uid) & indexMask;
        int valueIndex = index[indexEntry].value;
        if (valueIndex == -1)
            return nullptr;

        while (true) {
            if (WTF::equal(uid, values[valueIndex].m_key))
                return &values[valueIndex];

            indexEntry = index[indexEntry].next;
            if (indexEntry == -1)
                return nullptr;
            valueIndex = index[indexEntry].value;
            ASSERT(valueIndex != -1);
        };
    }

    class ConstIterator {
    public:
        ConstIterator(const HashTable* table, int position)
            : m_table(table)
            , m_position(position)
        {
            skipInvalidKeys();
        }

        const HashTableValue* value() const
        {
            return &m_table->values[m_position];
        }

        const HashTableValue& operator*() const { return *value(); }

        const char* key() const
        {
            return m_table->values[m_position].m_key;
        }

        const HashTableValue* operator->() const
        {
            return value();
        }

        bool operator!=(const ConstIterator& other) const
        {
            ASSERT(m_table == other.m_table);
            return m_position != other.m_position;
        }

        ConstIterator& operator++()
        {
            ASSERT(m_position < m_table->numberOfValues);
            ++m_position;
            skipInvalidKeys();
            return *this;
        }

    private:
        void skipInvalidKeys()
        {
            ASSERT(m_position <= m_table->numberOfValues);
            while (m_position < m_table->numberOfValues && !m_table->values[m_position].m_key)
                ++m_position;
            ASSERT(m_position <= m_table->numberOfValues);
        }

        const HashTable* m_table;
        int m_position;
    };

    ConstIterator begin() const
    {
        return ConstIterator(this, 0);
    }
    ConstIterator end() const
    {
        return ConstIterator(this, numberOfValues);
    }
};

JS_EXPORT_PRIVATE bool setUpStaticFunctionSlot(ExecState*, const HashTableValue*, JSObject* thisObject, PropertyName, PropertySlot&);
JS_EXPORT_PRIVATE void reifyStaticAccessor(VM&, const HashTableValue&, JSObject& thisObject, PropertyName);

inline BuiltinGenerator HashTableValue::builtinAccessorGetterGenerator() const
{
    ASSERT(m_attributes & Accessor);
    ASSERT(m_attributes & Builtin);
    return reinterpret_cast<BuiltinGenerator>(m_values.value1);
}

inline BuiltinGenerator HashTableValue::builtinAccessorSetterGenerator() const
{
    ASSERT(m_attributes & Accessor);
    ASSERT(m_attributes & Builtin);
    return reinterpret_cast<BuiltinGenerator>(m_values.value2);
}

/**
 * This method does it all (looking in the hashtable, checking for function
 * overrides, creating the function or retrieving from cache, calling
 * getValueProperty in case of a non-function property, forwarding to parent if
 * unknown property).
 */
template <class ThisImp, class ParentImp>
inline bool getStaticPropertySlot(ExecState* exec, const HashTable& table, ThisImp* thisObj, PropertyName propertyName, PropertySlot& slot)
{
    if (ParentImp::getOwnPropertySlot(thisObj, exec, propertyName, slot))
        return true;

    if (thisObj->staticFunctionsReified())
        return false;

    auto* entry = table.entry(propertyName);
    if (!entry)
        return false;

    if (entry->attributes() & BuiltinOrFunctionOrAccessor)
        return setUpStaticFunctionSlot(exec, entry, thisObj, propertyName, slot);

    if (entry->attributes() & ConstantInteger) {
        slot.setValue(thisObj, attributesForStructure(entry->attributes()), jsNumber(entry->constantInteger()));
        return true;
    }

    slot.setCacheableCustom(thisObj, attributesForStructure(entry->attributes()), entry->propertyGetter());
    return true;
}

/**
 * Simplified version of getStaticPropertySlot in case there are only functions.
 * Using this instead of getStaticPropertySlot allows 'this' to avoid implementing
 * a dummy getValueProperty.
 */
template <class ParentImp>
inline bool getStaticFunctionSlot(ExecState* exec, const HashTable& table, JSObject* thisObj, PropertyName propertyName, PropertySlot& slot)
{
    if (ParentImp::getOwnPropertySlot(thisObj, exec, propertyName, slot))
        return true;

    if (thisObj->staticFunctionsReified())
        return false;

    auto* entry = table.entry(propertyName);
    if (!entry)
        return false;

    return setUpStaticFunctionSlot(exec, entry, thisObj, propertyName, slot);
}

/**
 * Simplified version of getStaticPropertySlot in case there are no functions, only "values".
 * Using this instead of getStaticPropertySlot removes the need for a FuncImp class.
 */
template <class ThisImp, class ParentImp>
inline bool getStaticValueSlot(ExecState* exec, const HashTable& table, ThisImp* thisObj, PropertyName propertyName, PropertySlot& slot)
{
    if (ParentImp::getOwnPropertySlot(thisObj, exec, propertyName, slot))
        return true;

    if (thisObj->staticFunctionsReified())
        return false;

    auto* entry = table.entry(propertyName);
    if (!entry)
        return false;

    ASSERT(!(entry->attributes() & BuiltinOrFunctionOrAccessor));

    if (entry->attributes() & ConstantInteger) {
        slot.setValue(thisObj, attributesForStructure(entry->attributes()), jsNumber(entry->constantInteger()));
        return true;
    }

    slot.setCacheableCustom(thisObj, attributesForStructure(entry->attributes()), entry->propertyGetter());
    return true;
}

// 'base' means the object holding the property (possibly in the prototype chain of the object put was called on).
// 'thisValue' is the object that put is being applied to (in the case of a proxy, the proxy target).
// 'slot.thisValue()' is the object the put was originally performed on (in the case of a proxy, the proxy itself).
inline void putEntry(ExecState* exec, const HashTableValue* entry, JSObject* base, JSObject* thisValue, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
    if (entry->attributes() & BuiltinOrFunction) {
        if (!(entry->attributes() & ReadOnly)) {
            // If this is a function put it as an override property.
            if (JSObject* thisObject = jsDynamicCast<JSObject*>(thisValue))
                thisObject->putDirect(exec->vm(), propertyName, value);
        } else if (slot.isStrictMode())
            throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
    } else if (entry->attributes() & Accessor) {
        if (slot.isStrictMode())
            throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
    } else if (!(entry->attributes() & ReadOnly)) {
        JSValue updateThisValue = entry->attributes() & CustomAccessor ? slot.thisValue() : JSValue(base);
        entry->propertyPutter()(exec, JSValue::encode(updateThisValue), JSValue::encode(value));
        if (entry->attributes() & CustomAccessor)
            slot.setCustomAccessor(base, entry->propertyPutter());
        else
            slot.setCustomValue(base, entry->propertyPutter());
    } else if (slot.isStrictMode())
        throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
}

/**
 * This one is for "put".
 * It looks up a hash entry for the property to be set.  If an entry
 * is found it sets the value and returns true, else it returns false.
 */
inline bool lookupPut(ExecState* exec, PropertyName propertyName, JSObject* base, JSValue value, const HashTable& table, PutPropertySlot& slot)
{
    const HashTableValue* entry = table.entry(propertyName);

    if (!entry)
        return false;

    putEntry(exec, entry, base, base, propertyName, value, slot);
    return true;
}

inline void reifyStaticProperty(VM& vm, const HashTableValue& value, JSObject& thisObj)
{
    if (!value.m_key)
        return;

    Identifier propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>(value.m_key), strlen(value.m_key));
    if (value.attributes() & Builtin) {
        if (value.attributes() & Accessor)
            reifyStaticAccessor(vm, value, thisObj, propertyName);
        else
            thisObj.putDirectBuiltinFunction(vm, thisObj.globalObject(), propertyName, value.builtinGenerator()(vm), attributesForStructure(value.attributes()));
        return;
    }

    if (value.attributes() & Function) {
        thisObj.putDirectNativeFunction(
            vm, thisObj.globalObject(), propertyName, value.functionLength(),
            value.function(), value.intrinsic(), attributesForStructure(value.attributes()));
        return;
    }

    if (value.attributes() & ConstantInteger) {
        thisObj.putDirect(vm, propertyName, jsNumber(value.constantInteger()), attributesForStructure(value.attributes()));
        return;
    }

    if (value.attributes() & Accessor) {
        reifyStaticAccessor(vm, value, thisObj, propertyName);
        return;
    }

    CustomGetterSetter* customGetterSetter = CustomGetterSetter::create(vm, value.propertyGetter(), value.propertyPutter());
    thisObj.putDirectCustomAccessor(vm, propertyName, customGetterSetter, attributesForStructure(value.attributes()));
}

template<unsigned numberOfValues>
inline void reifyStaticProperties(VM& vm, const HashTableValue (&values)[numberOfValues], JSObject& thisObj)
{
    BatchedTransitionOptimizer transitionOptimizer(vm, &thisObj);
    for (auto& value : values)
        reifyStaticProperty(vm, value, thisObj);
}

} // namespace JSC

#endif // Lookup_h
