/*
    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 "structunionscriptclass.hpp"

// lib
#include <datainformationwithchildren.hpp>
#include <topleveldatainformation.hpp>
#include <parserutils.hpp>
#include <scriptlogger.hpp>
#include <structureslogging.hpp>
// Qt
#include <QScriptContext>

StructUnionScriptClass::StructUnionScriptClass(QScriptEngine* engine, ScriptHandlerInfo* handlerInfo)
    : DefaultScriptClass(engine, handlerInfo, 1)
    , s_childCount(engine->toStringHandle(ParserStrings::PROPERTY_CHILD_COUNT())) // read-only
    , s_children(engine->toStringHandle(ParserStrings::PROPERTY_CHILDREN())) // write-only
{
    appendProperty(s_childCount, QScriptValue::ReadOnly | QScriptValue::Undeletable);

    mStructUnionPrototype = engine->newObject();
    mStructUnionPrototype.setProperty(QStringLiteral("toString"),
                                      engine->newFunction(StructUnion_proto_toString));
    mStructUnionPrototype.setProperty(QStringLiteral("setChildren"),
                                      engine->newFunction(StructUnion_proto_setChildren));
    mStructUnionPrototype.setProperty(QStringLiteral("child"),
                                      engine->newFunction(StructUnion_proto_child));
}

StructUnionScriptClass::~StructUnionScriptClass() = default;

bool StructUnionScriptClass::queryAdditionalProperty(const DataInformation* data, const QScriptString& name, QScriptClass::QueryFlags* flags, uint* id)
{
    // no need to modify flags since both read and write are handled
    if (name == s_childCount) {
        *flags &= ~HandlesWriteAccess;
        return true;
    }
    if (name == s_children) {
        *flags &= ~HandlesReadAccess;
        return true;
    }

    bool isArrayIndex;
    quint32 pos = name.toArrayIndex(&isArrayIndex);
    uint count = data->childCount();
    bool isValidChild = false;
    if (isArrayIndex && pos < count) {
        isValidChild = true;
    } else {
        // compare name, names that match special properties/functions will be
        // hidden since these were checked before
        QString objName = name.toString();
        for (uint i = 0; i < count; ++i) {
            if (objName == data->childAt(i)->name()) {
                isValidChild = true;
                pos = i;
                break;
            }
        }
    }
    if (isValidChild) {
        *id = pos + 1; // add 1 to distinguish from the default value of 0
        *flags &= ~HandlesWriteAccess; // writing is not yet supported
        return true;
    }
    return false; // not found
}

bool StructUnionScriptClass::additionalPropertyFlags(const DataInformation* data, const QScriptString& name, uint id, QScriptValue::PropertyFlags* flags)
{
    // no need to modify flags since both read and write are handled
    if (id != 0) {
        *flags |= QScriptValue::ReadOnly;
        return true;
    }
    // TODO is this necessary, will there be any way a child has no id set?
    // check named children
    QString objName = name.toString();
    uint count = data->childCount();
    for (uint i = 0; i < count; ++i) {
        DataInformation* const child = data->childAt(i);
        if (objName == child->name()) {
            *flags |= QScriptValue::ReadOnly;
            return true;
        }
    }

    return false;
}

QScriptValue StructUnionScriptClass::additionalProperty(const DataInformation* data, const QScriptString& name, uint id)
{
    const auto* const dataW = static_cast<const DataInformationWithChildren*>(data);

    if (id != 0) {
        quint32 pos = id - 1;
        if (pos >= data->childCount()) {
            dataW->logError().nospace() << "Attempting to access out of bounds child: index was " << pos
                              << ", maximum is " << (data->childCount() - 1) << ".";
            return engine()->currentContext()->throwError(QScriptContext::RangeError,
                                                          QStringLiteral("Attempting to access struct index %1, but length is %2").arg(
                                                              QString::number(pos), QString::number(data->childCount())));
        }
        return data->childAt(pos)->toScriptValue(engine(), handlerInfo());
    }
    if (name == s_childCount) {
        return dataW->childCount();
    }
    if (name == s_children) {
        dataW->logError() << "Attempting to read read-only property:" << s_children.toString();
        return engine()->undefinedValue();
    }
    // TODO is this necessary, will there be any way a child has no id set?
    // TODO testing seems to indicate this is not necessary, will leave it thought until I'm sure
    // check named children
    QString objName = name.toString();
    uint count = data->childCount();
    for (uint i = 0; i < count; ++i) {
        DataInformation* const child = data->childAt(i);
        if (objName == child->name()) {
            return child->toScriptValue(engine(), handlerInfo());
        }
    }
    return {};
}

bool StructUnionScriptClass::setAdditionalProperty(DataInformation* data, const QScriptString& name, uint, const QScriptValue& value)
{
    auto* const dataW = static_cast<DataInformationWithChildren*>(data);
    if (name == s_children) {
        dataW->setChildren(value);
        return true;
    }
    // TODO set children!!
    return false;
}

QScriptValue StructUnionScriptClass::prototype() const
{
    return mStructUnionPrototype;
}

QScriptValue StructUnionScriptClass::StructUnion_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();
    }
    return data->typeName(); // TODO better toString
}

QScriptValue StructUnionScriptClass::StructUnion_proto_child(QScriptContext* ctx, QScriptEngine* eng)
{
    if (ctx->argumentCount() < 1) {
        std::ignore = ctx->throwError(QScriptContext::RangeError,
                                      QStringLiteral("(struct/union).child(name) needs at least one argument"));
        return eng->undefinedValue();
    }
    QScriptValue arg = ctx->argument(0);
    if (!arg.isString()) {
        std::ignore = ctx->throwError(QScriptContext::TypeError,
                                      QStringLiteral("(struct/union).child(name) argument has to be a string"));
        return QScriptValue::UndefinedValue;
    }
    DataInformation* const data = toDataInformation(ctx->thisObject());
    if (!data) {
        qCWarning(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "could not cast data";
        return eng->undefinedValue();
    }
    uint count = data->childCount();
    QString name = arg.toString();
    for (uint i = 0; i < count; ++i) {
        DataInformation* const child = data->childAt(i);
        if (child->name() == name) {
            return child->toScriptValue(eng, data->topLevelDataInformation()->scriptHandler()->handlerInfo());
        }
    }

    return eng->nullValue();
}

QScriptValue StructUnionScriptClass::StructUnion_proto_setChildren(QScriptContext* ctx, QScriptEngine* eng)
{
    if (ctx->argumentCount() < 1) {
        return ctx->throwError(QScriptContext::RangeError,
                               QStringLiteral("(struct/union).child(children) needs one argument"));
    }
    DataInformation* const data = toDataInformation(ctx->thisObject());
    if (!data) {
        qCWarning(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "could not cast data";
        return eng->undefinedValue();
    }
    auto* const dataW = static_cast<DataInformationWithChildren*>(data);
    dataW->setChildren(ctx->argument(0));
    return eng->undefinedValue();
}
