/*
 * 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_REF_ONE_H
#define ECXX_CTL_REF_ONE_H


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


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

#ifndef REFONE_DEBUG
    #define NO_REFONE_DEBUG
    #define REFONE_DEBUG(FMT, ...) SYSTEM_DEBUG_NOP
#endif


namespace eCxx {

namespace Ext {


//
// Base class of RefOne
//
class RefOneBase {
    public:
        RefOneBase(const RefOneBase&) = delete;
        RefOneBase(RefOneBase&&) = delete;

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

    public:
        inline RefOneBase()
        {}

        inline virtual ~RefOneBase()
        {}

        virtual void release() = 0; // Destroy the resource and the object by itself
};


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

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

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

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

        inline virtual void release()
        {
            REFONE_DEBUG("RefOne::destroy(%p)\n", _ptr);
            delete _ptr;
            _ptr = nullptr;

            REFONE_DEBUG("RefOne::deleteThis(%p)\n", this);
            delete this;
        }

    private:
        ValueT* _ptr;
};


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

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

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

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

        inline virtual void release()
        {
            REFONE_DEBUG("RefOneDel::destroy(%p)\n", _ptr);
            _del(_ptr);
            _ptr = nullptr;

            REFONE_DEBUG("RefOneDel::deleteThis(%p)\n", this);
            delete this;
        }

    private:
        ValueT*  _ptr;
        DeleterT _del;
};


//
// RefOne for object delete
//
template <typename ValueT>
class RefOneObj: public RefOneBase {
    public:
        RefOneObj(const RefOneObj&) = delete;
        RefOneObj(RefOneObj&&) = delete;

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

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

        inline virtual ~RefOneObj()
        { REFONE_DEBUG("RefOneObj::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 release()
        {
            REFONE_DEBUG("RefOneObj::destroy(%p)\n", &_storage);
            get()->~ValueT();

            REFONE_DEBUG("RefOneObj::deleteThis(%p)\n", this);
            delete this;
        }

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


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

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

    public:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnarrowing"
        template <typename... Args>
        inline RefOneObjSA(Args&&... args)
        {
            REFONE_DEBUG("RefOneObjSA::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 ~RefOneObjSA()
        { REFONE_DEBUG("RefOneObjSA::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 release()
        {
            REFONE_DEBUG("RefOneObjSA::destroy(%p, N=%zd)\n", &_storage, N);
            ValueT* ptr = get();
            for(size_t i = 0; i < N; ++i) {
                ptr->~ValueT();
                ++ptr;
            }

            REFONE_DEBUG("RefOneObjSA::deleteThis(%p)\n", this);
            delete this;
        }

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


} // namespace Ext

} // namespace eCxx


#endif
