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

    SPDX-License-Identifier: LGPL-2.1-or-later
*/

// test
#include "testutils.hpp"
// sut
#include <uniondatainformation.hpp>
#include <topleveldatainformation.hpp>
#include <primitivefactory.hpp>
#include <unsignedbitfielddatainformation.hpp>
// Okteta core
#include <Okteta/ByteArrayModel>
#include <Okteta/Byte>
// Qt
#include <QTest>
// Std
#include <array>
#include <memory>
#include <utility>

class UnionDataInformationTest : public QObject
{
    Q_OBJECT

private Q_SLOTS:
    void initTestCase();
    void testSize();
    void testReadData1();
    void testReadData1_data();

private:
    std::unique_ptr<Okteta::ByteArrayModel> model;
};

/** In binary:
 *  00010010 00110100 01010110 01111000
 *  10011010 10111100 11011110 11110000
 */
static constexpr std::array<Okteta::Byte, 8> testData = {
    0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0
};

void UnionDataInformationTest::initTestCase()
{
    model = std::make_unique<Okteta::ByteArrayModel>(testData);
    QCOMPARE(model->size(), static_cast<Okteta::Size>(testData.size()));
}

void UnionDataInformationTest::testSize()
{
    LoggerWithContext lwc(nullptr, QString());

    UnionDataInformation empty(QStringLiteral("empty"));
    QCOMPARE(empty.size(), BitCount32(0));
    std::vector<std::unique_ptr<DataInformation>> size54;
    size54.emplace_back(PrimitiveFactory::newInstance(QStringLiteral("8"), PrimitiveDataType::Bool8, lwc));
    size54.emplace_back(PrimitiveFactory::newInstance(QStringLiteral("16"), PrimitiveDataType::Int16, lwc));
    size54.emplace_back(PrimitiveFactory::newInstance(QStringLiteral("32"), PrimitiveDataType::Float, lwc));
    size54.emplace_back(std::make_unique<UnsignedBitfieldDataInformation>(QStringLiteral("54"), 54));
    UnionDataInformation fourChildren(QStringLiteral("four"), std::move(size54));
    QCOMPARE(fourChildren.size(), BitCount32(54));
}

void UnionDataInformationTest::testReadData1()
{
    LoggerWithContext lwc(nullptr, QString());

    std::unique_ptr<PrimitiveDataInformation> managedB8 = PrimitiveFactory::newInstance(QStringLiteral("8"), PrimitiveDataType::Bool8, lwc);
    PrimitiveDataInformation* const b8 = managedB8.get();
    std::unique_ptr<PrimitiveDataInformation> managedU32 = PrimitiveFactory::newInstance(QStringLiteral("32"), PrimitiveDataType::UInt32, lwc);
    PrimitiveDataInformation* const u32 = managedU32.get();
    std::unique_ptr<PrimitiveDataInformation> managedI16 = PrimitiveFactory::newInstance(QStringLiteral("16"), PrimitiveDataType::Int16, lwc);
    PrimitiveDataInformation* const i16 = managedI16.get();
    auto managedU54 = std::make_unique<UnsignedBitfieldDataInformation>(QStringLiteral("54"), 54);
    UnsignedBitfieldDataInformation* const u54 = managedU54.get();
    std::vector<std::unique_ptr<DataInformation>>  children;
    children.emplace_back(std::move(managedB8));
    children.emplace_back(std::move(managedU32));
    children.emplace_back(std::move(managedI16));
    children.emplace_back(std::move(managedU54));
    auto managedUn = std::make_unique<UnionDataInformation>(QStringLiteral("un"), std::move(children));
    managedUn->setByteOrder(DataInformation::DataInformationEndianness::EndiannessLittle);
    UnionDataInformation* const un = managedUn.get();
    TopLevelDataInformation top(std::move(managedUn));
    // read from bit 0
    QFETCH(uint, address);
    QFETCH(quint8, offset);
    QFETCH(quint64, bitsRemaining);
    // update needed before read
    top.scriptHandler()->updateDataInformation(un);
    qint64 read = un->readData(model.get(), address, bitsRemaining, &offset);
    QFETCH(qint64, readBitsTotal);
    QCOMPARE(read, readBitsTotal);
    QFETCH(quint8, offsetAfterRead);
    QCOMPARE(offset, offsetAfterRead);
    QFETCH(bool, unWasAble);
    QCOMPARE(un->wasAbleToRead(), unWasAble);

    QFETCH(quint8, b8Expected);
    QFETCH(bool, b8WasAble);
    QFETCH(qint16, i16Expected);
    QFETCH(bool, i16WasAble);
    QFETCH(quint32, u32Expected);
    QFETCH(bool, u32WasAble);
    QFETCH(quint64, u54Expected);
    QFETCH(bool, u54WasAble);

    QCOMPARE(b8->value().value<quint8>(), b8Expected);
    QCOMPARE(b8->wasAbleToRead(), b8WasAble);
    QCOMPARE(i16->value().value<qint16>(), i16Expected);
    QCOMPARE(i16->wasAbleToRead(), i16WasAble);
    QCOMPARE(u32->value().value<quint32>(), u32Expected);
    QCOMPARE(u32->wasAbleToRead(), u32WasAble);
    QCOMPARE(u54->value().value<quint64>(), u54Expected);
    QCOMPARE(u54->wasAbleToRead(), u54WasAble);
}

void UnionDataInformationTest::testReadData1_data()
{
    QTest::addColumn<uint>("address");
    QTest::addColumn<quint8>("offset");
    QTest::addColumn<quint64>("bitsRemaining"); // XXX get rid of this argument?

    QTest::addColumn<qint64>("readBitsTotal");
    QTest::addColumn<quint8>("offsetAfterRead");
    QTest::addColumn<bool>("unWasAble");

    QTest::addColumn<quint8>("b8Expected");
    QTest::addColumn<bool>("b8WasAble");
    QTest::addColumn<qint16>("i16Expected");
    QTest::addColumn<bool>("i16WasAble");
    QTest::addColumn<quint32>("u32Expected");
    QTest::addColumn<bool>("u32WasAble");
    QTest::addColumn<quint64>("u54Expected");
    QTest::addColumn<bool>("u54WasAble");

    /* In little endian:
     *  11110000 11011110 10111100 10011010
     *  01111000 01010110 00110100 00010010
     */
    QTest::newRow("0 bytes 0 bits") << 0U << quint8(0) << quint64(64) << qint64(54) << quint8(6) << true
        /* now b8 and i16 */ << quint8(0x12U) << true << qint16(0x3412) << true
        /* u32 and u54 */ << quint32(0x78563412U) << true << Q_UINT64_C(0x1ebc9a78563412) << true;
    // shifted by two bytes in little endian:
    // 11110000 11011110 10111100 10011010 01111000 01010110 00110100 000100xx
    QTest::newRow("0 bytes 2 bits") << 0U << quint8(2) << quint64(62) << qint64(54) << quint8(0) << true
        /* b8 */ << Utils::binary<quint8>("00 000100") << true
        /* i16 */ << Utils::binary<qint16>("10 00110100 000100") << true
        /* u32 */ << Utils::binary<quint32>("10 01111000 01010110 00110100 000100") << true
        /* u54 */ << Utils::binary<quint64>("11011110 10111100 10011010 01111000 01010110 00110100 000100") << true;

    // now so that the 54bit value fits in exactly, i.e. 10 bits shifted
    // shifted by 10 bytes in little endian:
    // 11110000 11011110 10111100 10011010 01111000 01010110 001101xx xxxxxxxx
    QTest::newRow("1 bytes 2 bits") << 1U << quint8(2) << quint64(54) << qint64(54) << quint8(0) << true
        /* b8 */ << Utils::binary<quint8>("10 001101") << true
        /* i16 */ << Utils::binary<qint16>("00 01010110 001101") << true
        /* u32 */ << Utils::binary<quint32>("00 10011010 01111000 01010110 001101") << true
        /* u54 */ << Utils::binary<quint64>("11110000 11011110 10111100 10011010 01111000 01010110 001101") << true;

    // now make the 54 bit value go past eof (11 bits)
    // shifted by 10 bytes in little endian:
    // 11110000 11011110 10111100 10011010 01111000 01010110 00110xxx xxxxxxxx
    QTest::newRow("1 bytes 3 bits") << 1U << quint8(3) << quint64(53) << qint64(-1) << quint8(3) << false
        /* b8 */ << Utils::binary<quint8>("110 00110") << true
        /* i16 */ << Utils::binary<qint16>("000 01010110 00110") << true
        /* u32 */ << Utils::binary<quint32>("100 10011010 01111000 01010110 00110") << true
        /* u54 */ << Utils::binary<quint64>("0") << false;
}

QTEST_GUILESS_MAIN(UnionDataInformationTest)

#include "uniondatainformationtest.moc"
