/*
 * 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_REF_COUNT_H
#define ECXX_CTL_REF_COUNT_H


#include <eCxx/Platform/Common/AtomicGuard.h>

#include "AlignedStorage.h"
#include "Utility.h"


//
// Uncomment this to enable reference-counter debugging
// (the system-wide debugging macro must be enabled for it to have any effect)
//
// #define REFCOUNT_DEBUG(FMT, ...) SYSTEM_DEBUG(FMT, ## __VA_ARGS__)

#ifndef REFCOUNT_DEBUG
    #define NO_REFCOUNT_DEBUG
    #define REFCOUNT_DEBUG(FMT, ...) SYSTEM_DEBUG_NOP
#endif


namespace eCxx {

namespace Ext {


//
// Base class of RefCount
//
// NOTE : It can only counts up to 255!
//
class RefCountBase {
    public:
        RefCountBase(const RefCountBase&) = delete;
        RefCountBase(RefCountBase&&) = delete;

        RefCountBase& operator=(const RefCountBase&) = delete;
        RefCountBase& operator=(RefCountBase&&) = delete;

    public:
        inline RefCountBase()
        {}

        inline virtual ~RefCountBase()
        {}

        inline void incRef()
        {
            REFCOUNT_DEBUG( "RefCountBase::incRef(this=%p, current_count=%zd)\n", this, static_cast<size_t>(refCount) );

            AtomicGuard g;

            ++refCount;
        }

        inline void decRef()
        {
            REFCOUNT_DEBUG( "RefCountBase::decRef(this=%p, current_count=%zd)\n", this, static_cast<size_t>(refCount) );

            {
                AtomicGuard g;

                --refCount;
                if(refCount) return;
            }

            destroy();

            {
                AtomicGuard g;

                if(refCountWeak) return;
            }

            deleteThis();
        }

        inline void incWeakRef()
        {
            REFCOUNT_DEBUG( "RefCountBase::incWeakRef(this=%p, current_count=%zd)\n", this, static_cast<size_t>(refCountWeak) );

            AtomicGuard g;

            ++refCountWeak;
        }

        inline void decWeakRef()
        {
            REFCOUNT_DEBUG( "RefCountBase::decWeakRef(this=%p, current_count=%zd)\n", this, static_cast<size_t>(refCountWeak) );

            {
                AtomicGuard g;

                --refCountWeak;
                if(refCount || refCountWeak) return;
            }

            deleteThis();
        }

        inline uint8_t useCount() const
        { return refCount; }

        virtual void destroy()    = 0; // Destroy the resource
        virtual void deleteThis() = 0; // Destroy the object by itself

    private:
        uint8_t refCount     = 1;
        uint8_t refCountWeak = 0;
};


//
// RefCount for pointer delete
//
template <typename ValueT>
class RefCount : public RefCountBase {
    public:
        RefCount(const RefCount&) = delete;
        RefCount(RefCount&&) = delete;

        RefCount& operator=(const RefCount&) = delete;
        RefCount& operator=(RefCount&&) = delete;

    public:
        inline RefCount(ValueT* ptr)
        : _ptr(ptr)
        { REFCOUNT_DEBUG("RefCount::CTOR(this=%p, ptr=%p)\n", this, _ptr); }

        inline virtual ~RefCount()
        { REFCOUNT_DEBUG("RefCount::DTOR(this=%p, ptr=%p)\n", this, _ptr); }

        inline virtual void destroy()
        {
            REFCOUNT_DEBUG("RefCount::destroy(%p)\n", _ptr);
            delete _ptr;
            _ptr = nullptr;
        }

        inline virtual void deleteThis()
        {
            REFCOUNT_DEBUG("RefCount::deleteThis(%p)\n", this);
            delete this;
        }

    private:
        ValueT* _ptr;
};


//
// RefCount for pointer delete with a custom deleter
//
template <typename ValueT, typename DeleterT>
class RefCountDel: public RefCountBase {
    public:
        RefCountDel(const RefCountDel&) = delete;
        RefCountDel(RefCountDel&&) = delete;

        RefCountDel& operator=(const RefCountDel&) = delete;
        RefCountDel& operator=(RefCountDel&&) = delete;

    public:
        inline RefCountDel(ValueT* ptr, DeleterT del)
        : _ptr(ptr)
        , _del(del)
        { REFCOUNT_DEBUG("RefCountDel::CTOR(this=%p, ptr=%p)\n", this, _ptr); }

        inline virtual ~RefCountDel()
        { REFCOUNT_DEBUG("RefCountDel::DTOR(this=%p, ptr=%p)\n", this, _ptr); }

        inline virtual void destroy()
        {
            REFCOUNT_DEBUG("RefCountDel::destroy(%p)\n", _ptr);
            _del(_ptr);
            _ptr = nullptr;
        }

        inline virtual void deleteThis()
        {
            REFCOUNT_DEBUG("RefCountDel::deleteThis(%p)\n", this);
            delete this;
        }

    private:
        ValueT*  _ptr;
        DeleterT _del;
};


//
// RefCount for object delete
//
template <typename ValueT>
class RefCountObj: public RefCountBase {
    public:
        RefCountObj(const RefCountObj&) = delete;
        RefCountObj(RefCountObj&&) = delete;

        RefCountObj& operator=(const RefCountObj&) = delete;
        RefCountObj& operator=(RefCountObj&&) = delete;

    public:
        template <typename... Args>
        inline RefCountObj(Args&&... args)
        {
            REFCOUNT_DEBUG("RefCountObj::CTOR(this=%p, obj=%p)\n", this, &_storage);
            new(&_storage) ValueT( eCxx::forward<Args>(args)... );
        }

        inline virtual ~RefCountObj()
        { REFCOUNT_DEBUG("RefCountObj::DTOR(this=%p, obj=%p)\n", this, &_storage); }

        inline ValueT* get()
        { return eCxx::launder( reinterpret_cast<ValueT*>(&_storage) ); }

        inline const ValueT* get() const
        { return eCxx::launder( reinterpret_cast<const ValueT*>(&_storage) ); }

        inline virtual void destroy()
        {
            REFCOUNT_DEBUG("RefCountObj::destroy(%p)\n", &_storage);
            get()->~ValueT();
        }

        inline virtual void deleteThis()
        {
            REFCOUNT_DEBUG("RefCountObj::deleteThis(%p)\n", this);
            delete this;
        }

    private:
        typename eCxx::alignedStorage<sizeof(ValueT), alignof(ValueT)>::Type _storage;
};


//
// RefCount for static-array-of-objects delete
//
template <typename ValueT, size_t N>
class RefCountObjSA: public RefCountBase {
    public:
        RefCountObjSA(const RefCountObjSA&) = delete;
        RefCountObjSA(RefCountObjSA&&) = delete;

        RefCountObjSA& operator=(const RefCountObjSA&) = delete;
        RefCountObjSA& operator=(RefCountObjSA&&) = delete;

    public:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnarrowing"
        template <typename... Args>
        inline RefCountObjSA(Args&&... args)
        {
            REFCOUNT_DEBUG("RefCountObjSA::CTOR(this=%p, obj=%p, N=%zd)\n", this, &_storage, N);
            new(&_storage) ValueT[N]{ eCxx::forward<Args>(args)... };
        }
#pragma GCC diagnostic pop

        inline virtual ~RefCountObjSA()
        { REFCOUNT_DEBUG("RefCountObjSA::DTOR(this=%p, obj=%p, N=%zd)\n", this, &_storage, N); }

        inline ValueT* get()
        { return eCxx::launder( reinterpret_cast<ValueT*>(&_storage) ); }

        inline const ValueT* get() const
        { return eCxx::launder( reinterpret_cast<const ValueT*>(&_storage) ); }

        inline virtual void destroy()
        {
            REFCOUNT_DEBUG("RefCountObjSA::destroy(%p, N=%zd)\n", &_storage, N);
            ValueT* ptr = get();
            for(size_t i = 0; i < N; ++i) {
                ptr->~ValueT();
                ++ptr;
            }
        }

        inline virtual void deleteThis()
        {
            REFCOUNT_DEBUG("RefCountObjSA::deleteThis(%p)\n", this);
            delete this;
        }

    private:
        typename eCxx::alignedStorage<sizeof(ValueT) * N, alignof(ValueT)>::Type _storage;
};


} // namespace Ext

} // namespace eCxx


#endif
