/*!
  \file
  \brief BDM's own smart pointers.
  \author Vaclav Barta.

  -----------------------------------
  BDM++ - C++ library for Bayesian Decision Making under Uncertainty

  Using IT++ for numerical operations
  -----------------------------------
*/

#ifndef shared_ptr_h
#define shared_ptr_h

#include <limits.h>
#include <algorithm>
#include <stdexcept>
#include <string>
#include "bdmerror.h"

namespace bdm {

/*! \brief A naive implementation of roughly a subset of the std::tr1::shared_ptr spec

  Really just roughly - it ignores memory
  exceptions, for example; also note I didn't read the spec.

  The standard template would naturally be preferable, \b if it was
  included in the standard libraries of all supported compilers - but
  as of 2009, that's still a problem...
*/
template <typename T>
class shared_ptr {
	template<class U> friend class shared_ptr;

private:
	T *payload;
	unsigned *refCnt;

public:
	/*!
	  \brief Default constructor

	  Creates an empty shared_ptr - one that doesn't point anywhere.
	*/
	shared_ptr() :
			payload ( 0 ),
			refCnt ( 0 ) {
	}

	/*! 
	  Constructs a shared_ptr that owns the pointer p (unless p
	  is NULL, in which case this constructor creates an empty
	  shared_ptr). When p isn't null, it must have been alllocated
	  by new!
	*/
	shared_ptr ( T *p ) :
			payload ( p ),
			refCnt ( p ? new unsigned ( 1 ) : 0 ) {
	}

	/*!
	  \brief Copy constructor

	  If other is empty, constructs an empty shared_ptr; otherwise,
	  constructs a shared_ptr that shares ownership with other.
	*/
	shared_ptr ( const shared_ptr<T> &other ) :
			payload ( other.payload ),
			refCnt ( other.refCnt ) {
		add_ref();
	}

	/*!
	  \brief Generalized copy

	  Allows initialization of shared pointer of a base type from
	  raw pointer to a derived type.

	  If other is empty, constructs an empty shared_ptr; otherwise,
	  constructs a shared_ptr that shares ownership with other.
	*/
	template<typename U>
	shared_ptr ( const shared_ptr<U> &other ) :
			payload ( other.payload ),
			refCnt ( other.refCnt ) {
		add_ref();
	}

	/*!
	  Destructor.
	*/
	~shared_ptr() {
		del_ref();
	}

	/*!
	  \brief Assignment operator
	*/
	shared_ptr<T> &operator= ( const shared_ptr<T> &other ) {
		other.add_ref();
		del_ref();

		payload = other.payload;
		refCnt = other.refCnt;

		return *this;
	}

	/*!
	  Returns the stored pointer (which remains owned by this
	  instance). For empty instances, this method returns NULL.
	*/
	T *get() {
		return payload;
	}

	/*!
	  Dereferences the stored pointer (which remains owned by
	  this instance). This method may only be called when the
	  stored pointer isn't NULL.
	*/
	T *operator->() {
		if ( !payload) {abort();};// "dereferencing NULL" );
		return payload;
	}

	//! Returns a reference to the object pointed to by the stored
	//! pointer. This method may only be called when the stored pointer
	//! isn't NULL.
	T &operator*() {
		bdm_assert_debug ( payload, "dereferencing NULL" );
		return *payload;
	}

	/*!
	  Returns the stored pointer (which remains owned by this
	  instance). For empty instances, this method returns NULL.
	*/
	const T* get() const {
		return payload;
	}

	//! Returns the stored pointer (which remains owned by this
	//! instance). This method may only be called when the stored
	//! pointer isn't NULL.
	const T *operator->() const {
		bdm_assert_debug ( payload, "dereferencing NULL" );
		return payload;
	}

	//! Returns a reference to the object pointed to by the stored
	//! pointer. This method may only be called when the stored pointer
	//! isn't NULL.
	const T &operator*() const {
		bdm_assert_debug ( payload, "dereferencing NULL" );
		return *payload;
	}

	//! Returns use_count() == 1
	bool unique() const {
		return refCnt && ( *refCnt == 1 );
	}

	/*!
	  Returns the number of shared_ptr instances (including
	  this instance) that share ownership with this instance. For
	  empty instances, this method returns 0.
	*/
	long use_count() const {
		return refCnt ? *refCnt : 0;
	}

	/*!
	  \brief Boolean cast

	  This operator returns true if and only if the instance isn't empty.
	*/
	operator bool() const {
		return !!payload;
	}

	/*!
	  \brief const cast

	  Shared pointer to T can be converted to shared pointer to
	  const T, just like T * can be converted to T const *.
	*/
	template<typename U>
	operator shared_ptr<const U>() const {
		shared_ptr<const U> cptr;		
		cptr.refCnt = refCnt;
		cptr.payload = payload;
		add_ref();
		return cptr;
	}

	/*!
	  \brief Efficient swap for shared_ptr.
	 */
	void swap ( shared_ptr &other ) {
		std::swap ( payload, other.payload );
		std::swap ( refCnt, other.refCnt );
	}

private:
	void add_ref() const {
		if ( refCnt ) {
			if ( *refCnt == UINT_MAX ) {
				throw std::overflow_error (
				    std::string ( "Shared pointer has too many references." ) );
			}

			++*refCnt;
		}
	}

	void del_ref() {
		if ( refCnt ) {
			if ( ! ( --*refCnt ) ) {
				delete payload;
				delete refCnt;
			}
		}
	}
};

//! Compare shared pointers 
template<typename T, typename U>
bool operator== ( shared_ptr<T> const &a, shared_ptr<U> const &b ) {
	return a.get() == b.get();
}

//! Compare shared pointers 
template<typename T, typename U>
bool operator!= ( shared_ptr<T> const &a, shared_ptr<U> const &b ) {
	return a.get() != b.get();
}

//! Compare shared pointers 
template<typename T, typename U>
bool operator< ( shared_ptr<T> const &a, shared_ptr<U> const &b ) {
	return a.get() < b.get();
}

/*! \brief A wrapper of shared_ptr which is never empty.

  T must have a default constructor.

  Note that shared_ptr's destructor isn't virtual - don't call delete
  on pointers to instances of this class.
 */
template <typename T>
class object_ptr : public shared_ptr<T>
{
public:
	/*!
	  \brief Default constructor

	  Calls T's default constructor.
	*/
	object_ptr() : shared_ptr<T> ( new T() ) { }

	/*!
	  \brief Upcast from shared_ptr<T> to object_ptr<T>

	  \param b The shared pointer, which must not be empty.
	*/
	object_ptr ( const shared_ptr<T> &b ) : shared_ptr<T> ( b ) {
		bdm_assert_debug ( this->get(), "object_ptr cannot be empty" );
	}

	/*! 
	  Constructs an object_ptr that owns the pointer p. p must
	  have been alllocated by new!
	*/
	object_ptr ( T *p ) : shared_ptr<T> ( p ) {
		bdm_assert_debug ( p, "object_ptr cannot be empty" );
	}

	/*!
	  \brief Assignment operator
	*/
	object_ptr<T> &operator= ( const object_ptr<T> &other ) {
		shared_ptr<T>::operator= ( other );
		return *this;
	}
};

#define SHAREDPTR(class_name) typedef bdm::object_ptr< class_name > class_name##_ptr

#define SHAREDPTR2(template_class_name, template_parameter_name) typedef bdm::object_ptr< template_class_name < template_parameter_name > > template_class_name##_##template_parameter_name##_ptr

}

#endif
