//
// Created by Ht on 2020/7/26.
//

#ifndef LIBDEVCPP_QDATAMEMORYPOOL_H
#define LIBDEVCPP_QDATAMEMORYPOOL_H

#include <list>
#include <vector>
#include <mutex>
#include <functional>
#include "QLogger.h"
#include <memory>
#include <unordered_map>



namespace qlibc {

    template <typename T, size_t BlockSize>
    class QObjectMemoryPool {
    public:
        using ObjectCreator = std::function<T*()>;
        using ObjectReleaser = std::function<void(T*)>;

        using QMemoryPoolObjectAccessor = std::function<void(T* obj)>;

    protected:
        std::list<T*>    blanks_;
        ObjectCreator*   creator_ = nullptr;
        ObjectReleaser*  relearer_ = nullptr;
        std::mutex       m_;
        unsigned long    ge_cnt_ = 0, re_cnt_ = 0;

    public:
        explicit QObjectMemoryPool(const ObjectCreator& creator) :
            creator_(new ObjectCreator(creator)) {
            for(size_t i=0;i<BlockSize;i++){
                blanks_.emplace_back(creator.operator()());
            }
        };

        explicit QObjectMemoryPool(const ObjectCreator& creator, const ObjectReleaser& releaser)  :
                creator_(new ObjectCreator(creator)), relearer_(new ObjectReleaser(releaser)) {
            setObjectCreator(creator);
            for(size_t i=0;i<BlockSize;i++){ // NOLINT(altera-unroll-loops)
                blanks_.emplace_back(creator.operator()());
            }
        };

        virtual ~QObjectMemoryPool() {
            delete creator_;
            for(auto* tp:blanks_) { // NOLINT(altera-unroll-loops)
                if(relearer_== nullptr)
                    delete tp;
                else
                    relearer_->operator()(tp);
            }
            blanks_.clear();
        };

        T* getBlankObject() {
            std::unique_lock<std::mutex> lock(m_);
            ge_cnt_++;
            T *p = nullptr;
            if(blanks_.empty()) {
                p = static_cast<T *>(creator_->operator()());
            } else {
                p = blanks_.front();
                blanks_.pop_front();
            }
            return p;
        }

        void releaseObject(T* message){
            re_cnt_++;
            std::unique_lock<std::mutex> lock(m_);
            checkMessageDuplicated(message);
            blanks_.emplace_back(message);
            if(re_cnt_ % 1000 == 0) {
                QEMark("----->Message pool size : %06d (after %08d / %08d access)]<-----", blanks_.size(), ge_cnt_, re_cnt_);
            }
        }

        void checkMessageDuplicated (T* obj) {
#ifdef DEBUG
            auto d = blanks_.rbegin();
            for(; d!=blanks_.rend(); d++) {
                auto e = d;
#else
                auto e = blanks_.rbegin();
#endif
                for(e++; e !=blanks_.rend(); e++) {
#ifdef DEBUG
                    if(*d==*e) {
#else
                        if(obj==*e) {
#endif
                        QErr("Duplicated message map!");
                        break;
                    }
                }
#ifdef DEBUG
            }
#endif
        }

        void invokeOnAllObject(const QObjectMemoryPool<T,BlockSize>::QMemoryPoolObjectAccessor& accessor) {
            std::unique_lock<std::mutex> locker_(m_);
            for(auto& it : blanks_) {
                accessor(it.second);
            }
        }

    protected:
        QObjectMemoryPool(const QObjectMemoryPool& obj) noexcept {}
    };

    template <typename T, size_t BlockSize>
    class QSharedObjectMemoryPool {
    public:
        using ObjectCreator = std::function<T*()>;
        using ObjectReleaser = std::function<void(std::shared_ptr<T>&)>;
        using QSharedMemoryPoolObjectAccessor = std::function<void(std::shared_ptr<T>& obj)>;
    protected:
        std::list<std::shared_ptr<T>>  blanks_;
        std::list<std::shared_ptr<T>>  pool_;           //用于保持外部不被释放
        ObjectCreator*      creator_ = nullptr;
        ObjectReleaser*     relearer_ = nullptr;
        std::mutex          m_;
        unsigned long       ge_cnt_ = 0, re_cnt_ = 0;
        bool                cleared_ = false;

    public:
        explicit QSharedObjectMemoryPool(const ObjectCreator& creator) : creator_(new ObjectCreator(creator)) {
            for(size_t i=0;i<BlockSize;i++){
                auto ref = std::shared_ptr<T>(creator.operator()());
                blanks_.emplace_back(ref);
                pool_.emplace_back(ref);
            }
        };

        explicit QSharedObjectMemoryPool(const ObjectCreator& creator, const ObjectReleaser& releaser) :
            creator_(new ObjectCreator(creator)) , relearer_(new ObjectReleaser(releaser)) {
            for(size_t i=0;i<BlockSize;i++) {
                auto ref = std::shared_ptr<T>(creator.operator()());
                blanks_.emplace_back(ref);
                pool_.emplace_back(ref);
            }
        };

        virtual ~QSharedObjectMemoryPool() {
            std::unique_lock<std::mutex> lock(m_);
            cleared_ = true;
            for(auto& tp:pool_) { // NOLINT(altera-unroll-loops)
                if(relearer_!= nullptr)
                    relearer_->operator()(tp);
                tp = nullptr;
            }
            blanks_.clear();
            pool_.clear();
            delete creator_;
            delete relearer_;
        };

//        //放入一个新对象。如果对象已存在，则返回原有智能指针、否则返回新创建的智能指针
//        //此处会创建新的指针
//        std::shared_ptr<T>& get(T* obj) {
//            std::unique_lock<std::mutex> lock(m_);
//            if(cleared_) return nullptr;
//            auto _it = pool_.find(obj);
//            if(_it!=pool_.end()) {
//                return _it->second;
//            } else {
//                pool_.insert(std::pair<T*, std::shared_ptr<T>>(obj, std::shared_ptr<T>(obj)));
//                return pool_.find(obj)->second;
//            }
//        }

        void checkMessageDuplicated (std::shared_ptr<T>& obj) {
#ifdef DEBUG
            auto d = blanks_.rbegin();
            for(; d!=blanks_.rend(); d++) {
                auto e = d;
#else
                auto e = blanks_.rbegin();
#endif
                for(e++; e !=blanks_.rend(); e++) {
#ifdef DEBUG
                    if(*d==*e) {
#else
                        if(obj.get()==(*e).get()) {
#endif
                        QErr("Duplicated message map!");
                        break;
                    }
                }
#ifdef DEBUG
            }
#endif
        }

        auto getBlankObject() -> std::shared_ptr<T> {
            std::unique_lock<std::mutex> lock(m_);
            if(cleared_) return nullptr;
            ge_cnt_++;
            if(blanks_.empty()) {
                auto ref = std::shared_ptr<T>(creator_->operator()());  //创建引用
                pool_.emplace_back(ref);
                return ref;
            } else {
                auto p = blanks_.front();   //拷贝一份引用值
                blanks_.pop_front();        //释放一份引用
                return p;
            }
        }

        void releaseObject(std::shared_ptr<T>& message) {
            re_cnt_++;
            std::unique_lock<std::mutex> lock(m_);
            if(cleared_) {
                message = nullptr;
                return;
            }
            checkMessageDuplicated(message);
            blanks_.emplace_back(message);  //拷贝一个指针引用
            message = nullptr;
            if (re_cnt_ % 1000 == 0) {
                QEMark("----->Message pool size : %06d (after %08d / %08d access)]<-----", blanks_.size(), ge_cnt_,
                       re_cnt_);
            }
        }

        void clear () {
            std::unique_lock<std::mutex> lock(m_);
            blanks_.clear();
            pool_.clear();
            QDInfo("Pool objects left : %d : %d ", blanks_.size(), pool_.size());
        }

        void invokeOnAllObject(const QSharedObjectMemoryPool<T,BlockSize>::QSharedMemoryPoolObjectAccessor& accessor) {
            std::unique_lock<std::mutex> locker_(m_);
            for(auto& it : pool_) {
                accessor(it);
            }
        }

    protected:
        QSharedObjectMemoryPool(const QSharedObjectMemoryPool& obj) noexcept {}
    };

    template <typename T>
    class JE_API QObjectSharedContext {
    public:
        using QObjShareCtxAccType = enum {
            OP_PUT = 0,     //设置
            OP_DEL,         //清理
            OP_GET,         //获取
            OP_ITO          //遍历
        };
        using QContextPoolObjectAccessor = std::function<void(std::shared_ptr<T>& obj, QObjectSharedContext<T>::QObjShareCtxAccType acc)>;

    protected:
        std::unordered_map<T*, std::shared_ptr<T>> objs_;
        std::recursive_mutex    oscm_;
        std::shared_ptr<T>      empty_;
        QObjectSharedContext<T>::QContextPoolObjectAccessor  *releaser_;
        QObjectSharedContext<T>::QContextPoolObjectAccessor  *debugger_ = nullptr;

    public:
        explicit QObjectSharedContext(const QObjectSharedContext<T>::QContextPoolObjectAccessor& r) :
            releaser_(new QObjectSharedContext<T>::QContextPoolObjectAccessor(r)) {}
        explicit QObjectSharedContext() : releaser_(nullptr) {}

        virtual ~QObjectSharedContext() {
            //自动清空所有内容
            std::unique_lock<std::recursive_mutex> locker_(oscm_);
            QMark("Finalizing memories, %3d objects left.", objs_.size());
            for(auto& it : objs_) {
                if(it.second.use_count()==1 && releaser_!= nullptr)
                    releaser_->operator()(it.second, OP_DEL);
#if 1 //def DEBUG_MEMORY
                if(debugger_!=nullptr)
                    debugger_->operator()(it.second, OP_DEL);
#endif
            }
#ifdef DEBUG
            printObjectsInPool();
#endif
            delete releaser_;
            delete debugger_;
        }

        inline void setReleaser(const QObjectSharedContext<T>::QContextPoolObjectAccessor& r) {
            delete releaser_;
            releaser_ = new QObjectSharedContext<T>::QContextPoolObjectAccessor(r);
        }

        inline void setDebugger(const QObjectSharedContext<T>::QContextPoolObjectAccessor& r) {
            delete debugger_;
            debugger_ = new QObjectSharedContext<T>::QContextPoolObjectAccessor(r);
        }

        //放入一个新对象。如果对象已存在，则返回原有智能指针、否则返回新创建的智能指针
        //此处会创建新的指针
        std::shared_ptr<T> get(T* obj) {
            if(obj==nullptr) return nullptr;
            std::unique_lock<std::recursive_mutex> locker_(oscm_);
            auto _it = objs_.find(obj);
            if(_it!=objs_.end()) {
                if(debugger_!=nullptr)
                    debugger_->operator()(_it->second, OP_GET);
                return _it->second;
            } else {
                auto spo = std::shared_ptr<T>(obj);
                objs_.insert(std::pair<T*, std::shared_ptr<T>>(obj, spo));
#ifdef DEBUG_MEMORY
                QDHLog("Getting object %p packed, %3d objects left.", obj, objs_.size());
                if(debugger_!=nullptr)
                    debugger_->operator()(spo, OP_PUT);
#endif
                return spo;
            }
        }

        //管理外部创建的对象引用
        std::shared_ptr<T> outer(std::shared_ptr<T>& spo) {
            if(spo==nullptr) return empty_;
            std::unique_lock<std::recursive_mutex> locker_(oscm_);
            auto* obj = spo.get();
            auto _it = objs_.find(obj);
            if(_it!=objs_.end()) {
                if(debugger_!=nullptr)
                    debugger_->operator()(spo, OP_GET);
                return _it->second;
            } else {
                objs_.insert(std::pair<T*, std::shared_ptr<T>>(obj, spo));    //拷贝引用
                if(debugger_!=nullptr)
                    debugger_->operator()(spo, OP_PUT);
#ifdef DEBUG_MEMORY
                QDHLog("Outer object %p packed, %3d objects left.", obj, objs_.size());
#endif
                return spo;
            }
        }

        //清理一个指针。如果原指针已被拷贝，则释放后、全部释放。
        void eraseObj(T* obj, bool debug = false) {
            std::unique_lock<std::recursive_mutex> locker_(oscm_);
            auto _it = objs_.find(obj);
            if (_it!= objs_.end() ) {
                if(releaser_ != nullptr && _it->second.use_count()==1)
                    releaser_->operator()(_it->second, OP_DEL);
                if(debugger_!=nullptr)
                    debugger_->operator()(_it->second, OP_DEL);
                objs_.erase(obj);
#ifdef DEBUG_MEMORY
                if(debug) {
                    QDHLog("Context object [%p] releasing, %3d objects left.", _it->first, objs_.size());
                    printObjectsInPool();
                }
#endif
            }
        }

        inline void eraseSp(const std::shared_ptr<T>& ptr) {
            erase(ptr.get());
        }

        void invokeOnAllObject(const QObjectSharedContext<T>::QContextPoolObjectAccessor& accessor) {
            std::unique_lock<std::recursive_mutex> locker_(oscm_);
            for(auto& it : objs_) {
                accessor(it.second, OP_ITO);
#ifdef DEBUG_MEMORY
                if(debugger_!=nullptr)
                    debugger_->operator()(it.second, OP_ITO);
#endif
            }
        }

        void printObjectsInPool() {
            for(auto& it : objs_) {
                if(debugger_!=nullptr)
                    debugger_->operator()(it.second, OP_ITO);
            }
        }

        //清理所有外部已无引用的对象，回收内存
        size_t gather() {
            std::unique_lock<std::recursive_mutex> locker_(oscm_);
            std::vector<T*> tod;
            for(auto& it : objs_) {
                if(it.second.use_count()==1) {
                    tod.emplace_back(it.first);
                }
            }
            for(auto* del : tod)
                eraseObj(del);
            auto r = tod.size();
            QDMark("Gathering released memories, %3d objects released, %3d objects left.", r, objs_.size());
//#ifdef DEBUG
//            printObjectsInPool();
//#endif
            return r;
        }

        static const char* AccessType (qlibc::QObjectSharedContext<T>::QObjShareCtxAccType ac) {
            switch (ac) {
                case qlibc::QObjectSharedContext<T>::OP_DEL:
                    return "del";
                case qlibc::QObjectSharedContext<T>::OP_GET:
                    return "get";
                case qlibc::QObjectSharedContext<T>::OP_ITO:
                    return "ito";
                case qlibc::QObjectSharedContext<T>::OP_PUT:
                    return "put";
                default:
                    return "unknown";
            }
        }
    };


#define SimpleMemPoolObjCreator(__type)  []()->__type*{ return new __type();}
#define ObjMemPool(__v,__t,__n) qlibc::QObjectMemoryPool<__t, __n> __v([]()->__t*{ return new __t();});
#define CreateObjMemPool(__t,__n) new qlibc::QObjectMemoryPool<__t, __n>([]()->__t{return new __t();})

#define ContextPoolAccessor(o, ac,t) (std::shared_ptr<t>& o, qlibc::QObjectSharedContext<t>::QObjShareCtxAccType ac)->void

}

#endif //LIBDEVCPP_QDATAMEMORYPOOL_H
