/*
    SPDX-FileCopyrightText: 2011, 2012 Alex Richardson <alex.richardson@gmx.de>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/

#include "primitivescriptclass.hpp"

// lib
#include <primitivedatainformation.hpp>
#include <parserutils.hpp>
#include <allprimitivetypes.hpp>
#include <structureslogging.hpp>
// Qt
#include <QScriptContext>

PrimitiveScriptClass::PrimitiveScriptClass(QScriptEngine* engine, ScriptHandlerInfo* handlerInfo, int propertiesSize)
    : DefaultScriptClass(engine, handlerInfo, propertiesSize + 20)
    , s_value(engine->toStringHandle(QStringLiteral("value")))
    , s_type(engine->toStringHandle(QStringLiteral("type")))
    , s_bool(engine->toStringHandle(QStringLiteral("bool")))
    , s_char(engine->toStringHandle(QStringLiteral("char")))
    , s_double(engine->toStringHandle(QStringLiteral("double")))
    , s_float(engine->toStringHandle(QStringLiteral("float")))
    , s_int(engine->toStringHandle(QStringLiteral("int")))
    , s_int8(engine->toStringHandle(QStringLiteral("int8")))
    , s_int16(engine->toStringHandle(QStringLiteral("int16")))
    , s_int32(engine->toStringHandle(QStringLiteral("int32")))
    , s_int64low32(engine->toStringHandle(QStringLiteral("int64low32")))
    , s_int64high32(engine->toStringHandle(QStringLiteral("int64high32")))
    , s_int64(engine->toStringHandle(QStringLiteral("int64")))
    , s_uint(engine->toStringHandle(QStringLiteral("uint")))
    , s_uint8(engine->toStringHandle(QStringLiteral("uint8")))
    , s_uint16(engine->toStringHandle(QStringLiteral("uint16")))
    , s_uint32(engine->toStringHandle(QStringLiteral("uint32")))
    , s_uint64low32(engine->toStringHandle(QStringLiteral("uint64low32")))
    , s_uint64high32(engine->toStringHandle(QStringLiteral("uint64high32")))
    , s_uint64(engine->toStringHandle(QStringLiteral("uint64")))
{
    appendProperty(s_value, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_type, QScriptValue::ReadOnly | QScriptValue::Undeletable);

    appendProperty(s_bool, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_char, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_double, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_float, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_int, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_int8, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_int16, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_int32, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_int64low32, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_int64high32, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_int64, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_uint, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_uint8, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_uint16, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_uint32, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_uint64low32, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_uint64high32, QScriptValue::ReadOnly | QScriptValue::Undeletable);
    appendProperty(s_uint64, QScriptValue::ReadOnly | QScriptValue::Undeletable);

    mPrimitivePrototype = engine->newObject();
    mPrimitivePrototype.setProperty(QStringLiteral("toString"), engine->newFunction(Primitive_proto_toString));
}

PrimitiveScriptClass::~PrimitiveScriptClass() = default;

bool PrimitiveScriptClass::queryAdditionalProperty(const DataInformation* data, const QScriptString& name, QScriptClass::QueryFlags* flags, uint*)
{
    Q_UNUSED(data)
    // TODO assign ids to improve speed? can't have children, so it won't conflict
    // no need to modify flags since both read and write are handled
    if (name == s_value || name == s_type) {
        *flags &= ~HandlesWriteAccess;
        return true;
    }
    if (name == s_bool || name == s_char || name == s_int || name == s_uint || name == s_float
               || name == s_double || name == s_int64 || name == s_uint64 || name == s_int64low32
               || name == s_int64high32 || name == s_uint64low32 || name == s_uint64high32 || name == s_int8
               || name == s_int16 || name == s_int32 || name == s_uint8 || name == s_uint16 || name == s_uint32) {
        *flags &= ~HandlesWriteAccess;
        return true;
    }
    return false;
}

bool PrimitiveScriptClass::additionalPropertyFlags(const DataInformation*, const QScriptString&, uint, QScriptValue::PropertyFlags*)
{
    return false;
}

QScriptValue PrimitiveScriptClass::additionalProperty(const DataInformation* data, const QScriptString& name, uint)
{
    const PrimitiveDataInformation* const pData = data->asPrimitive();

    if (name == s_value) {
        if (pData->wasAbleToRead()) {
            return pData->valueAsQScriptValue();
        }
        QScriptValue callee = engine()->currentContext()->thisObject();
        DataInformation* const cause = toDataInformation(callee);
        if (cause) {
            pData->logError() << "Attempting to read from uninitialized value. Callee was:" << cause->fullObjectPath();
        } else {
            pData->logError() << "Attempting to read from uninitialized value. Callee could not be determined.";
        }
        return engine()->undefinedValue();
    }
    if (name == s_type) {
        // bitfields are handled by own scriptclass and NotPrimitive indicates an error
        Q_ASSERT(!pData->isBitfield());
        Q_ASSERT(pData->type() != PrimitiveDataType::Invalid);
        return PrimitiveType::standardTypeName(pData->type());
    }

    AllPrimitiveTypes value = pData->value();
    if (name == s_bool) {
        return value.value<quint64>() != 0;
    }
    if (name == s_char) {
        return QString(value.value<quint8>() > 127 ? QChar::ReplacementCharacter : QChar(value.value<qint8>(), 0));
    }
    if (name == s_float) {
        return value.value<float>();
    }
    if (name == s_double) {
        return value.value<double>();
    }
    if (name == s_int || name == s_int32 || name == s_int64low32) {
        return value.value<qint32>();
    }
    if (name == s_uint || name == s_uint32 || name == s_uint64low32) {
        return value.value<quint32>();
    }
    if (name == s_int64) {
        return QString::number(value.value<qint64>());
    }
    if (name == s_uint64) {
        return QString::number(value.value<quint64>());
    }
    if (name == s_int64high32) {
        return qint32(value.value<qint64>() >> 32);
    }
    if (name == s_uint64high32) {
        return quint32(value.value<quint64>() >> 32);
    }
    if (name == s_int8) {
        return qint32(value.value<qint8>());
    }
    if (name == s_int16) {
        return qint32(value.value<qint16>());
    }
    if (name == s_uint8) {
        return quint32(value.value<quint8>());
    }
    if (name == s_uint16) {
        return quint32(value.value<quint16>());
    }

    return {};
}

bool PrimitiveScriptClass::setAdditionalProperty(DataInformation* data, const QScriptString& name, uint, const QScriptValue& value)
{
    Q_UNUSED(value)
    Q_UNUSED(data)
    Q_UNUSED(name)
    // TODO allow changing type
    return false;
}

QScriptValue PrimitiveScriptClass::prototype() const
{
    return mPrimitivePrototype;
}

QScriptValue PrimitiveScriptClass::Primitive_proto_toString(QScriptContext* ctx, QScriptEngine* eng)
{
    DataInformation* const data = toDataInformation(ctx->thisObject());
    if (!data) {
        qCWarning(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "could not cast data";
        return eng->undefinedValue();
    }
    // this might allow proper comparison between values without having to call .value
    return data->wasAbleToRead() ? data->valueString() : eng->undefinedValue();
}
