/*
 * Copyright (c) 2021 Huawei Technologies Co.,Ltd.
 *
 * CM 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.
 * -------------------------------------------------------------------------
 *
 * cm_client_api.cpp
 *
 *
 * IDENTIFICATION
 *    src/cm_client/cm_client_api.cpp
 *
 * -------------------------------------------------------------------------
 */
#include <stdlib.h>
#include "cm/cm_elog.h"
#include "cm/cm_msg.h"
#include "cm_client.h"
#include "cm_client_api.h"

static char *g_jsonStrPtr = NULL;

static bool IsStrOverLength(const char *str, uint32 maxLen)
{
    uint32 len = 0;
    while (len < maxLen) {
        if (str[len++] == '\0') {
            return true;
        }
    }
    return false;
}

static bool CanDoCmInit(const char *resName)
{
    if (GetIsClientInit()) {
        write_runlog(LOG, "cm_client has init, can't do init again.\n");
        return false;
    }
    if (resName == NULL) {
        (void)printf(_("resName length is NULL.\n"));
        return false;
    }
    if (!IsStrOverLength(resName, CM_MAX_RES_NAME)) {
        (void)printf(_("resName length >= %d.\n"), CM_MAX_RES_NAME);
        return false;
    }
    return true;
}

ClientCmLockMsg *GetLockSendMsg(const char *lockName, LockOption opt)
{
    ClientCmLockMsg *sendMsg = (ClientCmLockMsg*) malloc(sizeof(ClientCmLockMsg));
    if (sendMsg == NULL) {
        write_runlog(ERROR, "out of memory, lock option = %u.\n", (uint32)opt);
        return NULL;
    }
    sendMsg->head.msgType = (uint32)MSG_CM_RES_LOCK;
    sendMsg->info.lockOpt = (uint32)opt;
    errno_t rc = strcpy_s(sendMsg->info.lockName, CM_MAX_LOCK_NAME, lockName);
    securec_check_errno(rc, (void)rc);
    return sendMsg;
}

int ResLockCore(const char *lockName)
{
    ClientCmLockMsg *sendMsg = GetLockSendMsg(lockName, CM_RES_LOCK);
    if (sendMsg == NULL) {
        write_runlog(ERROR, "generate (%s)lock msg failed.\n", lockName);
        return 1;
    }

    ClientLockResult lockResult = SendLockMsgAndWaitResult((char*)sendMsg, sizeof(ClientCmLockMsg));
    if (lockResult.error != 0) {
        write_runlog(ERROR, "(%s)lock fail, error=%u.\n", lockName, lockResult.error);
    } else {
        write_runlog(LOG, "(%s)lock success.\n", lockName);
    }

    return (int)lockResult.error;
}

int ResUnlockCore(const char *lockName)
{
    ClientCmLockMsg *sendMsg = GetLockSendMsg(lockName, CM_RES_UNLOCK);
    if (sendMsg == NULL) {
        write_runlog(ERROR, "generate (%s) unlock msg failed.\n", lockName);
        return 1;
    }

    ClientLockResult lockResult = SendLockMsgAndWaitResult((char*)sendMsg, sizeof(ClientCmLockMsg));
    if (lockResult.error != 0) {
        write_runlog(ERROR, "unlock fail, error=%u.\n", lockResult.error);
    } else {
        write_runlog(LOG, "unlock success.\n");
    }

    return (int)lockResult.error;
}

int ResGetLockOwnerCore(const char *lockName, unsigned int *instId)
{
    ClientCmLockMsg *sendMsg = GetLockSendMsg(lockName, CM_RES_GET_LOCK_OWNER);
    if (sendMsg == NULL) {
        write_runlog(ERROR, "generate (%s) get lock owner msg failed.\n", lockName);
        return 1;
    }

    ClientLockResult lockResult = SendLockMsgAndWaitResult((char*)sendMsg, sizeof(ClientCmLockMsg));
    if (lockResult.error != 0) {
        write_runlog(ERROR, "get lock owner fail, error=%u.\n", lockResult.error);
    } else {
        *instId = lockResult.ownerId;
    }

    return (int)lockResult.error;
}

int ResTransLockCore(const char *lockName, unsigned int instId)
{
    ClientCmLockMsg *sendMsg = GetLockSendMsg(lockName, CM_RES_LOCK_TRANS);
    if (sendMsg == NULL) {
        write_runlog(ERROR, "generate (%s) get lock owner msg failed.\n", lockName);
        return 1;
    }
    sendMsg->info.transInstId = instId;

    ClientLockResult lockResult = SendLockMsgAndWaitResult((char*)sendMsg, sizeof(ClientCmLockMsg));
    if (lockResult.error != 0) {
        write_runlog(ERROR, "trans lock owner failed, error=%u.\n", lockResult.error);
    } else {
        write_runlog(LOG, "trans lock owner to %u success.\n", instId);
    }

    return (int)lockResult.error;
}

bool CanDoLockOperate(const char *lockName)
{
    if (!GetIsClientInit()) {
        (void)printf(_("cm_client is not alive, please init cm_client first.\n"));
        return false;
    }
    if (lockName == NULL) {
        write_runlog(ERROR, "lock name is NULL.\n");
        return false;
    }
    if (!IsStrOverLength(lockName, CM_MAX_LOCK_NAME)) {
        write_runlog(ERROR, "lock name is too long, max len is %d.\n", CM_MAX_LOCK_NAME);
        return false;
    }
    return true;
}

bool CanDoGetLockOwner(const char *lockName, const unsigned int *instId)
{
    if (!CanDoLockOperate(lockName)) {
        return false;
    }
    if (instId == NULL) {
        write_runlog(ERROR, "input parameter is NULL, can't get lock owner.\n");
        return false;
    }
    return true;
}

#ifdef __cplusplus
extern "C" {
#endif

int CmInit(unsigned int instId, const char *resName, CmNotifyFunc func)
{
    static bool isFirstInit = true;
    if (!CanDoCmInit(resName)) {
        return -1;
    }
    if (PreInit(instId, resName, func, &isFirstInit) != CM_SUCCESS) {
        (void)printf(_("resName(%s) instanceId(%u) init cm_client failed.\n"), resName, instId);
        return -1;
    }
    bool &isClientInit = GetIsClientInit();
    isClientInit = false;
    if (CreateConnectAgentThread() != CM_SUCCESS || CreateSendMsgThread() != CM_SUCCESS ||
        CreateRecvMsgThread() != CM_SUCCESS) {
        write_runlog(LOG, "cm_client create thread failed.\n");
        ShutdownClient();
        return -1;
    }
    bool isSuccess = SendInitMsgAndGetResult(resName, instId);
    if (!isSuccess) {
        write_runlog(ERROR, "resName(%s) instanceId(%u) init client failed, can check agent.\n", resName, instId);
        ShutdownClient();
        return -1;
    }
    write_runlog(LOG, "resName(%s) instanceId(%u) init cm_client success.\n", resName, instId);
    isClientInit = true;
    return 0;
}

void CmClientFini()
{
    ShutdownClient();
    FreeClientMemory();
}

static void GetResStatJsonHead(char *jsonStr, uint32 strLen, const OneResStatList *statList)
{
    int ret = snprintf_s(jsonStr,
        strLen,
        strLen - 1,
        "{\"version\":%llu,\"res_name\":\"%s\",\"inst_count\":%u,\"inst_status\":[",
        statList->version,
        statList->resName,
        statList->instanceCount);
    securec_check_intval(ret, (void)ret);
}

static void GetResStatJsonInst(char *instInfo, uint32 strLen, const CmResStatInfo *instStat, bool isEnd)
{
    int ret = snprintf_s(instInfo,
        strLen,
        strLen - 1,
        "{\"node_id\":%u,\"cm_instance_id\":%u,\"res_instance_id\":%u,\"is_work_member\":%u,\"status\":%u}",
        instStat->nodeId,
        instStat->cmInstanceId,
        instStat->resInstanceId,
        instStat->isWorkMember,
        instStat->status);
    securec_check_intval(ret, (void)ret);
    if (!isEnd) {
        errno_t rc = strcat_s(instInfo, MAX_PATH_LEN, ",");
        securec_check_errno(rc, (void)rc);
    }
}

static void ResStatusToJsonStr(const OneResStatList *statList)
{
    const int maxJsonStrLen = 10240;
    char jsonStr[maxJsonStrLen] = {0};

    GetResStatJsonHead(jsonStr, maxJsonStrLen, statList);

    errno_t rc;
    for (uint32 i = 0; i < statList->instanceCount; ++i) {
        char instInfo[MAX_PATH_LEN] = {0};
        GetResStatJsonInst(instInfo, MAX_PATH_LEN, &statList->resStat[i], (i == (statList->instanceCount - 1)));
        rc = strcat_s(jsonStr, maxJsonStrLen, instInfo);
        securec_check_errno(rc, (void)rc);
    }
    rc = strcat_s(jsonStr, maxJsonStrLen, "]}");
    securec_check_errno(rc, (void)rc);

    g_jsonStrPtr = strdup(jsonStr);
}

char *CmGetResStats()
{
    OneResStatList *statusList = GetClientStatusList();
    if (statusList->version == 0) {
        write_runlog(LOG, "version is 0, statList is invalid.\n");
        return NULL;
    }
    ResStatusToJsonStr(statusList);
    return g_jsonStrPtr;
}

int CmFreeResStats(char *resStats)
{
    if (resStats == NULL) {
        write_runlog(ERROR, "res stat ptr is NULL, can't free.\n");
        return 1;
    }
    if (resStats != g_jsonStrPtr) {
        write_runlog(ERROR, "res stat ptr is not stat list ptr, can't free.\n");
        return 1;
    }
    FREE_AND_RESET(g_jsonStrPtr);
    return 0;
}

int CmResLock(const char *lockName)
{
    if (!CanDoLockOperate(lockName)) {
        return 1;
    }
    return ResLockCore(lockName);
}

int CmResUnlock(const char *lockName)
{
    if (!CanDoLockOperate(lockName)) {
        return 1;
    }
    return ResUnlockCore(lockName);
}

int CmResGetLockOwner(const char *lockName, unsigned int *instId)
{
    if (!CanDoGetLockOwner(lockName, instId)) {
        return 1;
    }
    return ResGetLockOwnerCore(lockName, instId);
}

int CmResTransLock(const char *lockName, unsigned int instId)
{
    if (!CanDoLockOperate(lockName)) {
        return 1;
    }
    return ResTransLockCore(lockName, instId);
}

#ifdef __cplusplus
}
#endif
