/*!
  \file
  \brief BDM's own smart pointer.
  \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 "itpp_ext.h"

namespace bdm {

//! 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, _if_ it was
// included in the standard libraries of all supported compilers - but
// that's exactly what remains to be seen...
template <typename T>
class shared_ptr {
	template<class U> friend class shared_ptr;

private:
	T *payload;
	unsigned *refCnt;

public:
	//! 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).
	shared_ptr ( T *p ) :
			payload ( p ),
			refCnt ( p ? new unsigned ( 1 ) : 0 ) {
	}

	//! 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();
	}

	//! 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();
	}

	~shared_ptr() {
		del_ref();
	}

	shared_ptr &operator= ( const shared_ptr &other ) {
		other.add_ref();
		del_ref();

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

		return *this;
	}

	//! Returns the stored pointer (which remains owned by this
	//! instance).
	T *get() {
		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.
	T *operator->() {
		it_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.
	T &operator*() {
		it_assert_debug ( payload, "dereferencing NULL" );
		return *payload;
	}

	//! Returns the stored pointer (which remains owned by this
	//! instance).
	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 {
		it_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 {
		it_assert_debug ( payload, "dereferencing NULL" );
		return *payload;
	}

	bool unique() const {
		return refCnt && ( *refCnt == 1 );
	}

	long use_count() const {
		return refCnt ? *refCnt : 0;
	}

	operator bool() const {
		return payload;
	}

	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;
			}
		}
	}
};

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

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

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

}

#endif
