// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <QtTest/QTest>
#include <Qt3DRender/private/skeleton_p.h>
#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DCore/qjoint.h>
#include <Qt3DCore/qskeleton.h>
#include <Qt3DCore/qskeletonloader.h>
#include <Qt3DCore/private/qnode_p.h>
#include <Qt3DCore/private/qscene_p.h>
#include <Qt3DCore/private/qbackendnode_p.h>
#include <qbackendnodetester.h>
#include <testarbiter.h>
#include <testrenderer.h>

using namespace Qt3DCore;
using namespace Qt3DRender;
using namespace Qt3DRender::Render;

Q_DECLARE_METATYPE(Qt3DRender::Render::JointInfo)
Q_DECLARE_METATYPE(Qt3DRender::Render::SkeletonData)
Q_DECLARE_METATYPE(Qt3DCore::Sqt)

class tst_Skeleton : public Qt3DCore::QBackendNodeTester
{
    Q_OBJECT

private Q_SLOTS:
    void checkPeerPropertyMirroring()
    {
        // GIVEN
        TestRenderer renderer;
        NodeManagers nodeManagers;
        renderer.setNodeManagers(&nodeManagers);
        Skeleton backendSkeleton;
        backendSkeleton.setRenderer(&renderer);
        backendSkeleton.setSkeletonManager(nodeManagers.skeletonManager());
        QSkeletonLoader skeleton;

        skeleton.setSource(QUrl::fromLocalFile("funnybones.json"));

        // WHEN
        simulateInitializationSync(&skeleton, &backendSkeleton);

        // THEN
        QCOMPARE(backendSkeleton.peerId(), skeleton.id());
        QCOMPARE(backendSkeleton.isEnabled(), skeleton.isEnabled());
        QCOMPARE(backendSkeleton.source(), skeleton.source());
        QCOMPARE(backendSkeleton.rootJointId(), QNodeId());

        // GIVEN
        Skeleton backendSkeleton2;
        backendSkeleton2.setRenderer(&renderer);
        backendSkeleton2.setSkeletonManager(nodeManagers.skeletonManager());
        QSkeleton skeleton2;

        QJoint *joint = new QJoint();
        skeleton2.setRootJoint(joint);

        // WHEN
        simulateInitializationSync(&skeleton2, &backendSkeleton2);

        // THEN
        QCOMPARE(backendSkeleton2.peerId(), skeleton2.id());
        QCOMPARE(backendSkeleton2.isEnabled(), skeleton2.isEnabled());
        QCOMPARE(backendSkeleton2.source(), QUrl());
        QCOMPARE(backendSkeleton2.rootJointId(), joint->id());
    }

    void checkInitialAndCleanedUpState()
    {
        // GIVEN
        TestRenderer renderer;
        NodeManagers nodeManagers;
        renderer.setNodeManagers(&nodeManagers);
        Skeleton backendSkeleton;
        backendSkeleton.setRenderer(&renderer);
        backendSkeleton.setSkeletonManager(nodeManagers.skeletonManager());

        // THEN
        QVERIFY(backendSkeleton.peerId().isNull());
        QCOMPARE(backendSkeleton.isEnabled(), false);
        QCOMPARE(backendSkeleton.source(), QUrl());
        QCOMPARE(backendSkeleton.status(), QSkeletonLoader::NotReady);
        QCOMPARE(backendSkeleton.rootJointId(), QNodeId());

        // GIVEN
        QSkeletonLoader skeleton;
        skeleton.setSource(QUrl::fromLocalFile("skeleton1.json"));

        // WHEN
        simulateInitializationSync(&skeleton, &backendSkeleton);
        backendSkeleton.cleanup();

        // THEN
        QCOMPARE(backendSkeleton.source(), QUrl());
        QCOMPARE(backendSkeleton.isEnabled(), false);
        QCOMPARE(backendSkeleton.status(), QSkeletonLoader::NotReady);
        QCOMPARE(backendSkeleton.rootJointId(), QNodeId());
    }

    void checkDirectPropertyChanges()
    {
        // GIVEN
        TestRenderer renderer;
        NodeManagers nodeManagers;
        renderer.setNodeManagers(&nodeManagers);
        Skeleton backendSkeleton;
        backendSkeleton.setRenderer(&renderer);
        backendSkeleton.setSkeletonManager(nodeManagers.skeletonManager());
        backendSkeleton.setDataType(Skeleton::File);

        // Initialize to ensure skeleton manager is set
        QSkeletonLoader skeleton;
        skeleton.setSource(QUrl::fromLocalFile("skeleton1.json"));
        simulateInitializationSync(&skeleton, &backendSkeleton);

        // WHEN
        skeleton.setEnabled(false);
        backendSkeleton.syncFromFrontEnd(&skeleton, false);

        // THEN
        QCOMPARE(backendSkeleton.isEnabled(), false);

        // WHEN
        const QUrl newSource = QUrl::fromLocalFile("terminator.json");
        skeleton.setSource(newSource);
        backendSkeleton.syncFromFrontEnd(&skeleton, false);

        // THEN
        QCOMPARE(backendSkeleton.source(), newSource);
    }

    void checkCreateFrontendJoint_data()
    {
        QTest::addColumn<QMatrix4x4>("inverseBindMatrix");
        QTest::addColumn<Qt3DCore::Sqt>("localPose");
        QTest::addColumn<QString>("jointName");
        QTest::addColumn<QJoint *>("expectedJoint");

        QMatrix4x4 m;
        Qt3DCore::Sqt localPose;
        QTest::newRow("default") << m << localPose << QString() << new QJoint();

        const QVector3D t(1.0f, 2.0f, 3.0f);
        const QQuaternion r = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 45.0f);
        const QVector3D s(1.5f, 2.5f, 3.5f);
        localPose.scale = s;
        localPose.rotation = r;
        localPose.translation = t;


        QString name = QLatin1String("Foo");
        QJoint *joint = new QJoint();
        joint->setTranslation(t);
        joint->setRotation(r);
        joint->setScale(s);
        joint->setName(name);
        joint->setInverseBindMatrix(m);
        QTest::newRow("localPose") << m << localPose << name << joint;

        m.rotate(r);
        m.scale(QVector3D(1.0f, 1.0f, 1.0f) / s);
        m.translate(-t);
        name = QLatin1String("Bar");

        joint = new QJoint();
        joint->setTranslation(t);
        joint->setRotation(r);
        joint->setScale(s);
        joint->setInverseBindMatrix(m);
        joint->setName(name);
        QTest::newRow("inverseBind") << m << localPose << name << joint;
    }
};

QTEST_APPLESS_MAIN(tst_Skeleton)

#include "tst_skeleton.moc"
