/*!
  \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->() {
        bdm_assert_debug ( payload, "dereferencing NULL shared pointer" );
        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 shared pointer" );
        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 shared pointer" );
        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 shared pointer" );
        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
