/*!
  \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 {
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 &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
