// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of NVIDIA CORPORATION nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2016-2024 NVIDIA Corporation. All rights reserved.


#ifndef NVBLASTTKJOINTIMPL_H
#define NVBLASTTKJOINTIMPL_H


#include "NvBlastTkJoint.h"
#include "NvBlastTkCommon.h"
#include "NvBlastIndexFns.h"

#include "NvBlastAssert.h"
#include "NvBlastDLink.h"

#include <atomic>


namespace Nv
{
namespace Blast
{

// Forward declarations
class TkActorImpl;
class TkJointImpl;
class TkFamilyImpl;
class TkEventQueue;


/**
Double-sided link (DLink) which holds a reference back to a joint which contains it.
*/
struct TkJointLink : public DLink
{
    TkJointImpl*    m_joint;    //!< The joint containing this link.
};


/**
Implementation of TkJoint.
*/
class TkJointImpl : public TkJoint
{
public:
    /** Blank constructor only creates valid TkJointLinks (point back to this object) */
    TkJointImpl();

    /**
    This constructor sets all internal data.  If the joint is defined in an asset, the family
    instanced from that asset will own this joint, and the 'owner' parameter is that family.
    Otherwise, in the case where a joint is created from TkFramwork::createJoint, the joint
    is not owned by a family and 'owner' will be NULL.
    */
    TkJointImpl(const TkJointDesc& desc, TkFamilyImpl* owner);

    // Begin TkObject
    virtual void                release() override;
    // End TkObject

    // Begin TkJoint
    virtual const   TkJointData getData() const override;
    // End TkJoint

    // Public API

    /**
    Internal method to access a const reference to the joint data.

    \return a const reference to the joint data.
    */
    const TkJointData&          getDataInternal() const;    

    /**
    Internal method to access a non-const reference to the joint data.

    \return a non-const reference to the joint data.
    */
    TkJointData&                getDataWritable();

    /**
    Set the actors that this joint attaches to.  When the actors are different from the joint's current actors,
    an event will be generated on one of the actors' families event queues to signal the change.  Alternatively,
    if alternateQueue is not NULL then it will be used to hold the event.

    If a non-NULL attached actor becomes NULL, then this joint will detach its references to both actors (if
    they exist) and send an event of subtype Unreferenced.  This signals the user that the joint may be deleted.

    \param[in]  actor0          The new TkActor to replace the first attached actor.
    \param[in]  actor1          The new TkActor to replace the second attached actor.
    \param[in]  alternateQueue  If not NULL, this queue will be used to hold events generated by this function.
    */
    void                        setActors(TkActorImpl* actor0, TkActorImpl* actor1, TkEventQueue* alternateQueue = nullptr);

    /**
    Ensures that any attached actors no longer refer to this joint.
    */
    void                        removeReferencesInActors();

    /**
    Ensures that any attached actors' families no longer refer to this joint.  External joints (created using
    TkFramework::createJoint) are referenced by the attached actors' families.
    */
    void                        removeReferencesInFamilies();

private:
    TkJointData     m_data;     //!< The data given to the user: attached actors, chunk indices, and actor-local attachment positions.
    TkJointLink     m_links[2]; //!< One link for each actor in m_data.m_actors.  If m_data.m_actors[0] == m_data.m_actors[1], then only m_links[0] is used.
    TkFamilyImpl*   m_owner;    //!< The owning family if this is an internal joint created during TkFramework::createActor() from a TkAssetDesc with joint flags.

    friend class TkFrameworkImpl;
    friend class TkFamilyImpl;
    friend class TkActorImpl;
};


//////// TkJointImpl inline methods ////////

NV_INLINE TkJointImpl::TkJointImpl()
{
    m_links[0].m_joint = m_links[1].m_joint = this;
}


NV_INLINE const TkJointData& TkJointImpl::getDataInternal() const
{
    return m_data;
}


NV_INLINE TkJointData& TkJointImpl::getDataWritable()
{
    return m_data;
}

} // namespace Blast
} // namespace Nv


#endif // ifndef NVBLASTTKJOINTIMPL_H
