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

// lib
#include "arraydatainformation.hpp"
#include <primitivedatainformation.hpp>
#include <scriptlogger.hpp>
#include <safereference.hpp>
#include <structureslogging.hpp>
// Okteta core
#include <Okteta/AbstractByteArrayModel>
// KF
#include <KLocalizedString>
// Std
#include <algorithm>
#include <limits>
#include <utility>

template <PrimitiveDataType type>
inline PrimitiveArrayData<type>::PrimitiveArrayData(unsigned int supportedLength, unsigned int length,
                                                    std::unique_ptr<PrimitiveDataInformation>&& childType,
                                                    ArrayDataInformation* parent)
    : AbstractArrayData(std::move(childType), parent)
    , m_length(length)
    , mDummy(parent)
{
    Q_ASSERT(static_cast<PrimitiveDataInformation*>(this->childType())->type() == type);
    mData.resize(supportedLength);
}

template <PrimitiveDataType type>
qint64 PrimitiveArrayData<type>::readData(const Okteta::AbstractByteArrayModel* input, Okteta::Address address,
                                          BitCount64 bitsRemaining)
{
    Q_ASSERT(bitsRemaining % 8 == 0);
    const uint oldNumReadValues = mNumReadValues;
    if (this->length() == 0) {
        this->mNumReadValues = 0;
        if (oldNumReadValues != mNumReadValues) {
            mParent->topLevelDataInformation()->setChildDataChanged();
        }
        return 0; // no need to read anything
    }
    // integer division -> gives us the desired result, limited by the number of items in this array
    const quint64 maxRemaining = (bitsRemaining / 8) / sizeof(T);
    // since its 64 bits may be larger than a 32 bit value and have all lower 32 bits as zero
    // therefore we use std::numeric_limits::max()
    quint32 maxRemaining32 = (maxRemaining > std::numeric_limits<quint32>::max()
                              ? std::numeric_limits<quint32>::max() : quint32(maxRemaining));
    const quint32 maxNumItems = std::min(this->supportedLength(), maxRemaining32);
    this->mNumReadValues = maxNumItems;
    if (maxNumItems == 0) {
        if (oldNumReadValues != mNumReadValues) {
            mParent->topLevelDataInformation()->setChildDataChanged();
        }
        return -1; // reached EOF
    }
    const QSysInfo::Endian byteOrder = mChildType->effectiveByteOrder();
    const bool isDataDifferent = (byteOrder == QSysInfo::ByteOrder) ?
        this->readDataNativeOrder(maxNumItems, input, address) :
        this->readDataNonNativeOrder(maxNumItems, input, address);
    if (isDataDifferent || (oldNumReadValues != mNumReadValues)) {
        mParent->topLevelDataInformation()->setChildDataChanged();
    }

    const quint32 maxFullNumItems = qMin(this->length(), maxRemaining32);
    return maxFullNumItems * sizeof(T) * 8;
}

// TODO: add a compare API to AbstractByteArrayModel
// for this use-case also some setandreturnifchanged API would be useful,
// to save the double data access
static
bool isDataEqualNativeOrder(const Okteta::AbstractByteArrayModel* byteArrayModel,
                            Okteta::Address address, const Okteta::Size size,
                            const Okteta::Byte* data)
{
    for (int i = 0; i < size; ++i, ++address) {
        if (byteArrayModel->byte(address) != data[i]) {
            return false;
        }
    }
    return true;
}

template <PrimitiveDataType type>
bool PrimitiveArrayData<type>::readDataNativeOrder(uint numItems,
                                                   const Okteta::AbstractByteArrayModel* input,
                                                   Okteta::Address address)
{
    Q_ASSERT(numItems <= supportedLength());
    const Okteta::Size numBytes = numItems * sizeof(T);
    Q_ASSERT(input->size() >= numBytes + address);
    auto* const vectorBytes = reinterpret_cast<Okteta::Byte*>(this->mData.data());
    const bool isDataDifferent = !isDataEqualNativeOrder(input, address, numBytes, vectorBytes);
    if (isDataDifferent) {
        const Okteta::Size numCopied = input->copyTo(vectorBytes, address, numItems * sizeof(T));
        Q_ASSERT(numCopied == numBytes);
        Q_UNUSED(numCopied)
    }
    return isDataDifferent;
}

// TODO: see isDataEqualNativeOrder
static
bool isDataEqualNonNativeOrder(const Okteta::AbstractByteArrayModel* byteArrayModel,
                               Okteta::Address address, const Okteta::Size size,
                               const Okteta::Byte* data,
                               int typeSize)
{
    for (int itemOffs = 0; itemOffs < size; itemOffs += typeSize) {
        // the compiler should unroll this loop
        for (int byte = 0; byte < typeSize; ++byte) {
            if (byteArrayModel->byte(address + itemOffs + (typeSize - byte - 1)) != data[itemOffs + byte]) {
                return false;
            }
        }
    }
    return true;
}

template <PrimitiveDataType type>
bool PrimitiveArrayData<type>::readDataNonNativeOrder(uint numItems,
                                                      const Okteta::AbstractByteArrayModel* input,
                                                      Okteta::Address address)
{
    Q_ASSERT(numItems <= supportedLength());
    const uint numBytes = numItems * sizeof(T);
    Q_ASSERT(uint(input->size()) >= numBytes + address);
    auto* const vectorBytes = reinterpret_cast<Okteta::Byte*>(this->mData.data());
    const bool isDataDifferent = !isDataEqualNonNativeOrder(input, address, numBytes, vectorBytes, sizeof(T));
    if (isDataDifferent) {
        for (uint itemOffs = 0; itemOffs < numBytes; itemOffs += sizeof(T)) {
            // the compiler should unroll this loop
            for (uint byte = 0; byte < sizeof(T); ++byte) {
                vectorBytes[itemOffs + byte] = input->byte(address + itemOffs + (sizeof(T) - byte - 1));
            }
        }
    }
    return isDataDifferent;
}

template <PrimitiveDataType type>
bool PrimitiveArrayData<type>::setChildData(uint row, const QVariant& value, Okteta::AbstractByteArrayModel* out,
                                            Okteta::Address address, BitCount64 bitsRemaining)
{
    Q_ASSERT(row < supportedLength());
    Q_ASSERT(value.isValid());
    Q_ASSERT(bitsRemaining % 8 == 0);
    if (sizeof(T) * 8 > bitsRemaining) {
        this->mParent->logInfo().nospace() << "Not enough bits remaining ("
                                 << bitsRemaining << "), need " << (sizeof(T) * 8);
        return false;
    }
    const QSysInfo::Endian byteOrder = mChildType->effectiveByteOrder();
    bool ok = false;
    T convertedVal = DisplayClass::fromVariant(value, &ok);
    if (!ok) {
        this->mParent->logWarn().nospace() << "Could not convert '" << value << "' to '" << type << "'.";
        return false;
    }
    qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES)
        << AbstractArrayData::mParent->fullObjectPath() << "setting index" << row << "to"
        << value << "(= " << convertedVal << ")";
    this->mData[row] = convertedVal;
    this->writeOneItem(convertedVal, address, out, byteOrder);
    return true;
}

template <PrimitiveDataType type>
void PrimitiveArrayData<type>::writeOneItem(T value, Okteta::Address addr,
                                            Okteta::AbstractByteArrayModel* out, QSysInfo::Endian endianness)
{
    if (endianness == QSysInfo::LittleEndian) {
        for (uint i = 0; i < sizeof(T); ++i) {
            // compiler should be smart enough not to create a loop
            quint8 val = (quint64(value) & (quint64(0xff) << (8 * i))) >> (8 * i);
            out->setByte(addr + i, val);
        }
    } else {
        for (uint i = 0; i < sizeof(T); ++i) {
            // compiler should be smart enough not to create a loop
            const int maskBitOffset = 8 * (sizeof(T) - i - 1);
            quint8 val = (quint64(value) & (quint64(0xff) << maskBitOffset)) >> maskBitOffset;
            out->setByte(addr + i, val);
        }
    }
}

template <>
void PrimitiveArrayData<PrimitiveDataType::Float>::writeOneItem(float value, Okteta::Address addr,
                                                                Okteta::AbstractByteArrayModel* out,
                                                                QSysInfo::Endian endianness)
{
    Q_ASSERT(sizeof(float) == sizeof(quint32));
    const union {
        float floatVal;
        quint32 intVal;
    } un = {value};
    PrimitiveArrayData<PrimitiveDataType::UInt32>::writeOneItem(un.intVal, addr, out, endianness);
}

template <>
void PrimitiveArrayData<PrimitiveDataType::Double>::writeOneItem(double value, Okteta::Address addr,
                                                                 Okteta::AbstractByteArrayModel* out,
                                                                 QSysInfo::Endian endianness)
{
    Q_ASSERT(sizeof(double) == sizeof(quint64));
    const union {
        double doubleVal;
        quint64 intVal;
    } un = {value};
    PrimitiveArrayData<PrimitiveDataType::UInt64>::writeOneItem(un.intVal, addr, out, endianness);
}

// TODO: make more obvious what the difference to childAt() is
template <PrimitiveDataType type>
void PrimitiveArrayData<type>::activateIndex(uint index)
{
    Q_ASSERT(index < supportedLength());
    // invalidate all previous references
    SafeReferenceHolder::instance.invalidateAll(mChildType.get());

    const bool wasAbleToRead = (mNumReadValues > index);
    const QString name = QString::number(index);

    mChildType->asPrimitive()->setValue(mData[index]);
    mChildType->setName(name);
    mChildType->mWasAbleToRead = wasAbleToRead;

    mDummy.setDummyIndex(index);
    mDummy.setName(name);
    mDummy.setWasAbleToRead(wasAbleToRead);
}

template <PrimitiveDataType type>
QVariant PrimitiveArrayData<type>::dataAt(uint index, int column, int role)
{
    Q_ASSERT(index < supportedLength());
    if (role == Qt::DisplayRole) {
        if (column == DataInformation::ColumnName) {
            return nameAt(index);
        }
        if (column == DataInformation::ColumnType) {
            return mChildType->typeName();
        }
        if (column == DataInformation::ColumnValue) {
            return valueStringAt(index);
        }
        if (column == DataInformation::ColumnSize) {
            return sizeStringAt(index);
        }
    }
    if (role == Qt::ToolTipRole) {
        return DataInformation::tooltipString(nameAt(index), valueStringAt(index),
                                              mChildType->typeName(), sizeStringAt(index));
    }

    if (column == DataInformation::ColumnValue && uint(index) >= this->mNumReadValues) {
        return DataInformation::eofReachedData(role);
    }
    return {};
}

template <PrimitiveDataType type>
QString PrimitiveArrayData<type>::nameAt(uint index) const
{
    return QString(QLatin1Char('[') + QString::number(index) + QLatin1Char(']'));
}

template <PrimitiveDataType type>
QString PrimitiveArrayData<type>::valueStringAt(uint index)
{
    // if we are outside the valid range
    if (index >= this->mNumReadValues) {
        return DataInformation::eofReachedData(Qt::DisplayRole).toString();
    }
    if (Q_UNLIKELY(mChildType->toStringFunction().isValid())) {
        activateIndex(index);
        return mChildType->valueString();
    }
    return DisplayClass::staticValueString(mData[index]);
}

template <PrimitiveDataType type>
QString PrimitiveArrayData<type>::sizeStringAt(uint index) const
{
    return DataInformation::sizeString(sizeAt(index));
}

template <PrimitiveDataType type>
QString PrimitiveArrayData<type>::typeName() const
{
    return QString(mChildType->typeName() + QLatin1Char('[')
                   + QString::number(this->length()) + QLatin1Char(']'));
}

template <PrimitiveDataType type>
QString PrimitiveArrayData<type>::valueString() const
{
    return {};
}

template <PrimitiveDataType type>
int PrimitiveArrayData<type>::indexOf(const DataInformation* data) const
{
    if (data == &mDummy) {
        return this->mDummy.dummyIndex();
    }
    if (data == mChildType.get()) {
        return this->mDummy.dummyIndex();
    }
    Q_ASSERT_X(false, "PrimitiveArrayData::indexOf", "Logic error, should never be reached");
    return -1;
}

template <PrimitiveDataType type>
QScriptValue PrimitiveArrayData<type>::toScriptValue(uint index, QScriptEngine* engine, ScriptHandlerInfo* handlerInfo)
{
    activateIndex(index);
    return mChildType->toScriptValue(engine, handlerInfo);
}

template <PrimitiveDataType type>
QWidget* PrimitiveArrayData<type>::createChildEditWidget(uint index, QWidget* parent) const
{
    Q_ASSERT(index < supportedLength());
    Q_UNUSED(index)
    return DisplayClass::staticCreateEditWidget(parent);
}

template <PrimitiveDataType type>
QVariant PrimitiveArrayData<type>::dataFromChildWidget(uint index, const QWidget* w) const
{
    Q_ASSERT(index < supportedLength());
    Q_UNUSED(index)
    return DisplayClass::staticDataFromWidget(w);
}

template <PrimitiveDataType type>
void PrimitiveArrayData<type>::setChildWidgetData(uint index, QWidget* w) const
{
    Q_ASSERT(index < supportedLength());
    Q_UNUSED(index)
    DisplayClass::staticSetWidgetData(mData[index], w);
}

template <>
QString PrimitiveArrayData<PrimitiveDataType::Char>::valueString() const
{
    QString result;
    const uint size = supportedLength();
    result.reserve(size);
    for (uint index = 0; index < size; ++index) {
        QChar qchar(mData[index], 0);
        if (!qchar.isPrint()) {
            qchar = QChar(QChar::ReplacementCharacter);
        }
        result.append(qchar);
    }
    return result;
}


// now instantiate all the template instances
template class PrimitiveArrayData<PrimitiveDataType::Bool8>;
template class PrimitiveArrayData<PrimitiveDataType::Bool16>;
template class PrimitiveArrayData<PrimitiveDataType::Bool32>;
template class PrimitiveArrayData<PrimitiveDataType::Bool64>;
template class PrimitiveArrayData<PrimitiveDataType::Int8>;
template class PrimitiveArrayData<PrimitiveDataType::Int16>;
template class PrimitiveArrayData<PrimitiveDataType::Int32>;
template class PrimitiveArrayData<PrimitiveDataType::Int64>;
template class PrimitiveArrayData<PrimitiveDataType::UInt8>;
template class PrimitiveArrayData<PrimitiveDataType::UInt16>;
template class PrimitiveArrayData<PrimitiveDataType::UInt32>;
template class PrimitiveArrayData<PrimitiveDataType::UInt64>;
template class PrimitiveArrayData<PrimitiveDataType::Char>;
template class PrimitiveArrayData<PrimitiveDataType::Float>;
template class PrimitiveArrayData<PrimitiveDataType::Double>;
