/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * Most of the code in this file is taken (with some modifications) from:
 *     Make a shared_ptr from Scratch
 *     http://qamichaelpeng.github.io/2014/11/20/sharepointer_1.html
 *     Posted on November 20, 2014 by QAMichaelPeng
 * ----------------------------------------------------------------------------------
 */


#ifndef ECXX_CTL_SHARED_PTR_H
#define ECXX_CTL_SHARED_PTR_H


#include "DefaultDeleter.h"
#include "RefCount.h"


//
// Uncomment this to use eCxx::alignedStorage<L, A> for allocating the objects when creating
// a SharedPtr<T[]> using the makeSharedPtr<T, N>() function
//
// NOTE : # This would make the objects and SharedPtr<T[]> to be allocated at the same time.
//        # However it would increase program memory (flash) and RAM usage!
//
// #define ECXX_MAKE_SHARED_PTR_ARRAY_USE_ALIGNED_STORAGE


/*
 * The eCxx's own implementation of SharedPtr<T> is slightly different and in certain
 * areas may be more complete and flexible than those from the C++ STL library which is
 * included with the NodeMCU SDK.
 *
 * To reduce the possibility of conflicts, the class and its utility functions are wrapped
 * in an additional namespace.
 *
 * NOTE : # This class would be useful on AVRs where its C++ compiler does not include an
 *          implementation of the C++ STL library.
 *        # However, using many dynamic memory allocation on an MCU with very limited RAM
 *          can cause severe heap fragmentation problems.
 */


namespace eCxx {

namespace Ext {


//
// Forward declaration of the SharedPtr<> class
//
template <typename ValueT>
class SharedPtr;


//
// Helper functions
//
template <typename ValueT, typename ValueU>
inline RefCountBase* createRefCount(SharedPtr<ValueT>*, ValueU* p)
{ return new RefCount<ValueT>(p); }


template <typename ValueT>
inline RefCountBase* createRefCount(SharedPtr<ValueT[]>*, ValueT* p)
{ return new RefCountDel< ValueT, DefaultDeleter<ValueT[]> >( p, DefaultDeleter<ValueT[]>() ); }


//
// A simple shared-pointer class
//
template <typename ValueT>
class SharedPtr {
    public:
        typedef typename eCxx::removeExtent<ValueT>::Type ElementType;

    public:
        inline SharedPtr()
        : _refCount(nullptr)
        , _ptr     (nullptr)
        {}

        inline SharedPtr(ElementType* ptr)
        : _ptr(ptr)
        { _refCount = createRefCount(this, ptr); }

        template <typename ValueU>
        inline explicit SharedPtr(ValueU* ptr)
        : _refCount( createRefCount(this, ptr) )
        , _ptr     ( ptr )
        {}

        template <typename ValueU, typename DeleterT>
        inline SharedPtr(ValueU* ptr, DeleterT del)
        : _refCount( new RefCountDel<ValueU, DeleterT>(ptr, del) )
        , _ptr     ( ptr )
        {}

        inline SharedPtr(eCxx::nullptr_t)
        : _refCount(nullptr)
        , _ptr     (nullptr)
        {}

        template <typename DeleterT>
        inline SharedPtr(eCxx::nullptr_t, DeleterT del)
        : _refCount( new RefCountDel<ValueT, DeleterT>(nullptr, del) )
        , _ptr     ( nullptr )
        {}

        template <typename ValueU>
        inline SharedPtr(const SharedPtr<ValueU>& u, ValueT* ptr)
        : _refCount(nullptr)
        , _ptr     (nullptr)
        { resetRef(ptr, u._refCount); }

        inline SharedPtr(const SharedPtr& rhs)
        : _refCount(nullptr)
        , _ptr     (nullptr)
        { resetRef(rhs._ptr, rhs._refCount); }

        template <typename ValueU>
        inline SharedPtr(const SharedPtr<ValueU>& rhs)
        : _refCount(nullptr)
        , _ptr     (nullptr)
        { resetRef(rhs._ptr, rhs._refCount); }

        inline SharedPtr(SharedPtr&& rhs)
        : _refCount(rhs._refCount)
        , _ptr     (rhs._ptr)
        {
            rhs._refCount = nullptr;
            rhs._ptr      = nullptr;
        }

        template <typename ValueU>
        inline SharedPtr(SharedPtr<ValueU>&& rhs)
        : _refCount(rhs._refCount)
        , _ptr     (rhs._ptr)
        {
            rhs._refCount = nullptr;
            rhs._ptr      = nullptr;
        }

        inline ~SharedPtr()
        { decRef(); }

        inline SharedPtr& operator=(eCxx::nullptr_t)
        {
            resetRef(nullptr, nullptr);
            return *this;
        }

        inline SharedPtr& operator=(const SharedPtr& rhs)
        {
            resetRef(rhs._ptr, rhs._refCount);
            return *this;
        }

        template <typename ValueU>
        inline SharedPtr& operator=(const SharedPtr<ValueU>& rhs)
        {
            resetRef(rhs._ptr, rhs._refCount);
            return *this;
        }

        inline SharedPtr& operator=(SharedPtr&& rhs)
        {
            resetRef(nullptr, nullptr);
            swap(rhs);
            return *this;
        }

        template <typename ValueU>
        inline SharedPtr& operator=(SharedPtr<ValueU>&& rhs)
        {
            resetRef(nullptr, nullptr);
            swap(rhs);
            return *this;
        }

        inline void reset()
        { resetRef(nullptr, nullptr); }

        template <typename ValueU>
        inline void reset(ValueU* ptr)
        { SharedPtr<ValueT>(ptr).swap(*this); }

        template <typename ValueU, typename DeleterT>
        inline void reset(ValueU* ptr, DeleterT del)
        { SharedPtr<ValueT>(ptr, del).swap(*this); }

        inline ElementType* get() const
        { return _ptr; }

        inline ElementType& operator*() const
        { return *get(); }

        inline ElementType* operator->() const
        { return get(); }

        inline ElementType& operator[](size_t index) const
        { return _ptr[index]; }

        inline void swap(SharedPtr& x)
        {
            eCxx::swap(_ptr,      x._ptr     );
            eCxx::swap(_refCount, x._refCount);
        }

        inline uint8_t useCount() const
        {
            if(!_refCount) return 0;
            return _refCount->useCount();
        }

        inline bool unique() const
        { return useCount() == 1; }

        inline explicit operator bool() const
        { return get() != nullptr; }

        template <typename ValueU> bool ownerBefore(SharedPtr<ValueU>& rhs)
        { return _refCount < rhs._refCount; }

    private:
        RefCountBase* _refCount;
        ElementType*  _ptr;

    private:
        template <typename ValueU>
        inline void swap(SharedPtr<ValueU>& x)
        {
            eCxx::swap(_ptr,      x._ptr     );
            eCxx::swap(_refCount, x._refCount);
        }

        // Reset without increasing reference
        inline void resetRef_0(ElementType* ptr, RefCountBase* refCount)
        {
            if(_refCount) _refCount->decRef();

            _refCount = refCount;
            _ptr      = ptr;
        }

        inline void resetRef(ElementType* ptr, RefCountBase* refCount)
        {
            if(refCount) refCount->incRef();

            resetRef_0(ptr, refCount);
        }

        inline void decRef()
        {
            if(!_refCount) return;

            _refCount->decRef();
            _refCount = nullptr;
        }

        // Friend class and functions
        template <typename ValueU> friend class SharedPtr;
        template <typename ValueU> friend class WeakPtr;

        template <typename ValueU, typename... Args> friend SharedPtr<ValueU> makeSharedPtr(Args&&... args);
        template <typename ValueU, size_t N, typename... Args> friend SharedPtr<ValueU[]> makeSharedPtr(Args&&... args);
};


//
// makeSharedPtr<>()
//
template <typename ValueT, typename... Args>
inline SharedPtr<ValueT> makeSharedPtr(Args&&... args)
{
    RefCountObj<ValueT>* obj = new RefCountObj<ValueT>( eCxx::forward<Args>(args)... );

    SharedPtr<ValueT> p;
    p.resetRef_0(obj->get(), obj);

    return p;
}


#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnarrowing"
// NOTE : This helper function should only be used mostly to allocate array of objects
//        of which constructors only take one parameter each.
template <typename ValueT, size_t N, typename... Args>
inline SharedPtr<ValueT[]> makeSharedPtr(Args&&... args)
{
#ifndef ECXX_MAKE_SHARED_PTR_ARRAY_USE_ALIGNED_STORAGE
    return SharedPtr<ValueT[]>( new ValueT[N]{ eCxx::forward<Args>(args)... } );
#else
    RefCountObjSA<ValueT, N>* obj = new RefCountObjSA<ValueT, N>( eCxx::forward<Args>(args)... );

    SharedPtr<ValueT> p;
    p.resetRef_0(obj->get(), obj);

    return p;
#endif
}
#pragma GCC diagnostic pop


//
// Utility functions
//
template <typename ValueT>
inline void swap(SharedPtr<ValueT>& lhs, SharedPtr<ValueT>& rhs)
{ lhs.swap(rhs); }


template <typename ValueT, typename ValueU>
inline bool operator==(const SharedPtr<ValueT>& lhs, const SharedPtr<ValueU>& rhs)
{ return lhs.get() == rhs.get(); }


template <typename ValueT, typename ValueU>
inline bool operator!=(const SharedPtr<ValueT>& lhs, const SharedPtr<ValueU>& rhs)
{ return lhs.get() != rhs.get(); }


template <typename ValueT, typename ValueU>
inline bool operator<(const SharedPtr<ValueT>& lhs, const SharedPtr<ValueU>& rhs)
{ return lhs.get() < rhs.get(); }


template <typename ValueT, typename ValueU>
inline bool operator>(const SharedPtr<ValueT>& lhs, const SharedPtr<ValueU>& rhs)
{ return lhs.get() > rhs.get(); }


template <typename ValueT, typename ValueU>
inline bool operator<=(const SharedPtr<ValueT>& lhs, const SharedPtr<ValueU>& rhs)
{ return lhs.get() <= rhs.get(); }


template <typename ValueT, typename ValueU>
inline bool operator>=(const SharedPtr<ValueT>& lhs, const SharedPtr<ValueU>& rhs)
{ return lhs.get() >= rhs.get(); }


template <typename ValueT>
inline bool operator==(eCxx::nullptr_t, const SharedPtr<ValueT>& rhs)
{ return nullptr == rhs.get(); }


template <typename ValueT>
inline bool operator!=(eCxx::nullptr_t, const SharedPtr<ValueT>& rhs)
{ return nullptr != rhs.get(); }


template <typename ValueT>
inline bool operator<(eCxx::nullptr_t, const SharedPtr<ValueT>& rhs)
{ return nullptr < rhs.get(); }


template <typename ValueT>
inline bool operator>(eCxx::nullptr_t, const SharedPtr<ValueT>& rhs)
{ return nullptr > rhs.get(); }


template <typename ValueT>
inline bool operator<=(eCxx::nullptr_t, const SharedPtr<ValueT>& rhs)
{ return nullptr <= rhs.get(); }


template <typename ValueT>
inline bool operator>=(eCxx::nullptr_t, const SharedPtr<ValueT>& rhs)
{ return nullptr >= rhs.get(); }


template <typename ValueT>
inline bool operator==(const SharedPtr<ValueT>& lhs, eCxx::nullptr_t)
{ return lhs.get() == nullptr; }


template <typename ValueT>
inline bool operator!=(const SharedPtr<ValueT>& lhs, eCxx::nullptr_t)
{ return lhs.get() != nullptr; }


template <typename ValueT>
inline bool operator<(const SharedPtr<ValueT>& lhs, eCxx::nullptr_t)
{ return lhs.get() < nullptr; }


template <typename ValueT>
inline bool operator>(const SharedPtr<ValueT>& lhs, eCxx::nullptr_t)
{ return lhs.get() > nullptr; }


template <typename ValueT>
inline bool operator<=(const SharedPtr<ValueT>& lhs, eCxx::nullptr_t)
{ return lhs.get() <= nullptr; }


template <typename ValueT>
inline bool operator>=(const SharedPtr<ValueT>& lhs, eCxx::nullptr_t)
{ return lhs.get() >= nullptr; }


} // namespace Ext

} // namespace eCxx


#endif
