// Copyright (c) 2019-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

//! @file
//!
//! @brief Carbonite Interface for IRaycastQuery.
#pragma once

#include <carb/Interface.h>
#include <carb/events/IEvents.h>

#include <rtx/raytracing/RaycastQueryTypes.h>

namespace omni
{
namespace kit
{
namespace raycastquery
{

using RaycastSequenceId = uint32_t;

using Ray = rtx::raytracing::RaycastQueryRay;
using RayQueryResult = rtx::raytracing::RaycastQueryResult;

enum class Result : int32_t
{
    eSuccess = 0, // No error
    eInvalidParameter, // One of the given parameters is not valid
    eParameterIsNull, // One of the given parameters is null, which is not expected
    eRaycastSequenceDoesNotExist, // Raycast sequence requested does not exist
    eRaycastQueryManagerDoesNotExist, // Raycast query manager does not exist, raycast queries disabled
    eRaycastSequenceAdditionFailed, // Raycast sequence addition failed
};

struct RaycastQueryOp
{
    size_t(CARB_ABI* getQueryCount)(RaycastQueryOp* op);

    Ray*(CARB_ABI* getQueryRays)(RaycastQueryOp* op);

    RayQueryResult*(CARB_ABI* getQueryResults)(RaycastQueryOp* op);

    void(CARB_ABI* callback)(RaycastQueryOp* op);

    void(CARB_ABI* destroy)(RaycastQueryOp* op);
};

class IRaycastQuery
{
public:
    CARB_PLUGIN_INTERFACE("omni::rtx::IRaycastQuery", 1, 0)

    /**
     * @brief This function adds a raycast query operation in the current scene.
     *
     * @param raycastOp         operation that describes a query that needs to be executed
     *
     * @return error code if function failed
     *
     * threadsafety: safe to call from any thread
     */
    virtual Result submitRaycastQuery(RaycastQueryOp* raycastOp) = 0;

    /**
     * @brief Add a sequence of raycast queries that maintains last valid value
     * and will execute raycast on current scene.
     *
     * @param sequenceId (out)  sequence id (needed for removal)
     *
     * @return error code if function failed
     *
     * threadsafety: safe to call from any thread
     */
    virtual Result addRaycastSequence(RaycastSequenceId& sequenceId) = 0;

    /**
     * @brief Remove a sequence of raycasts.
     *
     * @param sequenceId        sequence id returned by addRaycastSequence
     *
     * @return error code if function failed
     *
     * threadsafety: safe to call from any thread
     */
    virtual Result removeRaycastSequence(RaycastSequenceId sequence) = 0;

    /**
     * @brief Submit a new ray request to a sequence of raycasts.
     *
     * @param sequenceId        sequence id returned by addRaycastSequence
     *
     * @return error code if function failed
     *
     * threadsafety: safe to call from any thread
     */
    virtual Result submitRayToRaycastSequence(RaycastSequenceId sequence, const Ray& ray) = 0;

    /**
     * @brief Get latest result from a sequence of raycasts.
     *
     * @param sequenceId        sequence id returned by addRaycastSequence
     * @param ray           latest ray that was resolved
     * @param result        result of the ray request
     *
     * @return error code if function failed
     *
     * threadsafety: safe to call from any thread
     */
    virtual Result getLatestResultFromRaycastSequence(RaycastSequenceId sequence,
                                                     Ray& ray,
                                                     RayQueryResult& result) = 0;

    /**
     * @brief Set the size of the raycast sequence.
     *
     * @param sequenceId     sequence id returned by addRaycastSequence
     * @param size           number of ray casts in sequence
     *
     * @return error code if function failed
     *
     * threadsafety: safe to call from any thread
     */
    virtual Result setRaycastSequenceArraySize(RaycastSequenceId sequence, size_t size) = 0;

    /**
     * @brief Submit a new ray request to a sequence of raycasts.
     *
     * @param sequenceId        sequence id returned by addRaycastSequence
     * @param ray               pointer to list of rays
     * @param size              length of the ray array
     *
     * @return error code if function failed
     *
     * threadsafety: safe to call from any thread
     */
    virtual Result submitRayToRaycastSequenceArray(RaycastSequenceId sequence, Ray* ray, size_t size) = 0;

    /**
     * @brief Get latest result from a sequence of raycasts.
     *
     * @param sequenceId        sequence id returned by addRaycastSequence
     * @param ray           latest ray that was resolved
     * @param result        result of the ray request
     *
     * @return error code if function failed
     *
     * threadsafety: safe to call from any thread
     */
    virtual Result getLatestResultFromRaycastSequenceArray(RaycastSequenceId sequence,
                                                          Ray* ray,
                                                          RayQueryResult* result,
                                                          size_t size) = 0;
};

template <typename RaycastQueryOpLambdaType>
class RaycastQueryTemplate final : public RaycastQueryOp
{
public:
    RaycastQueryTemplate(const Ray& queryRay, RaycastQueryOpLambdaType&& raycastOpLambda)
        : m_raycastOpLambda(static_cast<RaycastQueryOpLambdaType&&>(raycastOpLambda)), m_queryRay(queryRay)
    {
        m_queryResult = {};

        RaycastQueryOp::getQueryCount = RaycastQueryTemplate<RaycastQueryOpLambdaType>::getQueryCount;
        RaycastQueryOp::getQueryRays = RaycastQueryTemplate<RaycastQueryOpLambdaType>::getQueryRays;
        RaycastQueryOp::getQueryResults = RaycastQueryTemplate<RaycastQueryOpLambdaType>::getQueryResults;
        RaycastQueryOp::callback = RaycastQueryTemplate<RaycastQueryOpLambdaType>::callback;
        RaycastQueryOp::destroy = RaycastQueryTemplate<RaycastQueryOpLambdaType>::destroy;
    }

    ~RaycastQueryTemplate()
    {
    }

    static size_t CARB_ABI getQueryCount(RaycastQueryOp* op)
    {
        return 1;
    }

    static Ray* CARB_ABI getQueryRays(RaycastQueryOp* op)
    {
        RaycastQueryTemplate<RaycastQueryOpLambdaType>* typedOp = (RaycastQueryTemplate<RaycastQueryOpLambdaType>*)op;

        return &(typedOp->m_queryRay);
    }

    static RayQueryResult* CARB_ABI getQueryResults(RaycastQueryOp* op)
    {
        RaycastQueryTemplate<RaycastQueryOpLambdaType>* typedOp = (RaycastQueryTemplate<RaycastQueryOpLambdaType>*)op;

        return &(typedOp->m_queryResult);
    }

    static void CARB_ABI destroy(RaycastQueryOp* op)
    {
        delete ((RaycastQueryTemplate<RaycastQueryOpLambdaType>*)op);
    }

    static void CARB_ABI callback(RaycastQueryOp* op)
    {
        RaycastQueryTemplate<RaycastQueryOpLambdaType>* typedOp = (RaycastQueryTemplate<RaycastQueryOpLambdaType>*)op;

        typedOp->m_raycastOpLambda(typedOp->m_queryRay, typedOp->m_queryResult);
    }

private:
    RaycastQueryOpLambdaType m_raycastOpLambda;
    Ray m_queryRay;
    RayQueryResult m_queryResult;
};

/**
 * @brief This function adds a raycast query operation in the current scene
 *
 * @param ray          the ray to submit
 * @param callback     the callback lambda function to execute when resolved
 *
 * @return error code if function failed
 *
 * threadsafety: safe to call from any thread
 */
template <typename RaycastOpLambdaType>
inline void submitRaycastQueryLambda(IRaycastQuery* raycastQuery, const Ray& ray, RaycastOpLambdaType&& callback)
{
    auto raycastOp = new RaycastQueryTemplate<RaycastOpLambdaType>(ray, static_cast<RaycastOpLambdaType&&>(callback));

    raycastQuery->submitRaycastQuery(raycastOp);
}
}
}
}
