/* $Id: autocopyptr.hpp 6381 2006-09-04 20:40:33Z rubidium $ */

#ifndef  AUTOCOPYPTR_HPP
#define  AUTOCOPYPTR_HPP

/** CAutoCopyPtrT - kind of CoW (Copy on Write) pointer.
 *  It is non-invasive smart pointer (reference counter is held outside
 *  of Tdata).
 *  When copied, its new copy shares the same underlaying structure Tdata.
 *  When dereferenced, its behavior depends on 2 factors:
 *     - whether the data is shared (used by more than one pointer)
 *     - type of access (read/write)
 *    When shared pointer is dereferenced for write, new clone of Tdata
 *  is made first.
 *  Can't be used for polymorphic data types (interfaces).
 */
template <class Tdata_>
class CAutoCopyPtrT {
protected:
	typedef Tdata_ Tdata;

	struct CItem {
		int     m_ref_cnt;  ///< reference counter
		Tdata   m_data;     ///< custom data itself

		FORCEINLINE CItem()                  : m_ref_cnt(1)                     {};
		FORCEINLINE CItem(const Tdata& data) : m_ref_cnt(1), m_data(data)       {};
		FORCEINLINE CItem(const CItem& src)  : m_ref_cnt(1), m_data(src.m_data) {};
	};

	mutable CItem* m_pI; ///< points to the ref-counted data

public:
	FORCEINLINE CAutoCopyPtrT() : m_pI(NULL) {};
	FORCEINLINE CAutoCopyPtrT(const Tdata& data) : m_pI(new CItem(data)) {};
	FORCEINLINE CAutoCopyPtrT(const CAutoCopyPtrT& src) : m_pI(src.m_pI) {if (m_pI != NULL) m_pI->m_ref_cnt++;}
	FORCEINLINE ~CAutoCopyPtrT() {if (m_pI == NULL || (--m_pI->m_ref_cnt) > 0) return; delete m_pI; m_pI = NULL;}

	/** data accessor (read only) */
	FORCEINLINE const Tdata& GetDataRO() const {if (m_pI == NULL) m_pI = new CItem(); return m_pI->m_data;}
	/** data accessor (read / write) */
	FORCEINLINE Tdata& GetDataRW() {CloneIfShared(); if (m_pI == NULL) m_pI = new CItem(); return m_pI->m_data;}

	/** clone data if it is shared */
	FORCEINLINE void CloneIfShared()
	{
		if (m_pI != NULL && m_pI->m_ref_cnt > 1) {
			// we share data item with somebody, clone it to become an exclusive owner
			CItem* pNewI = new CItem(*m_pI);
			m_pI->m_ref_cnt--;
			m_pI = pNewI;
		}
	}

	/** assign pointer from the other one (maintaining ref counts) */
	FORCEINLINE void Assign(const CAutoCopyPtrT& src)
	{
		if (m_pI == src.m_pI) return;
		if (m_pI != NULL && (--m_pI->m_ref_cnt) <= 0) delete m_pI;
		m_pI = src.m_pI;
		if (m_pI != NULL) m_pI->m_ref_cnt++;
	}

	/** dereference operator (read only) */
	FORCEINLINE const Tdata* operator -> () const {return &GetDataRO();}
	/** dereference operator (read / write) */
	FORCEINLINE Tdata* operator -> () {return &GetDataRW();}

	/** assignment operator */
	FORCEINLINE CAutoCopyPtrT& operator = (const CAutoCopyPtrT& src) {Assign(src); return *this;}

	/** forwarding 'lower then' operator to the underlaying items */
	FORCEINLINE bool operator < (const CAutoCopyPtrT& other) const
	{
		assert(m_pI != NULL);
		assert(other.m_pI != NULL);
		return (m_pI->m_data) < (other.m_pI->m_data);
	}
};


#endif /* AUTOCOPYPTR_HPP */
