/********************************************************************
 * File: Ptr.h
 * Author: Keith Schwarz (htiek@cs.stanford.edu)
 *
 * An intrusive reference-counted pointer object used to refer to
 * entities.  This based on the Ptr class from Prof. David Cheriton's
 * "Object-Oriented Programming: A Modeling and Simulation Approach"
 * although there are several differences.
 */
#ifndef Ptr_Included
#define Ptr_Included

#include "RefCountedObject.h"
#include <stdexcept>

namespace Copper3D
{
	
	template <typename T> class Ptr
	{
	public:
		/* Builds a pointer to a resource. */
		Ptr(T* resource = NULL);

		/* Detaches the pointer. */
		~Ptr();

		/* Generalized copy constructor. */
		Ptr(const Ptr& other);
		template <typename U>
			Ptr(const Ptr<U>& other);

		/* Assignment operator. */
		Ptr& operator= (const Ptr& other);

		/* Pointer operators. */
		T& operator*()  const;
		T* operator->() const;

		/* Exchanges contents with another pointer. */
		void swap(Ptr& other);

	private:
		T* resource;

		/* All pointers are friends of one another. */
		template <typename U> friend class Ptr;
	};

	/*** Utility Functions ***/

	/* Access raw pointer. */
	template <typename T> T* Get(const Ptr<T>& ptr);

	/* Comparison operators. */
	template <typename T1, typename T2> bool operator<  (const Ptr<T1>& lhs, const Ptr<T2>& rhs);
	template <typename T1, typename T2> bool operator<= (const Ptr<T1>& lhs, const Ptr<T2>& rhs);
	template <typename T1, typename T2> bool operator== (const Ptr<T1>& lhs, const Ptr<T2>& rhs);
	template <typename T1, typename T2> bool operator!= (const Ptr<T1>& lhs, const Ptr<T2>& rhs);
	template <typename T1, typename T2> bool operator>= (const Ptr<T1>& lhs, const Ptr<T2>& rhs);
	template <typename T1, typename T2> bool operator>  (const Ptr<T1>& lhs, const Ptr<T2>& rhs);

	/* Convert-to-bool operator. */
	template <typename T> bool operator! (const Ptr<T>& operand);
}

/************ Implementation Below This Point **************/
#include <algorithm>

template <typename T> 
	Copper3D::Ptr<T>::Ptr(T* res) {
	/* Attach to the resource if it's non-NULL. */
	resource = res;
	if (resource) resource->addRef();
}

/* Dtor just drops the reference count. */
template <typename T> 
	Copper3D::Ptr<T>::~Ptr() {
	if (resource) resource->deleteRef();
}

/* Copy ctor attaches to the other guy's resource. */
template <typename T>
	Copper3D::Ptr<T>::Ptr(const Ptr& other) {
	resource = other.resource;
	if (resource) resource->addRef();
}
template <typename T>
	template <typename U>
		Copper3D::Ptr<T>::Ptr(const Ptr<U>& other) {
	resource = other.resource;
	if (resource) resource->addRef();
}

/* Assignment operator implemented using copy-and-swap. */
template <typename T>
	void Copper3D::Ptr<T>::swap(Ptr& other) {
	std::swap(resource, other.resource);
}
template <typename T>
	Copper3D::Ptr<T>& Copper3D::Ptr<T>::operator= (const Ptr<T>& other) {
	Ptr copy(other);
	swap(copy);
	return *this;
}

/* Pointer dereference and arrow are standard implementations. */
template <typename T>
	T& Copper3D::Ptr<T>::operator*() const {
	if (resource == NULL)
		throw std::runtime_error("Null pointer dereference.");
	return *resource;
}
template <typename T>
	T* Copper3D::Ptr<T>::operator-> () const {
	return &**this;
}

/* Get returns the address of the object returned by the pointer's * operator. */
template <typename T>
	T* Copper3D::Get(const Ptr<T>& ptr) {
	return &*ptr;
}

/* Comparison operators perform the comparison on the stored pointer. */
template <typename T1, typename T2>
	bool Copper3D::operator<  (const Ptr<T1>& lhs, const Ptr<T2>& rhs) {
	return Get(lhs) < Get(rhs);
}
template <typename T1, typename T2>
	bool Copper3D::operator<= (const Ptr<T1>& lhs, const Ptr<T2>& rhs) {
	return Get(lhs) <= Get(rhs);
}
template <typename T1, typename T2>
	bool Copper3D::operator== (const Ptr<T1>& lhs, const Ptr<T2>& rhs) {
	return Get(lhs) == Get(rhs);
}
template <typename T1, typename T2>
	bool Copper3D::operator!= (const Ptr<T1>& lhs, const Ptr<T2>& rhs) {
	return Get(lhs) != Get(rhs);
}
template <typename T1, typename T2>
	bool Copper3D::operator>= (const Ptr<T1>& lhs, const Ptr<T2>& rhs) {
	return Get(lhs) >= Get(rhs);
}
template <typename T1, typename T2>
	bool Copper3D::operator>  (const Ptr<T1>& lhs, const Ptr<T2>& rhs) {
	return Get(lhs) > Get(rhs);
}

/* Logical not applies the operator to the stored pointer. */
template <typename T>
	bool Copper3D::operator! (const Ptr<T>& operand) {
	return !Get(operand);
}

#endif