// 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
#ifndef QV4VALUE_P_H
#define QV4VALUE_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <limits.h>
#include <cmath>

#include <QtCore/QString>
#include "qv4global_p.h"
#include <private/qv4heap_p.h>
#include <private/qv4internalclass_p.h>
#include <private/qv4staticvalue_p.h>

#include <private/qnumeric_p.h>
#include <private/qv4calldata_p.h>

QT_BEGIN_NAMESPACE

namespace QV4 {

namespace Heap {
    struct Base;
}

struct Q_QML_PRIVATE_EXPORT Value : public StaticValue
{
    using ManagedPtr = Managed *;

    Value() = default;
    constexpr Value(quint64 val) : StaticValue(val) {}

    static constexpr Value fromStaticValue(StaticValue staticValue)
    {
        return {staticValue._val};
    }

    inline bool isString() const;
    inline bool isStringOrSymbol() const;
    inline bool isSymbol() const;
    inline bool isObject() const;
    inline bool isFunctionObject() const;

    QML_NEARLY_ALWAYS_INLINE String *stringValue() const {
        if (!isString())
            return nullptr;
        return reinterpret_cast<String *>(const_cast<Value *>(this));
    }
    QML_NEARLY_ALWAYS_INLINE StringOrSymbol *stringOrSymbolValue() const {
        if (!isStringOrSymbol())
            return nullptr;
        return reinterpret_cast<StringOrSymbol *>(const_cast<Value *>(this));
    }
    QML_NEARLY_ALWAYS_INLINE Symbol *symbolValue() const {
        if (!isSymbol())
            return nullptr;
        return reinterpret_cast<Symbol *>(const_cast<Value *>(this));
    }
    QML_NEARLY_ALWAYS_INLINE Object *objectValue() const {
        if (!isObject())
            return nullptr;
        return reinterpret_cast<Object*>(const_cast<Value *>(this));
    }
    QML_NEARLY_ALWAYS_INLINE ManagedPtr managed() const {
        if (!isManaged())
            return nullptr;
        return reinterpret_cast<Managed*>(const_cast<Value *>(this));
    }
    QML_NEARLY_ALWAYS_INLINE Value::HeapBasePtr heapObject() const {
        return isManagedOrUndefined() ? m() : nullptr;
    }

    static inline Value fromHeapObject(HeapBasePtr m)
    {
        Value v;
        v.setM(m);
        return v;
    }

    int toUInt16() const;
    inline int toInt32() const;
    inline unsigned int toUInt32() const;
    qint64 toLength() const;
    inline qint64 toIndex() const;

    bool toBoolean() const {
        if (integerCompatible())
            return static_cast<bool>(int_32());

        return toBooleanImpl(*this);
    }
    static bool toBooleanImpl(Value val);
    double toInteger() const;
    inline ReturnedValue convertedToNumber() const;
    inline double toNumber() const;
    static double toNumberImpl(Value v);
    double toNumberImpl() const { return toNumberImpl(*this); }

    QString toQStringNoThrow() const;
    QString toQString() const;
    QString toQString(bool *ok) const;

    Heap::String *toString(ExecutionEngine *e) const {
        if (isString())
            return reinterpret_cast<Heap::String *>(m());
        return toString(e, *this);
    }
    QV4::PropertyKey toPropertyKey(ExecutionEngine *e) const;

    static Heap::String *toString(ExecutionEngine *e, Value val);
    Heap::Object *toObject(ExecutionEngine *e) const {
        if (isObject())
            return reinterpret_cast<Heap::Object *>(m());
        return toObject(e, *this);
    }
    static Heap::Object *toObject(ExecutionEngine *e, Value val);

    inline bool isPrimitive() const;

    template <typename T>
    const T *as() const {
        if (!isManaged())
            return nullptr;

        Q_ASSERT(m()->internalClass->vtable);
#if !defined(QT_NO_QOBJECT_CHECK)
        static_cast<const T *>(this)->qt_check_for_QMANAGED_macro(static_cast<const T *>(this));
#endif
        const VTable *vt = m()->internalClass->vtable;
        while (vt) {
            if (vt == T::staticVTable())
                return static_cast<const T *>(this);
            vt = vt->parent;
        }
        return nullptr;
    }
    template <typename T>
    T *as() {
        if (isManaged())
            return const_cast<T *>(const_cast<const Value *>(this)->as<T>());
        else
            return nullptr;
    }

    template<typename T> inline T *cast() {
        return static_cast<T *>(managed());
    }
    template<typename T> inline const T *cast() const {
        return static_cast<const T *>(managed());
    }

    uint asArrayLength(bool *ok) const;

    static constexpr Value fromReturnedValue(ReturnedValue val)
    {
        return fromStaticValue(StaticValue::fromReturnedValue(val));
    }

    // As per ES specs
    bool sameValue(Value other) const;
    bool sameValueZero(Value other) const;

    inline void mark(MarkStack *markStack);

    static double toInteger(double d) { return StaticValue::toInteger(d); }
    static int toInt32(double d) { return StaticValue::toInt32(d); }
    static unsigned int toUInt32(double d) { return StaticValue::toUInt32(d); }
    inline static constexpr Value emptyValue()
    {
        return fromStaticValue(StaticValue::emptyValue());
    }
    static inline constexpr Value fromBoolean(bool b)
    {
        return fromStaticValue(StaticValue::fromBoolean(b));
    }
    static inline constexpr Value fromInt32(int i)
    {
        return fromStaticValue(StaticValue::fromInt32(i));
    }
    inline static constexpr Value undefinedValue()
    {
        return fromStaticValue(StaticValue::undefinedValue());
    }
    static inline constexpr Value nullValue()
    {
        return fromStaticValue(StaticValue::nullValue());
    }
    static inline Value fromDouble(double d)
    {
        return fromStaticValue(StaticValue::fromDouble(d));
    }
    static inline Value fromUInt32(uint i)
    {
        return fromStaticValue(StaticValue::fromUInt32(i));
    }

    Value &operator =(const ScopedValue &v);
    Value &operator=(ReturnedValue v)
    {
        StaticValue::operator=(v);
        return *this;
    }
    Value &operator=(ManagedPtr m) {
        if (!m) {
            setM(nullptr);
        } else {
            _val = reinterpret_cast<Value *>(m)->_val;
        }
        return *this;
    }
    Value &operator=(HeapBasePtr o) {
        setM(o);
        return *this;
    }

    template<typename T>
    Value &operator=(const Scoped<T> &t);
};
Q_STATIC_ASSERT(std::is_trivial_v<Value>);
Q_STATIC_ASSERT(sizeof(Value) == sizeof(StaticValue));

template<>
inline StaticValue &StaticValue::operator=<Value>(const Value &value)
{
    _val = value._val;
    return *this;
}

template<typename Managed>
inline StaticValue &StaticValue::operator=(const Managed &m)
{
    *static_cast<Value *>(this) = m;
    return *this;
}

template<>
inline Value &StaticValue::asValue<Value>()
{
    return *static_cast<Value *>(this);
}

template<>
inline const Value &StaticValue::asValue<Value>() const
{
    return *static_cast<const Value *>(this);
}

template<>
inline Value *CallData::argValues<Value>()
{
    return static_cast<Value *>(static_cast<StaticValue *>(args));
}

template<>
inline const Value *CallData::argValues<Value>() const
{
    return static_cast<const Value *>(static_cast<const StaticValue *>(args));
}

template<typename HeapBase>
inline Encode::Encode(HeapBase *o)
{
    val = Value::fromHeapObject(o).asReturnedValue();
}

inline void Value::mark(MarkStack *markStack)
{
    HeapBasePtr o = heapObject();
    if (o)
        o->mark(markStack);
}

inline bool Value::isString() const
{
    HeapBasePtr b = heapObject();
    return b && b->internalClass->vtable->isString;
}

bool Value::isStringOrSymbol() const
{
    HeapBasePtr b = heapObject();
    return b && b->internalClass->vtable->isStringOrSymbol;
}

bool Value::isSymbol() const
{
    HeapBasePtr b = heapObject();
    return b && b->internalClass->vtable->isStringOrSymbol && !b->internalClass->vtable->isString;
}

inline bool Value::isObject() const

{
    HeapBasePtr b = heapObject();
    return b && b->internalClass->vtable->isObject;
}

inline bool Value::isFunctionObject() const
{
    HeapBasePtr b = heapObject();
    return b && b->internalClass->vtable->isFunctionObject;
}

inline bool Value::isPrimitive() const
{
    return !isObject();
}

inline double Value::toNumber() const
{
    if (isInteger())
        return int_32();
    if (isDouble())
        return doubleValue();
    return toNumberImpl();
}

inline ReturnedValue Value::convertedToNumber() const
{
    if (isInteger() || isDouble())
        return asReturnedValue();
    Value v;
    v.setDouble(toNumberImpl());
    return v.asReturnedValue();
}

inline
ReturnedValue Heap::Base::asReturnedValue() const
{
    return Value::fromHeapObject(const_cast<Value::HeapBasePtr>(this)).asReturnedValue();
}

// For source compat with older code in other modules
using Primitive = Value;

template<typename T>
ReturnedValue value_convert(ExecutionEngine *e, const Value &v);

inline int Value::toInt32() const
{
    if (Q_LIKELY(integerCompatible()))
        return int_32();

    if (Q_LIKELY(isDouble()))
        return QJSNumberCoercion::toInteger(doubleValue());

    return QJSNumberCoercion::toInteger(toNumberImpl());
}

inline unsigned int Value::toUInt32() const
{
    return static_cast<unsigned int>(toInt32());
}

inline qint64 Value::toLength() const
{
    if (Q_LIKELY(integerCompatible()))
        return int_32() < 0 ? 0 : int_32();
    double i = Value::toInteger(isDouble() ? doubleValue() : toNumberImpl());
    if (i <= 0)
        return 0;
    if (i > (static_cast<qint64>(1) << 53) - 1)
        return (static_cast<qint64>(1) << 53) - 1;
    return static_cast<qint64>(i);
}

inline qint64 Value::toIndex() const
{
    qint64 idx;
    if (Q_LIKELY(integerCompatible())) {
        idx = int_32();
    } else {
        idx = static_cast<qint64>(Value::toInteger(isDouble() ? doubleValue() : toNumberImpl()));
    }
    if (idx > (static_cast<qint64>(1) << 53) - 1)
        idx = -1;
    return idx;
}

inline double Value::toInteger() const
{
    if (integerCompatible())
        return int_32();

    return Value::toInteger(isDouble() ? doubleValue() : toNumberImpl());
}


template <size_t o>
struct HeapValue : Value {
    static constexpr size_t offset = o;
    HeapBasePtr base() {
        HeapBasePtr base = reinterpret_cast<HeapBasePtr>(this) - (offset/sizeof(Heap::Base));
        Q_ASSERT(base->inUse());
        return base;
    }

    void set(EngineBase *e, const Value &newVal) {
        WriteBarrier::write(e, base(), data_ptr(), newVal.asReturnedValue());
    }
    void set(EngineBase *e, HeapBasePtr b) {
        WriteBarrier::write(e, base(), data_ptr(), b->asReturnedValue());
    }
};

template <size_t o>
struct ValueArray {
    static constexpr size_t offset = o;
    uint size;
    uint alloc;
    Value values[1];

    Value::HeapBasePtr base() {
        Value::HeapBasePtr base = reinterpret_cast<Value::HeapBasePtr>(this)
                - (offset/sizeof(Heap::Base));
        Q_ASSERT(base->inUse());
        return base;
    }

    void set(EngineBase *e, uint index, Value v) {
        WriteBarrier::write(e, base(), values[index].data_ptr(), v.asReturnedValue());
    }
    void set(EngineBase *e, uint index, Value::HeapBasePtr b) {
        WriteBarrier::write(e, base(), values[index].data_ptr(), Value::fromHeapObject(b).asReturnedValue());
    }
    inline const Value &operator[] (uint index) const {
        Q_ASSERT(index < alloc);
        return values[index];
    }
    inline const Value *data() const {
        return values;
    }

    void mark(MarkStack *markStack) {
        for (Value *v = values, *end = values + alloc; v < end; ++v)
            v->mark(markStack);
    }
};

// It's really important that the offset of values in this structure is
// constant across all architecture,  otherwise JIT cross-compiled code will
// have wrong offsets between host and target.
Q_STATIC_ASSERT(offsetof(ValueArray<0>, values) == 8);

class OptionalReturnedValue {
    ReturnedValue value;
public:

    OptionalReturnedValue() : value(Value::emptyValue().asReturnedValue()) {}
    explicit OptionalReturnedValue(ReturnedValue v)
        : value(v)
    {
        Q_ASSERT(!Value::fromReturnedValue(v).isEmpty());
    }

    ReturnedValue operator->() const { return value; }
    ReturnedValue operator*() const { return value; }
    explicit operator bool() const { return !Value::fromReturnedValue(value).isEmpty(); }
};

}

QT_END_NAMESPACE

#endif // QV4VALUE_DEF_P_H
