#ifndef f_VD2_SYSTEM_REFCOUNT_H
#define f_VD2_SYSTEM_REFCOUNT_H

#include <vd2/system/vdtypes.h>
#include <vd2/system/atomic.h>

// Reference counting is a relatively straightforward and simple method of garbage collection.
// The rules are:
//
// 1) Objects increment their reference count on an AddRef() and decrement it on a Release().
// 2) Objects destroy themselves when their reference count is dropped to zero.
// 3) Clients create references with AddRef() and destroy them with Release().
//
// One way to interact with refcounted objects is to call AddRef() whenever a pointer is
// created, and Release() when the pointer is nulled or changed.  The vdrefptr<T> template
// does this automatically.  Reference counting may be "combined" between pointers for
// optimization reasons, such that fewer reference counts are outstanding than actual pointers;
// this requires weak (non-refcounted) pointers and explicit refcount management.
//
// Reference counting has two issues:
//
// A) It is expensive.  VirtualDub only uses it sparingly, for large objects that span the DLL
//    barrier.  Small, simple objects are better copied.
//
// B) Reference counting cannot cope with cycles.  This issue is avoided by arranging objects
//    in a clearly ordered tree, such that no class ever holds a pointer to another object
//    of the same class or to a parent in the reference hierarchy.  vdrefptr<T> can implicitly
//    create cycles if you are not careful.
//
// In VirtualDub, reference counting must be multithread safe, so atomic increment/decrement
// should be used.  vdrefcounted<T> handles this automatically for the template type class.
//
// Two final implementation details:
//
// * Little or no code should be executed after the reference count drops to zero, preferably
//   nothing more than the destructor implicitly generated by the compiler.  The reason is
//   that otherwise there is the potential for an object to be resurrected past its final
//   release by temporarily creating a new reference on the object.  Handling this sensibly,
//   uh, sucks.
//
// * AddRef() and Release() traditionally return the reference count on the object after
//   increment or decrement, but this is not required -- for Release builds, it is only
//   required that the value for Release() be zero iff the object is destroyed.  (The
//   same applies for AddRef(), but since the result of AddRef() is always non-zero,
//   the return of AddRef() is of no use unless it is the actual count.)

class VDINTERFACE IVDRefCount {
public:
	virtual int AddRef()=0;
	virtual int Release()=0;
};

template<class T> class vdrefcounted : public T {
public:
	vdrefcounted() : mRefCount(0) {}
	virtual ~vdrefcounted() {}

	inline virtual int AddRef() {
		return mRefCount.inc();
	}

	inline virtual int Release() {
		int rv = mRefCount.dec();

		if (!rv)
			delete this;

		return rv;
	}

protected:
	VDAtomicInt		mRefCount;
};

template<class T> class vdrefptr {
protected:
	T *ptr;

public:
	typedef vdrefptr<T> self_type;
	typedef T			element_type;

	explicit vdrefptr(T *p = 0) : ptr(p) {
		if (p)
			p->AddRef();
	}

	explicit vdrefptr(const self_type& src) {
		ptr = src.ptr;
		if (ptr)
			ptr->AddRef();
	}

	~vdrefptr() {
		if (ptr)
			ptr->Release();
	}

	inline self_type& operator=(T *src) {
		if (src)
			src->AddRef();
		if (ptr)
			ptr->Release();
		ptr = src;
		return *this;
	}

	inline self_type& operator=(const vdrefptr& src) {
		if (src.ptr)
			src.ptr->AddRef();
		if (ptr)
			ptr->Release();
		ptr = src.ptr;
		return *this;
	}

	operator T*() const { return ptr; }
	T& operator*() const { return *ptr; }
	T *operator->() const { return ptr; }

	inline void clear() {			// release held object and zero out
		if (ptr)
			ptr->Release();
		ptr = NULL;
	}

	inline void set(T* src) {		// release held object and accept reference
		if (ptr)
			ptr->Release();

		ptr = src;
	}

	inline T *release() {			// transfer away held reference
		T *p = ptr;
		ptr = NULL;
		return p;
	}
};

#endif
