/**------------------------------------------------------------------------
 * @file
 *
 * Copyright 2001 by the University of Toronto (UT).
 *
 *                       All Rights Reserved
 * 
 * Permission to copy and modify this software and its documentation 
 * only for internal use in your organization is hereby granted, 
 * provided that this notice is retained thereon and on all copies.  
 * UT makes no representations as to the suitability and operability 
 * of this software for any purpose.  It is provided "as is" without 
 * express or implied warranty.
 *
 * UT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT
 * SHALL UT BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * No other rights, including for example, the right to redistribute 
 * this software and its documentation or the right to prepare 
 * derivative works, are granted unless specifically provided in a 
 * separate license agreement.
 *
 * Author: Diego Macrini (dmac@cs.toronto.edu)
 * 
 * $Log: SmartPtr.h,v $
 * Revision 1.2  2002/05/16 22:51:05  dmac
 * Several bugs fixed and a new DAG-derived class added
 *
 * Revision 1.1.1.1  2002/03/10 07:38:07  dmac
 * Imported sources
 *
 *
 *-----------------------------------------------------------------------*/
#ifndef _SMART_PTR_H_
#define _SMART_PTR_H_

#include "BasicUtils.h"

template<class T> class SmartPtr;

template<class T> std::ostream& operator<<(std::ostream& os, const SmartPtr<T>& x);
template<class T> std::istream& operator>>(std::istream& is, SmartPtr<T>& x);

/*!
	\brief Wrapper class of a pointer to class T. It handles the destruction of T.

	This class wraps a pointer to on object of class T, an keeps track
	of the number of references to the same object. When the number of references
	becomes zero, the referenced object is destroyed.

	If the non-constant version of operator->() is called for an object with 
	number of references grater than 1, a copy of the referenced object is performed 
	by calling its member function CreateObject(). This implies that class T must
	have this function implemented.

	The need for requiring a function T::CreateObject() is that we don't know
	whether we have a pointer to an object T or to a class derived from T. Thus,
	the only solution is to force T to have a virtual function that will
	create and copy the correct type of object.
*/

template<class T> class SmartPtr
{
protected:
	struct DATAREF {
		T* pData;
		int nLinks;
		DATAREF() { nLinks = 1; }
	};

	DATAREF* ptr;

public:
	
	SmartPtr() { 
		ptr = new DATAREF;
		ptr->pData = NULL;
	}

	SmartPtr(T* p) { 
		ptr = new DATAREF; 
		ptr->pData = p; 
	}

	SmartPtr(const T* p) {
		ptr = new DATAREF;
		ptr->pData = (T*)p; //Supposely it's safe to discard 'const' here

		// There is someone else ponting at this object, so add one ref
		// s.t. we don't destroy the actual data in the destructor.
		ptr->nLinks++;
	}
	
	SmartPtr(const T& obj) {
		ptr = new DATAREF;
		ptr->pData = new T;
		*ptr->pData = obj; // Clone object
	}

	SmartPtr(const SmartPtr<T>& x)	{ 
		x.ptr->nLinks++;
		ptr = x.ptr;
	}

	~SmartPtr() { 
		if (--ptr->nLinks == 0) {
			if (ptr->pData)
				delete ptr->pData;
			delete ptr;
		} 
	}	

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

	bool operator==(const SmartPtr<T>& rhs) const
	{
		return ptr->pData == rhs.ptr->pData;
	}

	bool IsNull() const
	{
		return ptr->pData == NULL;
	}

	T* operator->() {
		if (ptr->nLinks > 1) {
			ptr->nLinks--;
			T* p = ptr->pData->CreateObject();
			*p = *ptr->pData;
			
			ptr = new DATAREF;
			ptr->pData = p;
			
			WARNING(true, "possible not desired copy of data");
		}

		return ptr->pData; 
	}

	SmartPtr<T>& operator=(const SmartPtr<T>& rhs) {
		rhs.ptr->nLinks++;	// protect against 'x = x'
		
		if (--ptr->nLinks == 0) {
			delete ptr->pData;
			delete ptr;
		}

		ptr = rhs.ptr;
		return *this;
	}

	SmartPtr<T>& operator=(T* rhs) {
	
		if (ptr->nLinks > 1)
		{
			ptr->nLinks--;
			ptr = new DATAREF;
		}
		else
			delete ptr->pData;

		ptr->pData = rhs;
		return *this;
	}

	int Links() const { return ptr->nLinks; }

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

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

#ifdef WIN32
	template<class T> friend std::ostream& operator<<(std::ostream& os, const SmartPtr<T>& x);
	template<class T> friend std::istream& operator>>(std::istream& is, SmartPtr<T>& x);
#else
	friend std::ostream& operator<< <T>(std::ostream& os, const SmartPtr<T>& x);
	friend std::istream& operator>> <T>(std::istream& is, SmartPtr<T>& x);
#endif //WIN32
};

template<class T>
std::ostream& operator<<(std::ostream& os, const SmartPtr<T>& x) { return os << *(x.ptr->pData); }

template<class T>
std::istream& operator>>(std::istream& is, SmartPtr<T>& x) { return is >> *(x.ptr->pData); }

#endif //_SMART_PTR_H_
