/* Lecture code 14.0
 *
 * SharedPtr class template.  SharedPtr is a modified version of the SmartPointer class from
 * Chapter 23 of the course reader and is a reference-counted smart pointer you can use to
 * automate resource management.  You will be using a modified version of SharedPtr with
 * several extra features in Assignment 1.
 */

#ifndef SharedPtr_Included
#define SharedPtr_Included

#include <iostream>
#include <cstdlib> // For NULL
using namespace std;

/* A reference-counted shared pointer class that allows several pointers
 * to each access the same resource.  When the final pointer is deallocated,
 * the resource is cleaned up.
 */
template <typename ElemType> class SharedPtr
{
public:
	/* Primary constructor wraps the resource, which is expected to come
	 * from a call to new.
	 */
	explicit SharedPtr(ElemType* elem);

	/* Copy ctor, assignment operator, and destructor properly manage the
	 * resources.
	 */
	SharedPtr(const SharedPtr &other);
	SharedPtr& operator = (const SharedPtr& other);
	~SharedPtr();

	/* Arrow and star operator allow us to use the pointer like a regular
	 * C++ pointer.  As an exercise, consider adding == and the other relational
	 * operators.
	 */
	ElemType& operator *() const { return *resource; }
	ElemType* operator ->() const { return resource;}

	/* Function to return the raw stored pointer.  Notice that although the implementation
	 * of this function is inside the SharedPtr class, because the function is a friend it
	 * is technically a free function.
	 */
	template <typename ElemType> 
	friend ElemType* Get(const SharedPtr<ElemType>& p) { return p.resource; }

	/* Helper function to return the number of pointers sharing this resource. */
	template <typename ElemType> 
	friend size_t GetShareCount(const SharedPtr<ElemType>& p) { return *p.refCount; }

	/* Causes a SharedPtr to start managing a different resource. */
	template <typename ElemType> 
	friend void Reset(SharedPtr<ElemType>& p, ElemType* res)
	{
		SharedPtr<ElemType> temp(res);
		p = temp;
	}
private:
	ElemType* resource;       // The actual resource
	size_t* refCount; // The number of pointers referencing it.

	/* Utility functions to change where the pointer is pointing. */
	void detach();
	void attachTo(const SharedPtr &other);
};

/* Basic constructor just creates a SharedPtr referencing the specified
 * resource.
 */
template <typename ElemType>
SharedPtr<ElemType>::SharedPtr(ElemType* res)
{
	resource = res;
	refCount = new size_t(1);
}

/* attachTo attaches the pointer to the specified resource and updates the reference
 * count.
 */
template <typename ElemType>
void SharedPtr<ElemType>::attachTo(const SharedPtr& other)
{
	resource = other.resource;
	refCount = other.refCount;
	++(*refCount);
}

/* Copy constructor causes this pointer to tag onto the second pointer. */
template <typename ElemType>
SharedPtr<ElemType>::SharedPtr(const SharedPtr& other)
{
	attachTo(other);
}

/* detach drops the reference count and cleans up the specified resource if the
 * count drops to zero.
 */
template <typename ElemType> void SharedPtr<ElemType>::detach()
{
	--(*refCount);
	if(*refCount == 0)
	{
		delete refCount;
		delete resource;
	}
}

/* Destructor just calls detach. */
template <typename ElemType> SharedPtr<ElemType>::~SharedPtr()
{
	detach();
}

/* Assignment operator attaches the pointer to the specified resource after
 * first dropping existing resource.
 */
template <typename ElemType>
SharedPtr<ElemType>& SharedPtr<ElemType>::operator =(const SharedPtr& other)
{
	if(this != &other) // Don't forget self-assignment!
	{
		detach();
		attachTo(other);
	}
	return *this;
}

#endif
