#ifndef _MONTY_RC_H_
#define _MONTY_RC_H_

#include <stddef.h>
#include <iostream>

namespace monty
{
  class RefCounted;

  template <class T>
  struct RefCounted_traits {
    typedef RefCounted parent;
  };

  ///\brief reference counted pointer
  template<typename T>
  class rc_ptr
  {
  private:
    //typedef typename T::_descended_from_RefCounted _validate_descended_from_RefCounted; // make sure we are descended from RefCounted
    T * ref;
    template<typename S> friend class rc_ptr; // Should be for T <: S, but we cant write that

    template<typename Y>
    using _Convertible = typename std::enable_if<std::is_constructible<T*,Y*>::value,rc_ptr<T> >::type;
  public:
    typedef T * pointer;
    typedef T & reference;

    rc_ptr();
    rc_ptr(pointer ptr); // from pointer
    rc_ptr(rc_ptr<T> && ptr);
    rc_ptr(const rc_ptr<T> & that); // const copy
    // To allow construction when S <: T like
    //   rc_ptr<S> s;
    //   rc_ptr<T> t(s);
    template<typename Y, typename = _Convertible<Y>>
    rc_ptr(const rc_ptr<Y> & r) noexcept : rc_ptr<T>(r.ref) { }


    ~rc_ptr();

    reference operator*()  const;
    pointer   operator->() const;
    rc_ptr<T> operator=(const rc_ptr<T> & other);
    rc_ptr<T> operator=(pointer other);

    bool      operator==(const rc_ptr<T> & other);

    pointer get() const;
    void reset();

    template<class S>
    rc_ptr<S> as() { return rc_ptr<S>(ref); }

    // cast to pointer to immediate super-class
    operator rc_ptr<typename RefCounted_traits<T>::parent>() { return rc_ptr<typename RefCounted_traits<T>::parent>(ref); }

    //operator bool() { return ref != nullptr; }
  };



  class RefCounted
  {
    typedef int _descended_from_RefCounted;
    public:
  //protected:
    RefCounted() : refcount(0) {}
  //private:
    typedef ptrdiff_t count_t;
    count_t refcount;
    count_t refincr() { return ++refcount; }
    count_t refdecr() { return --refcount; }

    template<typename T> friend class rc_ptr; // allow rc_ptr to fiddle with the private parts

  //public:
    ~RefCounted()
    {
      //std::cout << "delete " << this << std::endl;
      refcount = -9999;
    }
  };

  class rc_reserve
  {
  private:
    RefCounted * ref;
  public:
    rc_reserve(RefCounted * ref) : ref(ref) { ref->refincr(); }
    ~rc_reserve() { ref->refdecr(); }
  };

  //----------------------------------
  // rc_ptr implementation

  // constructors
  template<typename T> rc_ptr<T>::rc_ptr(pointer that)           : ref(that)
  {
    if (ref) ref->refincr();
  }
  //template<typename T>
  //template<typename S> rc_ptr<T>::rc_ptr(const rc_ptr<S> & that) : ref(that.ref)
  //{
  //  if (ref) ref->refincr();
  // }
  template<typename T> rc_ptr<T>::rc_ptr(const rc_ptr<T> & that) : ref(that.ref)
  {
    if (ref) ref->refincr();
  }

  template<typename T> rc_ptr<T>::rc_ptr(rc_ptr<T> && that) : ref(that.ref)
  {
      that.ref = NULL;;
  }

  template<typename T> rc_ptr<T>::rc_ptr() : ref(nullptr)
  {
  }


  // destructor
  template<typename T> rc_ptr<T>::~rc_ptr()
  {
    if (ref && ref->refdecr() == 0) delete ref;
  }

  // members

  template<typename T> typename rc_ptr<T>::reference rc_ptr<T>::operator*() const { return *ref; }
  template<typename T> typename rc_ptr<T>::pointer rc_ptr<T>::operator->()  const { return  ref; }
  template<typename T> void     rc_ptr<T>::reset()
  {
    if (ref)
      if (ref->refdecr() == 0)
        delete ref;
    ref = NULL;
  }
  template<typename T> typename rc_ptr<T>::pointer rc_ptr<T>::get() const         { return ref; }

  template<typename T> rc_ptr<T> rc_ptr<T>::operator=(const rc_ptr<T> & other)
  {
    if (other.ref != nullptr)
     other.ref->refincr(); // if this==other, we have to increment other before we decrement this.
    reset();
    ref = other.ref;
    return *this;
  }
  template<typename T> rc_ptr<T> rc_ptr<T>::operator=(T * other)
  {
    if (other != nullptr)
      other->refincr();
    reset();
    ref = other;
    return *this;
  }

  template<typename T> bool      rc_ptr<T>::operator==(const rc_ptr<T> & other) { return ref == other.ref; }

  template<typename T> bool operator==(const rc_ptr<T> & lhs,const rc_ptr<T> & rhs) { return lhs.get() == rhs.get(); }
  template<typename T> bool operator!=(const rc_ptr<T> & lhs,const rc_ptr<T> & rhs) { return lhs.get() != rhs.get(); }
  template<typename T> bool operator==(const rc_ptr<T> & lhs,const T * rhs) { return lhs.get() == rhs; }
  template<typename T> bool operator==(const T * lhs,const rc_ptr<T> & rhs) { return lhs == rhs.get(); }
  template<typename T> bool operator!=(const rc_ptr<T> & lhs,const T * rhs) { return lhs.get() != rhs; }
  template<typename T> bool operator!=(const T * lhs,const rc_ptr<T> & rhs) { return lhs != rhs.get(); }

}

#endif
