/*
 * 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_UNIQUE_PTR_H
#define ECXX_CTL_UNIQUE_PTR_H


#include "DefaultDeleter.h"
#include "RefOne.h"


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


/*
 * The eCxx's own implementation of UniquePtr<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 UniquePtr<> class
//
template <typename ValueT>
class UniquePtr;


//
// Helper functions
//
template <typename ValueT, typename ValueU>
inline RefOneBase* createRefOne(UniquePtr<ValueT>*, ValueU* p)
{ return new RefOne<ValueT>(p); }


template <typename ValueT>
inline RefOneBase* createRefOne(UniquePtr<ValueT[]>*, ValueT* p)
{ return new RefOneDel< ValueT, DefaultDeleter<ValueT[]> >( p, DefaultDeleter<ValueT[]>() ); }


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

    public:
        inline UniquePtr()
        : _refOne(nullptr)
        , _ptr   (nullptr)
        {}

        inline UniquePtr(ElementType* ptr)
        : _ptr(ptr)
        { _refOne = createRefOne(this, ptr); }

        template <typename ValueU>
        inline explicit UniquePtr(ValueU* ptr)
        : _refOne( createRefOne(this, ptr) )
        , _ptr   ( ptr )
        {}

        template <typename ValueU, typename DeleterT>
        inline UniquePtr(ValueU* ptr, DeleterT del)
        : _refOne( new RefOneDel<ValueU, DeleterT>(ptr, del) )
        , _ptr   ( ptr )
        {}

        inline UniquePtr(eCxx::nullptr_t)
        : _refOne(nullptr)
        , _ptr   (nullptr)
        {}

        template <typename DeleterT>
        inline UniquePtr(eCxx::nullptr_t, DeleterT del)
        : _refOne( new RefOneDel<ValueT, DeleterT>(nullptr, del) )
        , _ptr   ( nullptr )
        {}

        template <typename ValueU>
        inline UniquePtr(const UniquePtr<ValueU>& u, ValueT* ptr) = delete;

        template <typename ValueU>
        inline UniquePtr(UniquePtr<ValueU>& u, ValueT* ptr)
        : _refOne(nullptr)
        , _ptr   (nullptr)
        { swap(u); }

        inline UniquePtr(const UniquePtr& rhs) = delete;

        inline UniquePtr(UniquePtr& rhs)
        : _refOne(nullptr)
        , _ptr   (nullptr)
        { swap(rhs); }

        template <typename ValueU>
        inline UniquePtr(const UniquePtr<ValueU>& rhs) = delete;

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

        inline UniquePtr(UniquePtr&& rhs)
        : _refOne(rhs._refOne)
        , _ptr   (rhs._ptr)
        {
            rhs._refOne = nullptr;
            rhs._ptr    = nullptr;
        }

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

        inline ~UniquePtr()
        { release(); }

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

        inline UniquePtr& operator=(const UniquePtr& rhs) = delete;

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

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

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

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

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

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

        template <typename ValueU, typename DeleterT>
        inline void reset(ValueU* ptr, DeleterT del)
        { UniquePtr<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(UniquePtr& x)
        {
            eCxx::swap(_ptr,    x._ptr   );
            eCxx::swap(_refOne, x._refOne);
        }

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

    private:
        RefOneBase*  _refOne;
        ElementType* _ptr;

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

        inline void resetRef(ElementType* ptr, RefOneBase* refOne)
        {
            if(_refOne) _refOne->release();

            _refOne = refOne;
            _ptr    = ptr;
        }

        inline void release()
        {
            if(!_refOne) return;

            _refOne->release();
            _refOne = nullptr;
        }

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

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


//
// makeUniquePtr<>()
//
template <typename ValueT, typename... Args>
inline UniquePtr<ValueT> makeUniquePtr(Args&&... args)
{
    RefOneObj<ValueT>* obj = new RefOneObj<ValueT>( eCxx::forward<Args>(args)... );

    UniquePtr<ValueT> p;
    p.resetRef(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 UniquePtr<ValueT[]> makeUniquePtr(Args&&... args)
{
#ifndef ECXX_FOR_MAKE_UNIQUE_PTR_ARRAY_USE_ALIGNED_STORAGE
    return UniquePtr<ValueT[]>( new ValueT[N]{ eCxx::forward<Args>(args)... } );
#else
    RefOneObjSA<ValueT, N>* obj = new RefOneObjSA<ValueT, N>( eCxx::forward<Args>(args)... );

    UniquePtr<ValueT> p;
    p.resetRef(obj->get(), obj);

    return p;
#endif
}
#pragma GCC diagnostic pop


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


} // namespace Ext

} // namespace eCxx


#endif
