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

#include "skeleton_p.h"

#include <QCoreApplication>
#include <QFile>
#include <QFileInfo>

#include <Qt3DCore/qjoint.h>
#include <Qt3DRender/private/abstractrenderer_p.h>
#include <Qt3DRender/private/managers_p.h>
#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DRender/private/renderlogging_p.h>
#include <Qt3DCore/private/qskeleton_p.h>
#include <Qt3DCore/private/qskeletonloader_p.h>
#include <Qt3DCore/private/qmath3d_p.h>

QT_BEGIN_NAMESPACE

using namespace Qt3DCore;

namespace Qt3DRender {
namespace Render {

Skeleton::Skeleton()
    : BackendNode(Qt3DCore::QBackendNode::ReadWrite)
    , m_status(Qt3DCore::QSkeletonLoader::NotReady)
    , m_createJoints(false)
    , m_dataType(Unknown)
    , m_skeletonManager(nullptr)
    , m_jointManager(nullptr)
{
}

void Skeleton::cleanup()
{
    m_source.clear();
    m_status = Qt3DCore::QSkeletonLoader::NotReady;
    m_createJoints = false;
    m_rootJointId = Qt3DCore::QNodeId();
    clearData();
    setEnabled(false);
}

void Skeleton::syncFromFrontEnd(const QNode *frontEnd, bool firstTime)
{
    BackendNode::syncFromFrontEnd(frontEnd, firstTime);
    const QAbstractSkeleton *node = qobject_cast<const QAbstractSkeleton *>(frontEnd);
    if (!node)
        return;
    const QSkeleton *skeletonNode = qobject_cast<const QSkeleton *>(frontEnd);
    const QSkeletonLoader *loaderNode = qobject_cast<const QSkeletonLoader *>(frontEnd);

    if (firstTime) {
        m_skeletonHandle = m_skeletonManager->lookupHandle(peerId());

        if (skeletonNode) {
            m_dataType = Data;
            m_rootJointId = skeletonNode->rootJoint()->id();
            if (!m_rootJointId.isNull()) {
                markDirty(AbstractRenderer::SkeletonDataDirty);
                m_skeletonManager->addDirtySkeleton(SkeletonManager::SkeletonDataDirty, m_skeletonHandle);
            }
        }

        if (loaderNode) {
            m_dataType = File;
            m_source = loaderNode->source();
            if (!m_source.isEmpty()) {
                markDirty(AbstractRenderer::SkeletonDataDirty);
                m_skeletonManager->addDirtySkeleton(SkeletonManager::SkeletonDataDirty, m_skeletonHandle);
            }
        }
    }

    if (loaderNode) {
        if (loaderNode->source() != m_source) {
            m_source = loaderNode->source();
            markDirty(AbstractRenderer::SkeletonDataDirty);
            m_skeletonManager->addDirtySkeleton(SkeletonManager::SkeletonDataDirty, m_skeletonHandle);
        }
        m_createJoints = loaderNode->isCreateJointsEnabled();

        auto newJointId = Qt3DCore::qIdForNode(loaderNode->rootJoint());
        if (newJointId != m_rootJointId) {
            m_rootJointId = newJointId;

            // If using a QSkeletonLoader to create frontend QJoints, when those joints are
            // set on the skeleton, we end up here. In order to allow the subsequent call
            // to loadSkeleton(), see below, to build the internal data from the frontend
            // joints rather than from the source url again, we need to change the data type
            // to Data.
            m_dataType = Data;

            // If the joint changes, we need to rebuild our internal SkeletonData and
            // the relationships between joints and skeleton. Mark the skeleton data as
            // dirty so that we get a loadSkeletonJob executed to process this skeleton.
            if (!m_rootJointId.isNull()) {
                markDirty(AbstractRenderer::SkeletonDataDirty);
                m_skeletonManager->addDirtySkeleton(SkeletonManager::SkeletonDataDirty, m_skeletonHandle);
            }
        }
    }

    auto d = Qt3DCore::QAbstractSkeletonPrivate::get(node);
    m_skeletonData.localPoses = d->m_localPoses;
}

void Skeleton::setStatus(QSkeletonLoader::Status status)
{
    if (status != m_status)
        m_status = status;
}

void Skeleton::clearData()
{
    m_name.clear();
    m_skeletonData.joints.clear();
    m_skeletonData.localPoses.clear();
    m_skeletonData.jointNames.clear();
    m_skeletonData.jointIndices.clear();
}

void Skeleton::setSkeletonData(const SkeletonData &data)
{
    m_skeletonData = data;
    m_skinningPalette.resize(m_skeletonData.joints.size());
}

// Called from UpdateSkinningPaletteJob
void Skeleton::setLocalPose(HJoint jointHandle, const Qt3DCore::Sqt &localPose)
{
    // Find the corresponding index into the JointInfo vector
    // and set the local pose
    const int jointIndex = m_skeletonData.jointIndices.value(jointHandle, -1);
    Q_ASSERT(jointIndex != -1);
    m_skeletonData.localPoses[jointIndex] = localPose;
}

QVector<QMatrix4x4> Skeleton::calculateSkinningMatrixPalette()
{
    const QVector<Sqt> &localPoses = m_skeletonData.localPoses;
    QVector<JointInfo> &joints = m_skeletonData.joints;
    for (int i = 0; i < m_skeletonData.joints.size(); ++i) {
        // Calculate the global pose of this joint
        JointInfo &joint = joints[i];
        if (joint.parentIndex == -1) {
            joint.globalPose = localPoses[i].toMatrix();
        } else {
            JointInfo &parentJoint = joints[joint.parentIndex];
            joint.globalPose = parentJoint.globalPose * localPoses[i].toMatrix();
        }

        m_skinningPalette[i] = joint.globalPose * joint.inverseBindPose;
    }
    return m_skinningPalette;
}


SkeletonFunctor::SkeletonFunctor(AbstractRenderer *renderer,
                                 SkeletonManager *skeletonManager,
                                 JointManager *jointManager)
    : m_renderer(renderer)
    , m_skeletonManager(skeletonManager)
    , m_jointManager(jointManager)
{
}

Qt3DCore::QBackendNode *SkeletonFunctor::create(Qt3DCore::QNodeId id) const
{
    Skeleton *backend = m_skeletonManager->getOrCreateResource(id);
    backend->setRenderer(m_renderer);
    backend->setSkeletonManager(m_skeletonManager);
    backend->setJointManager(m_jointManager);
    return backend;
}

Qt3DCore::QBackendNode *SkeletonFunctor::get(Qt3DCore::QNodeId id) const
{
    return m_skeletonManager->lookupResource(id);
}

void SkeletonFunctor::destroy(Qt3DCore::QNodeId id) const
{
    m_skeletonManager->releaseResource(id);
}

} // namespace Render
} // namespace Qt3DRender

QT_END_NAMESPACE
