/*****************************************************************************
 * $LastChangedDate: 2011-04-09 21:58:06 -0400 (Sat, 09 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Reference-counted pointer template classes (implementation).
 * @remarks Documented at shptr_doc.hh.
 *//*
 * LEGAL:   COPYRIGHT (C) 2007 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

// -- (included by shptr.hh) --

#if ! DOXYGEN

////////////////////////////////////////////////////////////////////////////////
/// @brief Reference-counted pointer template classes (implementation).
///
/// Documented at shptr_doc.hh.
///
template<typename SHARED>
class SHPTR_CLASS : public SHPTR_THREADABLE_CLASS
{
COPYABLE(SHPTR_CLASS)
template<typename SHPTR> friend class SHPTR_CLASS;
private:

    // -- shptr methods MUST BE LOCKED 100% --
    // -- SHARED objects initially have refCount=0 until bound to a shptr --

#include "base/shptr_impl_debug.hh"

public:

    // Default ctor necessary to use shptr in STL containers.
    SHPTR_CLASS( void )
    {
    SHPTR_AUTO_LOCK; /* lock first */           SET_TYPESIG(this,TYPESIG_SHARED_PTR);

#if SHPTR_NULL
        mObj = NULL;                            // hide a land-mine
#else
        mObj = new SHARED;                      // need a real SHARED object for non-null shptr class
        mObj->mRefCount = 1;                    // this shptr is the first to reference object
#endif
    }

    // The usual ctor: shptr = new Shared;
    SHPTR_CLASS( SHARED* obj )                  // Shared initially has refCount=0 (zero)
    {
    SHPTR_AUTO_LOCK;                            SET_TYPESIG(this,TYPESIG_SHARED_PTR);

        mObj = obj;                             TEST_SHPTR_YIELD(); SHPTR_CHECK(this,0); SHPTR_CHECK(obj,0);
        IF_NOT_NULL_SHPTR(obj)
        {
            ++(obj->mRefCount);                 TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1); SHPTR_CHECK(obj,1);
        }
    }

    // Copy ctor: shptr = shptr;
    // The compiler won't choose the following templatized ctor as a copy ctor
    // (though suitable) so this non-template copy ctor remains necessary.
    SHPTR_CLASS( const SHPTR_CLASS& src )
    {
    SHPTR_AUTO_LOCK;                            SET_TYPESIG(this,TYPESIG_SHARED_PTR);
                                                // src shptr should hold a reference
        mObj = src.mObj;                        TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1); SHPTR_CHECK(&src,1);
        IF_NOT_NULL_SHPTR(src.mObj)
        {
            ++(src.mObj->mRefCount);            // copying a shptr increments the refCount
        }                                       TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1); SHPTR_CHECK(&src,1);
    }

    // Copy ctor for shptr<Derived> as shptr<Base>.
    // -- Same code as shptr( const shptr& src ) --
    template<typename SHARED2>
    SHPTR_CLASS( const SHPTR_CLASS<SHARED2>& src )
    {
    SHPTR_AUTO_LOCK;                            SET_TYPESIG(this,TYPESIG_SHARED_PTR);
                                                // src shptr should hold a reference
        mObj = src.mObj;                        TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1); SHPTR_CHECK(&src,1);
        IF_NOT_NULL_SHPTR(src.mObj)
        {
            ++(src.mObj->mRefCount);            // copying a shptr increments the refCount
        }                                       TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1); SHPTR_CHECK(&src,1);
    }

    // Destroy this shptr and also the shared object if refCount falls to 0.
    ~SHPTR_CLASS()
    {
    SHPTR_AUTO_LOCK;                            TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1);

        IF_NOT_NULL_SHPTR(mObj)
        {                                       TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1);
            if ( --(mObj->mRefCount) == 0 )     // delete object if last reference
            {                                   TEST_SHPTR_YIELD(); SHPTR_CHECK(this,0);  // can be 0 now
                delete mObj;                    DEBUG_CODE( mObj = NULL; )
            }
        }                                       INVALIDATE_TYPESIG(this,TYPESIG_SHARED_PTR);  // now invalidate
    }

    // Assignment: shptr = Shared*
    // Reassign this shptr with a different Shared object.
    // To tolerate self-assignment (must have no net effect on refCount):
    // 1. Inc refCount of incoming SHARED.
    // 2. Dec refCount of this shptr.
    SHPTR_CLASS& operator=( SHARED* obj )       // This shptr was constructed already so it holds a reference (or NULL),
    {                                           // but the passed SHARED may be initially have zero references.
    SHPTR_AUTO_LOCK;                            TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1); SHPTR_CHECK(obj,0);

        IF_NOT_NULL_SHPTR(obj)                  // order-dependent to tolerate self-assignment
        {
            ++(obj->mRefCount);
        }                                       TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1); SHPTR_CHECK(obj,1);
        IF_NOT_NULL_SHPTR(mObj)
        {
            if ( --(mObj->mRefCount) == 0 )     // dec outgoing object's refCount
            {
                delete mObj;                    DEBUG_CODE( mObj = NULL; )
            }
        }                                       // same now, a shptr must have refCount>=1 upon returning
        mObj = obj;                             TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1); SHPTR_CHECK(obj,1);

        return *this;
    }

    // Assignment: shptr = shptr
    // -- Same as operator=(SHARED*) but change obj to src.mObj --
    // -- Calling the other operator=() can be done but does require locking this too. --
    SHPTR_CLASS& operator=( const SHPTR_CLASS& src )
    {
    SHPTR_AUTO_LOCK;                            TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1); SHPTR_CHECK(src.mObj,0);

        //---------------------------
        SHARED* const obj = src.mObj;           // for similarity with other operator=(SHARED*obj)
        //---------------------------

        IF_NOT_NULL_SHPTR(obj)                  // order-dependent to tolerate self-assignment
        {
            ++(obj->mRefCount);
        }                                       TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1); SHPTR_CHECK(obj,1);
        IF_NOT_NULL_SHPTR(mObj)
        {
            if ( --(mObj->mRefCount) == 0 )     // dec outgoing object's refCount
            {
                delete mObj;                    DEBUG_CODE( mObj = NULL; )
            }
        }                                       // same now, a shptr must have refCount>=1 upon returning
        mObj = obj;                             TEST_SHPTR_YIELD(); SHPTR_CHECK(this,1); SHPTR_CHECK(obj,1);

        return *this;
    }

    SHARED* operator->( void ) const
    {
    SHPTR_AUTO_LOCK;                            SHPTR_CHECK(this,1);
        return mObj;
    }

    // Limited use (extracted reference could dangle).
    SHARED& operator*( void ) const
    {
    SHPTR_AUTO_LOCK;                            SHPTR_CHECK(this,1);
        return *mObj;
    }

    // Operators:
    bool operator==( const SHPTR_CLASS& other ) const { SHPTR_AUTO_LOCK; SHPTR_CHECK(this,1); SHPTR_CHECK(&other,1); return mObj == other.mObj; }
    bool operator!=( const SHPTR_CLASS& other ) const { SHPTR_AUTO_LOCK; SHPTR_CHECK(this,1); SHPTR_CHECK(&other,1); return mObj != other.mObj; }
    bool operator==( const SHARED* obj )        const { SHPTR_AUTO_LOCK; SHPTR_CHECK(this,1); SHPTR_CHECK(obj,0);    return mObj == obj; }
    bool operator!=( const SHARED* obj )        const { SHPTR_AUTO_LOCK; SHPTR_CHECK(this,1); SHPTR_CHECK(obj,0);    return mObj != obj; }

    // Limited use.
    SHARED*             PTR( void ) const { SHPTR_AUTO_LOCK; SHPTR_CHECK(this,1); return mObj; }
    const SHARED* CONST_PTR( void ) const { SHPTR_AUTO_LOCK; SHPTR_CHECK(this,1); return mObj; }
    SHARED&             REF( void ) const { SHPTR_AUTO_LOCK; SHPTR_CHECK(this,1); return *mObj; }
    const SHARED& CONST_REF( void ) const { SHPTR_AUTO_LOCK; SHPTR_CHECK(this,1); return *mObj; }
    uint        GetRefCount( void ) const { SHPTR_AUTO_LOCK; SHPTR_CHECK(this,1); return mObj->GetRefCount(); }

private:
    SHARED*  mObj;  ///< the shared object
public:
    DECLARE_TYPESIG(TYPESIG_SHARED_PTR)
};

template<typename T> bool operator==( const SHPTR_CLASS<T>& a, const SHPTR_CLASS<T>& b ) { SHPTR_AUTO_LOCK; return a.PTR() == b.PTR(); }
template<typename T> bool operator!=( const SHPTR_CLASS<T>& a, const SHPTR_CLASS<T>& b ) { SHPTR_AUTO_LOCK; return a.PTR() != b.PTR(); }
template<typename T> bool operator<(  const SHPTR_CLASS<T>& a, const SHPTR_CLASS<T>& b ) { SHPTR_AUTO_LOCK; return a.PTR()  < b.PTR(); }

#undef SHPTR_CHECK

#endif // ! DOXYGEN
