/*
 * 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 based on:
 *     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_WEAK_PTR_H
#define ECXX_CTL_WEAK_PTR_H


#include "SharedPtr.h"


/*
 * The eCxx's own implementation of WeakPtr<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 {


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

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

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

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

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

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

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

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

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

        inline ~WeakPtr()
        { decRef(); }

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

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

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

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

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

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

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

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

        inline SharedPtr<ValueT> lock() const
        {
            if( expired() ) return SharedPtr<ValueT>();

            SharedPtr<ValueT> ptr;
            ptr.resetRef(_ptr, _refCount);

            return ptr;
        }

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

        inline bool expired() const
        { return !_refCount | !_refCount->useCount(); }

        inline explicit operator bool() const
        { return !expired(); }

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

    private:
        RefCountBase* _refCount;
        ElementType*  _ptr;

    private:
        inline void resetRef(ElementType* ptr, RefCountBase* refCount)
        {
            if( refCount)  refCount->incWeakRef();
            if(_refCount) _refCount->decWeakRef();

            _refCount = refCount;
            _ptr      = ptr;
        }

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

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

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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


} // namespace Ext

} // namespace eCxx


#endif
