/*
 * Copyright 2021 Rockchip Electronics Co. LTD
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define MODULE_TAG "mpp_mem_pool"

#include <string.h>

#include "mpp_err.h"
#include "mpp_env.h"
#include "mpp_mem.h"
#include "mpp_list.h"
#include "mpp_debug.h"

#include "mpp_mem_pool.h"

#define MPP_MEM_POOL_DBG_FLOW           (0x00000001)

#define mem_pool_dbg(flag, fmt, ...)    _mpp_dbg(mpp_mem_pool_debug, flag, fmt, ## __VA_ARGS__)
#define mem_pool_dbg_f(flag, fmt, ...)  _mpp_dbg_f(mpp_mem_pool_debug, flag, fmt, ## __VA_ARGS__)

#define mem_pool_dbg_flow(fmt, ...)     mem_pool_dbg(MPP_MEM_POOL_DBG_FLOW, fmt, ## __VA_ARGS__)

RK_U32 mpp_mem_pool_debug = 0;

typedef struct MppMemPoolNode_t {
    void                *check;
    struct list_head    list;
    void                *ptr;
    size_t              size;
} MppMemPoolNode;

typedef struct MppMemPoolImpl_t {
    void                *check;
    size_t              size;
    pthread_mutex_t     lock;
    struct list_head    service_link;

    struct list_head    used;
    struct list_head    unused;
    RK_S32              used_count;
    RK_S32              unused_count;

    /* extra flag for C++ static destruction order error */
    RK_S32              finalized;
} MppMemPoolImpl;

class MppMemPoolService
{
public:
    static MppMemPoolService* getInstance() {
        AutoMutex auto_lock(get_lock());
        static MppMemPoolService pool_service;
        return &pool_service;
    }
    static Mutex *get_lock() {
        static Mutex lock;
        return &lock;
    }

    MppMemPoolImpl *get_pool(size_t size);
    void put_pool(MppMemPoolImpl *impl);

private:
    MppMemPoolService();
    ~MppMemPoolService();
    struct list_head    mLink;
};

MppMemPoolService::MppMemPoolService()
{
    INIT_LIST_HEAD(&mLink);

    mpp_env_get_u32("mpp_mem_pool_debug", &mpp_mem_pool_debug, 0);
}

MppMemPoolService::~MppMemPoolService()
{
    if (!list_empty(&mLink)) {
        MppMemPoolImpl *pos, *n;

        list_for_each_entry_safe(pos, n, &mLink, MppMemPoolImpl, service_link) {
            put_pool(pos);
        }
    }
}

MppMemPoolImpl *MppMemPoolService::get_pool(size_t size)
{
    MppMemPoolImpl *pool = mpp_malloc(MppMemPoolImpl, 1);
    if (NULL == pool)
        return NULL;

    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&pool->lock, &attr);
    pthread_mutexattr_destroy(&attr);

    pool->check = pool;
    pool->size = size;
    pool->used_count = 0;
    pool->unused_count = 0;
    pool->finalized = 0;

    INIT_LIST_HEAD(&pool->used);
    INIT_LIST_HEAD(&pool->unused);
    INIT_LIST_HEAD(&pool->service_link);
    AutoMutex auto_lock(get_lock());
    list_add_tail(&pool->service_link, &mLink);

    return pool;
}

void MppMemPoolService::put_pool(MppMemPoolImpl *impl)
{
    MppMemPoolNode *node, *m;

    if (impl != impl->check) {
        mpp_err_f("invalid mem impl %p check %p\n", impl, impl->check);
        return;
    }

    if (impl->finalized)
        return;

    pthread_mutex_lock(&impl->lock);

    if (!list_empty(&impl->unused)) {
        list_for_each_entry_safe(node, m, &impl->unused, MppMemPoolNode, list) {
            MPP_FREE(node);
            impl->unused_count--;
        }
    }

    if (!list_empty(&impl->used)) {
        mpp_err_f("found %d used buffer size %d\n",
                  impl->used_count, impl->size);

        list_for_each_entry_safe(node, m, &impl->used, MppMemPoolNode, list) {
            MPP_FREE(node);
            impl->used_count--;
        }
    }

    if (impl->used_count || impl->unused_count)
        mpp_err_f("pool size %d found leaked buffer used:unused [%d:%d]\n",
                  impl->size, impl->used_count, impl->unused_count);

    pthread_mutex_unlock(&impl->lock);

    {
        AutoMutex auto_lock(get_lock());
        list_del_init(&impl->service_link);
    }

    impl->finalized = 1;
    mpp_free(impl);
}

MppMemPool mpp_mem_pool_init_f(const char *caller, size_t size)
{
    mem_pool_dbg_flow("pool %d init from %s", size, caller);

    return (MppMemPool)MppMemPoolService::getInstance()->get_pool(size);
}

void mpp_mem_pool_deinit_f(const char *caller, MppMemPool pool)
{
    MppMemPoolImpl *impl = (MppMemPoolImpl *)pool;

    mem_pool_dbg_flow("pool %d deinit from %s", impl->size, caller);

    MppMemPoolService::getInstance()->put_pool(impl);
}

void *mpp_mem_pool_get_f(const char *caller, MppMemPool pool)
{
    MppMemPoolImpl *impl = (MppMemPoolImpl *)pool;
    MppMemPoolNode *node = NULL;
    void* ptr = NULL;

    pthread_mutex_lock(&impl->lock);

    mem_pool_dbg_flow("pool %d get used:unused [%d:%d] from %s", impl->size,
                      impl->used_count, impl->unused_count, caller);

    if (!list_empty(&impl->unused)) {
        node = list_first_entry(&impl->unused, MppMemPoolNode, list);
        if (node) {
            list_del_init(&node->list);
            list_add_tail(&node->list, &impl->used);
            impl->unused_count--;
            impl->used_count++;
            ptr = node->ptr;
            goto DONE;
        }
    }

    node = mpp_malloc_size(MppMemPoolNode, sizeof(MppMemPoolNode) + impl->size);
    if (NULL == node) {
        mpp_err_f("failed to create node from size %d pool\n", impl->size);
        goto DONE;
    }

    node->check = node;
    node->ptr = (void *)(node + 1);
    node->size = impl->size;
    INIT_LIST_HEAD(&node->list);
    list_add_tail(&node->list, &impl->used);
    impl->used_count++;
    ptr = node->ptr;

DONE:
    pthread_mutex_unlock(&impl->lock);
    if (node)
        memset(node->ptr, 0 , node->size);
    return ptr;
}

void mpp_mem_pool_put_f(const char *caller, MppMemPool pool, void *p)
{
    MppMemPoolImpl *impl = (MppMemPoolImpl *)pool;
    MppMemPoolNode *node = (MppMemPoolNode *)((RK_U8 *)p - sizeof(MppMemPoolNode));

    if (impl != impl->check) {
        mpp_err_f("invalid mem pool %p check %p\n", impl, impl->check);
        return ;
    }

    if (node != node->check) {
        mpp_err_f("invalid mem pool ptr %p node %p check %p\n",
                  p, node, node->check);
        return ;
    }

    pthread_mutex_lock(&impl->lock);

    mem_pool_dbg_flow("pool %d put used:unused [%d:%d] from %s", impl->size,
                      impl->used_count, impl->unused_count, caller);

    list_del_init(&node->list);
    list_add(&node->list, &impl->unused);
    impl->used_count--;
    impl->unused_count++;

    pthread_mutex_unlock(&impl->lock);
}
