/*
 * Copyright (c) 2022 Huawei Technologies Co.,Ltd.
 *
 * DSS is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 *
 * dss_shm_hashmap.c
 *
 *
 * IDENTIFICATION
 *    src/common/dss_shm_hashmap.c
 *
 * -------------------------------------------------------------------------
 */

#include "dss_shm_hashmap.h"
#include "dss_ga.h"
#include "dss_log.h"
#include "dss_errno.h"
#include "dss_defs.h"

uint32 shm_hashmap_calc_bucket_idx(shm_hash_ctrl_t *hash_ctrl, uint32 hash)
{
    uint32 bucket_idx = hash & hash_ctrl->high_mask;
    if (bucket_idx > hash_ctrl->max_bucket) {
        bucket_idx &= hash_ctrl->low_mask;
    }
    return bucket_idx;
}

bool32 shm_hashmap_need_extend_and_redistribute(shm_hash_ctrl_t *hash_ctrl)
{
    if (hash_ctrl->bucket_num == hash_ctrl->bucket_limits && hash_ctrl->max_bucket == hash_ctrl->high_mask) {
        return CM_FALSE;
    }
    uint32 max_bucket = hash_ctrl->max_bucket;
    uint64 enums = 0;
    for (uint32 i = 0; i <= max_bucket; i++) {
        shm_hashmap_bucket_t *bucket = shm_hashmap_get_bucket(hash_ctrl, i, NULL);
        if (bucket != NULL) {
            enums += bucket->entry_num;
        }
    }
    return ((enums >= (uint64)(DSS_HASH_FILL_FACTOR * (hash_ctrl->max_bucket + 1))) &&
            ((hash_ctrl->bucket_num != hash_ctrl->bucket_limits) || (hash_ctrl->max_bucket != hash_ctrl->high_mask)));
}

status_t shm_hashmap_extend_segment(shm_hash_ctrl_t *hash_ctrl)
{
    uint32 segment_num = hash_ctrl->nsegments;
    uint32 objectid = ga_alloc_object(GA_SEGMENT_POOL, CM_INVALID_ID32);
    if (objectid == CM_INVALID_ID32) {
        DSS_THROW_ERROR(ERR_DSS_GA_ALLOC_OBJECT, GA_SEGMENT_POOL);
        return CM_ERROR;
    }
    shm_hashmap_bucket_t *addr = (shm_hashmap_bucket_t *)ga_object_addr(GA_SEGMENT_POOL, objectid);
    if (addr == NULL) {
        DSS_THROW_ERROR(ERR_DSS_GA_GET_ADDR, GA_SEGMENT_POOL, objectid);
        return CM_ERROR;
    }
    errno_t rc = memset_s(addr, DSS_BUCKETS_SIZE_PER_SEGMENT, 0, DSS_BUCKETS_SIZE_PER_SEGMENT);
    if (rc != EOK) {
        CM_THROW_ERROR(ERR_SYSTEM_CALL, rc);
        return CM_ERROR;
    }
    uint32 *dirs = (uint32 *)OFFSET_TO_ADDR(hash_ctrl->dirs);
    dirs[segment_num] = objectid;
    hash_ctrl->nsegments++;
    LOG_DEBUG_INF(
        "[HASHMAP]Succeed to extend segment, segment num is %u, object id is %u.", hash_ctrl->nsegments, objectid);
    return CM_SUCCESS;
}

shm_hashmap_bucket_t *shm_hashmap_get_bucket(shm_hash_ctrl_t *hash_ctrl, uint32 bucket_idx, uint32 *segment_objid)
{
    uint32 *dirs = (uint32 *)OFFSET_TO_ADDR(hash_ctrl->dirs);
    uint32 segment_idx = bucket_idx / DSS_BUCKETS_PER_SEGMENT;
    DSS_ASSERT_LOG(segment_idx < hash_ctrl->nsegments, "segment idx %u exceeds nsegments %u, bucket_idx is %u.",
        segment_idx, hash_ctrl->nsegments, bucket_idx);
    uint32 objectid = dirs[segment_idx];
    shm_hashmap_bucket_t *segment = (shm_hashmap_bucket_t *)ga_object_addr(GA_SEGMENT_POOL, objectid);
    if (segment == NULL) {
        DSS_THROW_ERROR(ERR_DSS_GA_GET_ADDR, GA_SEGMENT_POOL, objectid);
        return NULL;
    }
    if (segment_objid != NULL) {
        *segment_objid = objectid;
    }
    uint32 sub_bucket_idx = bucket_idx % DSS_BUCKETS_PER_SEGMENT;
    return &segment[sub_bucket_idx];
}

static status_t shm_hashmap_init_segments(shm_hash_ctrl_t *hash_ctrl)
{
    uint32 expect_segments = CM_ALIGN_CEIL(hash_ctrl->bucket_num, DSS_BUCKETS_PER_SEGMENT);
    for (uint32 i = 0; i < expect_segments; i++) {
        status_t status = shm_hashmap_extend_segment(hash_ctrl);
        if (status != CM_SUCCESS) {
            return status;
        }
    }
    return CM_SUCCESS;
}
int32 shm_hashmap_init(shm_hashmap_t *map, uint32 id, cm_oamap_compare_t compare_func)
{
    void *addr = NULL;
    uint32 shm_key;
    if (map == NULL) {
        LOG_DEBUG_ERR("Null pointer specified");
        return ERR_DSS_INVALID_PARAM;
    }
    map->hash_ctrl.bucket_limits = DSS_MAX_BUCKET_NUM;
    map->hash_ctrl.bucket_num = DSS_INIT_BUCKET_NUM;
    map->hash_ctrl.max_bucket = map->hash_ctrl.bucket_num - 1;
    map->hash_ctrl.high_mask = map->hash_ctrl.bucket_num - 1;
    map->hash_ctrl.low_mask = map->hash_ctrl.bucket_num - 1;
    map->hash_ctrl.func = compare_func;
    map->shm_id = id;
    map->not_extend = 1;
    uint64 size = DSS_MAX_SEGMENT_NUM * (uint32)sizeof(uint32_t);
    addr = cm_get_shm(SHM_TYPE_HASH, id, size, CM_SHM_ATTACH_RW);
    if (addr == NULL) {
        LOG_RUN_ERR("get hash map shm failed, id is %u.", id);
        return CM_ERROR;
    }
    shm_key = cm_shm_key_of(SHM_TYPE_HASH, id);
    map->hash_ctrl.dirs = cm_trans_shm_offset(shm_key, addr);
    errno_t err = memset_s(addr, size, 0, size);
    if (err != EOK) {
        CM_THROW_ERROR(ERR_SYSTEM_CALL, err);
        (void)cm_del_shm(SHM_TYPE_HASH, id);
        return CM_ERROR;
    }
    status_t status = shm_hashmap_init_segments(&map->hash_ctrl);
    if (status != CM_SUCCESS) {
        (void)cm_del_shm(SHM_TYPE_HASH, id);
        return CM_ERROR;
    }
    return CM_SUCCESS;
}

void shm_hashmap_destroy(shm_hashmap_t *map, uint32 id)
{
    CM_ASSERT(map != NULL);
    if (map->hash_ctrl.dirs != SHM_INVALID_ADDR) {
        (void)cm_del_shm(SHM_TYPE_HASH, id);
        map->hash_ctrl.dirs = SHM_INVALID_ADDR;
    }
}