// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "instancerepeater_p.h"
#include <math.h>
#include <QMatrix4x4>

QT_BEGIN_NAMESPACE

/*!
    \qmltype InstanceModel
    \inherits Object3D
    \inqmlmodule QtQuick3D.Helpers
    \since 6.4
    \brief Defines a data model based on an instance table.

    The InstanceModel QML type is a data model that provides access to the elements of an \l Instancing table.

    The following roles are available:
    \table
        \header
            \li Role name
            \li Description
        \row
            \li \c modelPosition
            \li The position of the instance as a \l vector3d
        \row
            \li \c modelRotation
            \li The rotation of the instance as a \l quaternion
        \row
            \li \c modelScale
            \li The scale of the instance  as a \l vector3d
        \row
            \li \c modelColor
            \li The \l color of the instance
        \row
            \li \c modelData
            \li The custom data of the instance as a \l vector4d
    \endtable

    \sa InstanceRepeater
*/

/*!
    \qmlproperty Instancing InstanceModel::instancingTable

    This property specifies the underlying instance table of the model.
*/

InstanceModel::InstanceModel(QObject *parent)
    : QAbstractListModel(parent)
{
}

QVariant InstanceModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();
    ensureTable();

    int idx = index.row();

    if (idx >= m_count) {
        qWarning("InstanceModel: index out of range");
        return QVariant();
    }

    auto *instanceData = reinterpret_cast<const QQuick3DInstancing::InstanceTableEntry*>(m_instanceData.data()) + idx;

    switch (role) {
    case ColorRole:
        return instanceData->getColor();
    case PositionRole:
        return instanceData->getPosition();
    case RotationRole:
        return instanceData->getRotation();
    case ScaleRole:
        return instanceData->getScale();
    case CustomDataRole:
        return instanceData->instanceData;
    }
    return QVariant();
}

int InstanceModel::rowCount(const QModelIndex &) const
{
    ensureTable();
    return m_count;
}

void InstanceModel::setInstancing(QQuick3DInstancing *instancing)
{
    if (m_instancing == instancing)
        return;
    QObject::disconnect(m_tableConnection);
    m_instancing = instancing;
    m_tableConnection = QObject::connect(instancing, &QQuick3DInstancing::instanceTableChanged, this, &InstanceModel::reset);
    emit instancingChanged();
}

const QQuick3DInstancing::InstanceTableEntry *InstanceModel::instanceData(int index) const
{
    if (index >= m_count)
        return nullptr;
    return reinterpret_cast<const QQuick3DInstancing::InstanceTableEntry*>(m_instanceData.constData()) + index;
}

void InstanceModel::ensureTable() const
{
    auto *that = const_cast<InstanceModel*>(this);
    that->m_instanceData = m_instancing->instanceBuffer(&that->m_count);
}

void InstanceModel::reset()
{
    beginResetModel();
    m_instanceData.clear();
    endResetModel();
}

/*!
    \qmltype InstanceRepeater
    \inherits Repeater3D
    \inqmlmodule QtQuick3D.Helpers
    \since 6.4
    \brief Instantiates components based on an instance table.

    The InstanceRepeater type is used to create a number of objects based on an
    \l Instancing table. It is a \l Repeater3D subtype that takes an Instancing table instead
    of a data model, and automatically applies \c position, \c scale, and \c rotation.

    One use case is to implement \l {View3D::pick}{picking} by creating invisible dummy objects
    that match the rendered instances. To improve performance, the dummy objects can be created with a
    simpler geometry than the instanced models.

    For example:
    \qml
        InstanceRepeater {
            instancingTable: myInstanceTable
            Model {
                source: "#Cube"
                pickable: true
                property int instanceIndex: index // expose the index, so we can identify the instance
                opacity: 0
            }
        }
    \endqml

    \sa InstanceModel
*/

/*!
    \qmlproperty Instancing InstanceRepeater::instancingTable

    This property specifies the instance table used by the repeater.
*/

InstanceRepeater::InstanceRepeater(QQuick3DNode *parent)
    : QQuick3DRepeater(parent)
{
}

QQuick3DInstancing *InstanceRepeater::instancing() const
{
    return m_model ? m_model->instancing() : nullptr;
}

void InstanceRepeater::setInstancing(QQuick3DInstancing *instancing)
{
    if (m_model && m_model->instancing() == instancing)
        return;
    if (!m_model)
        m_model = new InstanceModel(this);
    m_model->setInstancing(instancing);
    setModel(QVariant::fromValue(m_model));
    emit instancingChanged();
}

void InstanceRepeater::initDelegate(int index, QQuick3DNode *node)
{
    Q_ASSERT(m_model);
    auto *entry = m_model->instanceData(index);
    Q_ASSERT(entry);
    node->setPosition(entry->getPosition());
    node->setScale(entry->getScale());
    node->setRotation(entry->getRotation());
}

QT_END_NAMESPACE
