// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default


#ifndef QQUICK3DXINPUTMANAGER_OPENXR_P_H
#define QQUICK3DXINPUTMANAGER_OPENXR_P_H

#include <QObject>

#include <openxr/openxr.h>
#include <functional>
#include "qquick3dxractionmapper_p.h"
#include <QtQuick3DXr/private/qtquick3dxrglobal_p.h>

#include <private/qquick3dmodel_p.h>

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

QT_BEGIN_NAMESPACE

class QQuaternion;
class QQuick3DXrHandInput;
class QQuick3DXrInputManager;
class QQuick3DXrHandModel;
class QQuick3DXrController;

class QQuick3DXrInputManagerPrivate : public QObject
{
    Q_OBJECT
    Q_DECLARE_PUBLIC(QQuick3DXrInputManager)
public:
    explicit QQuick3DXrInputManagerPrivate(QQuick3DXrInputManager &manager);
    ~QQuick3DXrInputManagerPrivate();

    void init(XrInstance instance, XrSession session);
    void teardown();

    bool isValid() const { return m_initialized; }

    static QQuick3DXrInputManagerPrivate *get(QQuick3DXrInputManager *inputManager);

    using Handedness = QtQuick3DXr::Handedness;
    using HandPoseSpace = QtQuick3DXr::HandPoseSpace;

    void pollActions();
    void updatePoses(XrTime predictedDisplayTime, XrSpace appSpace);
    void updateHandtracking(XrTime predictedDisplayTime, XrSpace appSpace, bool aimExtensionEnabled);

    XrSpace handSpace(Handedness handedness, HandPoseSpace poseSpace);
    bool isHandActive(Handedness handedness);
    bool isHandTrackerActive(Handedness handedness);

    void setPosePositionAndRotation(Handedness handedness, HandPoseSpace poseSpace, const QVector3D &position, const QQuaternion &rotation);

    QQuick3DXrHandInput *leftHandInput() const;
    QQuick3DXrHandInput *rightHandInput() const;

    void setupHandModel(QQuick3DXrHandModel *model);
    void registerController(QQuick3DXrController *controller);
    void unregisterController(QQuick3DXrController *controller);

    bool isPoseInUse(Handedness handedness, HandPoseSpace poseSpace);

    // NOTE: Static for now...
    qsizetype getPokeJointIndex() const { return qsizetype(XR_HAND_JOINT_INDEX_TIP_EXT); }

    PFN_xrCreateHandTrackerEXT xrCreateHandTrackerEXT_;
    PFN_xrDestroyHandTrackerEXT xrDestroyHandTrackerEXT_;
    PFN_xrLocateHandJointsEXT xrLocateHandJointsEXT_;

    PFN_xrGetHandMeshFB xrGetHandMeshFB_;

    XrHandTrackerEXT handTracker[2] = {XR_NULL_HANDLE, XR_NULL_HANDLE};

    XrHandJointLocationEXT jointLocations[2][XR_HAND_JOINT_COUNT_EXT];
    XrHandJointVelocityEXT jointVelocities[2][XR_HAND_JOINT_COUNT_EXT];

private:
    void setupHandModelInternal(QQuick3DXrHandModel *model, Handedness handedness);

    void setupHandTracking();
    bool queryHandMesh(Handedness handedness);
    void setupActions();
    void destroyActions();
    [[nodiscard]] bool checkXrResult(const XrResult &result);
    void setPath(XrPath &path, const QByteArray &pathString);

    void createAction(XrActionType type,
                      const char *name,
                      const char *localizedName,
                      int numSubactions,
                      XrPath *subactionPath,
                      XrAction &action);
    void getBoolInputState(XrActionStateGetInfo &getInfo, const XrAction &action, std::function<void(bool)> setter);
    void getFloatInputState(XrActionStateGetInfo &getInfo, const XrAction &action, std::function<void(float)> setter);

    void setInputValue(Handedness handedness, int id, const char *shortName, float value);

    QQuick3DXrInputManager *q_ptr = nullptr;

    XrInstance m_instance{XR_NULL_HANDLE};
    XrSession m_session{XR_NULL_HANDLE};

    enum SubPathSelector {NoSubPath = 0, LeftHandSubPath = 1, RightHandSubPath = 2, BothHandsSubPath = 3};

    struct QXRHandComponentPath
    {
        XrPath paths[2] = {{}, {}};
        QByteArray componentPathString;
    };
    QXRHandComponentPath makeHandInputPaths(const QByteArrayView path);
    XrPath makeInputPath(const QByteArrayView path);

    struct InputActionInfo {
        QQuick3DXrInputAction::Action id;
        const char *shortName;
        const char *localizedName;
        XrActionType type;
    };

    QList<InputActionInfo> m_handInputActionDefs;

    struct HandActions {
        XrAction gripPoseAction{XR_NULL_HANDLE};
        XrAction aimPoseAction{XR_NULL_HANDLE};
        XrAction hapticAction{XR_NULL_HANDLE};
    };

    enum class ActionPaths {
        leftGripPose, // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
        leftAimPose, // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
        leftHaptic, // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
        rightGripPose, // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
        rightAimPose, // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
        rightHaptic // OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
    };
    Q_ENUM(ActionPaths)

    enum class InputNames {
        AClick, // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
        BClick, // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
        ATouch, // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)
        BTouch, // OCULUS_TOUCH (right) | VALVE_INDEX (right + left)

        XClick, // OCULUS_TOUCH (left)
        YClick, // OCULUS_TOUCH (left)
        XTouch, // OCULUS_TOUCH (left)
        YTouch, // OCULUS_TOUCH (left)

        MenuClick, // OCULUS_TOUCH (left) | MICROSOFT_MRM (right + left) | HTC_VIVE (right + left)
        SystemClick, // OCULUS_TOUCH (right) | VALVE_INDEX (right + left) | HTC_VIVE (right + left)
        SystemTouch, // VALVE_INDEX (right + left)

        SqueezeValue, // right + left: OCULUS_TOUCH | VALVE_INDEX
        SqueezeForce, // right + left: VALVE_INDEX
        SqueezeClick, // right + left: MICROSOFT_MRM | HTC_VIVE

        TriggerValue, // right + left: OCULUS_TOUCH | VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
        TriggerTouch, // right + left: OCULUS_TOUCH | VALVE_INDEX
        TriggerClick, // right + left: VALVE_INDEX | HTC_VIVE

        ThumbstickX, // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
        ThumbstickY, // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
        ThumbstickClick, // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left) | MICROSOFT_MRM (left)
        ThumbstickTouch, // OCULUS_TOUCH (right + left) | VALVE_INDEX (right + left)
        ThumbrestTouch, // OCULUS_TOUCH (right + left)

        TrackpadX, // right + left:  VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
        TrackpadY, // right + left:  VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
        TrackpadForce, // right + left:  VALVE_INDEX
        TrackpadClick, // right + left:  VALVE_INDEX | MICROSOFT_MRM | HTC_VIVE
        TrackpadTouch // right + left:  MICROSOFT_MRM | HTC_VIVE
    };
    Q_ENUM(InputNames)

    struct InputMapping {
        QQuick3DXrInputAction::Action action;
        InputNames handComponentPath;
        SubPathSelector subPathSelector;
    };

    struct ControllerBindings {
        QByteArray profileName;
        QByteArray profilePath;
        QList<InputMapping> profileMappingDefs;
        QList<ActionPaths> supportedActionPaths;
    };

    // Input State
    XrActionSet m_actionSet{XR_NULL_HANDLE};
    XrPath m_handSubactionPath[2] = {XR_NULL_PATH, XR_NULL_PATH};
    XrSpace m_handGripSpace[2] {XR_NULL_HANDLE, XR_NULL_HANDLE};
    XrSpace m_handAimSpace[2] {XR_NULL_HANDLE, XR_NULL_HANDLE};

    QQuick3DXrHandInput *m_handInputState[2];
    HandActions m_handActions;
    XrAction m_inputActions[QQuick3DXrInputAction::NumActions] = {};
    QSet<QQuick3DXrController *> m_controllers;
    bool m_poseInUse[2][2] = {};
    bool m_poseUsageDirty = true;

    uint m_aimStateFlags[2] = {};
    bool m_initialized = false;
    bool m_validAimStateFromUpdatePoses[2] = {false, false};

    void loadBindings(QList<ControllerBindings>* controllerBindingsList);
    void setUpBindings(QList<ControllerBindings>* controllerBindingsList, QMap<InputNames, QXRHandComponentPath>* handComponentPaths);

    // Hand Mesh Data
    struct HandMeshData {
        QVector<XrVector3f> vertexPositions;
        QVector<XrVector3f> vertexNormals;
        QVector<XrVector2f> vertexUVs;
        QVector<XrVector4sFB> vertexBlendIndices;
        QVector<XrVector4f> vertexBlendWeights;
        QVector<int16_t> indices;
        XrPosef jointBindPoses[XR_HAND_JOINT_COUNT_EXT];
        XrHandJointEXT jointParents[XR_HAND_JOINT_COUNT_EXT];
        float jointRadii[XR_HAND_JOINT_COUNT_EXT];
    } m_handMeshData[2];


    struct HandGeometryData {
        QQuick3DGeometry *geometry = nullptr;
    } m_handGeometryData[2];

    QQuick3DGeometry *createHandMeshGeometry(const HandMeshData &handMeshData);
    void createHandModelData(Handedness handedness);
    friend class QOpenXrHandModel;
};

QT_END_NAMESPACE

#endif // QQUICK3DXINPUTMANAGER_OPENXR_P_H
