/** @file

    $Id$

    $Date$
    $Author$

    @copy &copy; Marc W&auml;ckerlin
    @license LGPL, see file <a href="license.html">COPYING</a>

*/
#ifndef __MRW__SMARTPOINTER_HPP__
#define __MRW__SMARTPOINTER_HPP__

namespace mrw {

  class PointerCounter {
    private:
      unsigned int _cnt;
    public:
      PointerCounter() throw():
        _cnt(1) {
      }
      PointerCounter* incr() throw() {
        ++_cnt;
        return this;
      }
      int decr() throw() {
        return --_cnt;
      }
      int get() throw() {
        return _cnt;
      }
  };
  
  class SmartPointerParent {
    protected:
      template<class TYPE>
        PointerCounter* getCounter(TYPE& sp) throw() {
          return sp._cnt;
      }
      template<class TYPE>
        typename TYPE::Pointer getPointer(TYPE& sp) throw() {
          return sp._ptr;
      }
  };
  
  /** @addtogroup AutoTools */
  //@{
  
  /** @brief Smart Pointer Implementation
      @pre \#include <mrw/smartpointer.hpp>
      
      This is a smart pointer that can be casted withing the
      inheritance of the pointer it is storing.

      A smart pointer is a pointer that is automatically cleaned up
      when it is no more referenced. Therefore you only allocate
      memory, but you never free it, just as in Java, cleaning up is
      done behind the scenes. If you assign a smart pointer to another
      smart pointer, both point to the same memory. A counter counts
      the number of references to the object and frees it as soon as
      the last smart pointer pointing to the same memory has been
      destroyed.

      A smart pointer is used just as a normal pointer, except that
      you can assign it only to other smart pointers, not to ordinary
      pointers, and you don't have to delete the memory allocated. Any
      memory assigned to a smart pointer is consumed and mustn't be
      used any more. Never allocate a smart pointer with
      <code>new</code>, but only the pointer that is stored in the
      smart pointer!

      @note memory assigned to a smart pointer is consumed
  */  
  template<class TYPE> class SmartPointer: public SmartPointerParent {
    private:
      typedef TYPE* Pointer;
      PointerCounter* _cnt;
      TYPE* _ptr;
    private:
      void drop() throw() {
        if (_cnt && !_cnt->decr()) {
          delete _cnt; _cnt=0;
          delete _ptr; _ptr=0;
        }
      }
    private:
      friend class SmartPointerParent;
      friend class SmartPointerTest;
    public:
      SmartPointer() throw():
        _cnt(0), _ptr(0) {
      }
      SmartPointer(const SmartPointer<TYPE>& o) throw():
        _cnt(o._cnt?o._cnt->incr():0), _ptr(o._ptr) {
      }
      SmartPointer(TYPE* ptr) throw():
        _cnt(ptr ? new PointerCounter : 0), _ptr(ptr) {
      }
      template<class OTHER> SmartPointer(const SmartPointer<OTHER>& o) throw():
        _cnt(0), _ptr(dynamic_cast<TYPE*>(getPointer(o))) {
          if (_ptr) _cnt = getCounter(o)->incr();
      }
      ~SmartPointer() throw() {
        drop();
      }
      SmartPointer& operator=(const SmartPointer<TYPE>& o) throw() {
        if (o._ptr==_ptr) return *this;
        drop();
        _cnt = o._cnt ? o._cnt->incr() : 0;
        _ptr = o._ptr;
        return *this;
      }
      SmartPointer& operator=(TYPE* ptr) throw() {
        if (ptr==_ptr) return *this;
        drop();
        _cnt = ptr ? new PointerCounter : 0;
        _ptr = ptr;
        return *this;
      }
      template<class OTHER>
      SmartPointer& operator=(const SmartPointer<OTHER>& o) throw() {
        if (getPointer(o)==_ptr) return *this;
        drop();
        _ptr = dynamic_cast<TYPE*>(getPointer(o));
        _cnt = _ptr ? getCounter(o)->incr() : 0;
	return *this;
      }
      TYPE& operator*() throw() {
        return *_ptr;
      }
      const TYPE& operator*() const throw() {
        return *_ptr;
      }
      TYPE* const operator->() throw() {
        return _ptr;
      }
      const TYPE* const operator->() const throw() {
        return _ptr;
      }
      operator bool() throw() {
        return _ptr!=0;
      }
  };
  //@}
}
#endif
