/*
* Copyright (c) 2022 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.
* -------------------------------------------------------------------------
*
* ctl_common_res.cpp
*      cm_ctl common res functions
*
* IDENTIFICATION
*    src/cm_ctl/ctl_common_res.cpp
*
* -------------------------------------------------------------------------
*/
#include "cm/libpq-fe.h"
#include "cm/libpq-int.h"
#include "cm/cm_misc.h"
#include "cm/cm_msg.h"
#include "ctl_common.h"
#include "ctl_common_res.h"

static status_t ResInstCheckConCms(CM_Conn **pCmsCon)
{
    do_conn_cmserver(false, 0, false, pCmsCon);
    if (pCmsCon == NULL) {
        return CM_ERROR;
    } else {
        if (CMPQstatus(*pCmsCon) != CONNECTION_OK) {
            FINISH_CONNECTION_WITHOUT_EXITCODE(*pCmsCon);
            return CM_ERROR;
        }
    }
    return CM_SUCCESS;
}

static inline void ResInstCheckGetQueryMsg(QueryOneResInstStat *queryMsg, uint32 resInstId)
{
    queryMsg->msgType = (int)MSG_CTL_CM_QUERY_RES_INST;
    queryMsg->instId = resInstId;
}

static inline void NodeCheckGetQueryMsg(QueryOneNodeStatSt *queryMsg, uint32 nodeId)
{
    queryMsg->msgType = (int)MSG_CTL_CM_QUERY_NODE;
    queryMsg->nodeId = nodeId;
}

static bool NodeCheckGetResult(CM_Conn *pCmsCon)
{
    struct timespec timeBegin = {0, 0};
    (void)clock_gettime(CLOCK_MONOTONIC, &timeBegin);
    for (;;) {
        if (cm_client_flush_msg(pCmsCon) == TCP_SOCKET_ERROR_EPIPE) {
            break;
        }
        CM_BREAK_IF_TRUE(IsTimeOut(&timeBegin, "[NodeCheckGetResult]"));
        char *recvMsg = recv_cm_server_cmd(pCmsCon);
        while (recvMsg != NULL) {
            cm_msg_type *msgTypePtr = (cm_msg_type*)recvMsg;
            if (msgTypePtr->msg_type == (int)MSG_CM_CTL_QUERY_NODE_ACK) {
                CmsToCtlOneNodeStat *ackMsg = (CmsToCtlOneNodeStat*)recvMsg;
                return (ResStatus)ackMsg->isNodeOnline;
            }
            write_runlog(DEBUG1, "unknown the msg type is %d.\n", msgTypePtr->msg_type);
            recvMsg = recv_cm_server_cmd(pCmsCon);
            CmUsleep(CTL_RECV_CYCLE);
        }
    }
    return false;
}

static ResStatus ResInstCheckGetResult(CM_Conn *pCmsCon)
{
    struct timespec timeBegin = {0, 0};
    (void)clock_gettime(CLOCK_MONOTONIC, &timeBegin);
    for (;;) {
        if (cm_client_flush_msg(pCmsCon) == TCP_SOCKET_ERROR_EPIPE) {
            break;
        }
        CM_BREAK_IF_TRUE(IsTimeOut(&timeBegin, "[ResInstCheckGetResult]"));
        char *recvMsg = recv_cm_server_cmd(pCmsCon);
        while (recvMsg != NULL) {
            cm_msg_type *msgTypePtr = (cm_msg_type*)recvMsg;
            if (msgTypePtr->msg_type == (int)MSG_CM_CTL_QUERY_RES_INST_ACK) {
                CmsToCtlOneResInstStat *ackMsg = (CmsToCtlOneResInstStat*)recvMsg;
                return (ResStatus)ackMsg->instStat.status;
            }
            write_runlog(DEBUG1, "unknown the msg type is %d.\n", msgTypePtr->msg_type);
            recvMsg = recv_cm_server_cmd(pCmsCon);
            CmUsleep(CTL_RECV_CYCLE);
        }
    }

    return CM_RES_STAT_UNKNOWN;
}

ResStatus GetResInstStatus(uint32 instId)
{
    CM_Conn *pCmsCon = NULL;
    if (ResInstCheckConCms(&pCmsCon) != CM_SUCCESS) {
        write_runlog(DEBUG1, "connect cms primary failed.\n");
        return CM_RES_STAT_UNKNOWN;
    }

    QueryOneResInstStat queryMsg = {0};
    ResInstCheckGetQueryMsg(&queryMsg, instId);
    if (cm_client_send_msg(pCmsCon, 'C', (char*)&queryMsg, sizeof(queryMsg)) != 0) {
        write_runlog(DEBUG1, "GetResInstStatus send query one res inst msg to cms fail!\n");
        FINISH_CONNECTION_WITHOUT_EXITCODE(pCmsCon);
        return CM_RES_STAT_UNKNOWN;
    }

    ResStatus result = ResInstCheckGetResult(pCmsCon);
    FINISH_CONNECTION_WITHOUT_EXITCODE(pCmsCon);

    return result;
}

bool IsNodeStatOnline(uint32 nodeId)
{
    CM_Conn *pCmsCon = NULL;
    if (ResInstCheckConCms(&pCmsCon) != CM_SUCCESS) {
        write_runlog(DEBUG1, "connect cms primary failed.\n");
        return CM_RES_STAT_UNKNOWN;
    }

    QueryOneNodeStatSt queryMsg = {0};
    NodeCheckGetQueryMsg(&queryMsg, nodeId);
    if (cm_client_send_msg(pCmsCon, 'C', (char*)&queryMsg, sizeof(queryMsg)) != 0) {
        write_runlog(DEBUG1, "GetNodeStatus send query one res inst msg to cms fail!\n");
        FINISH_CONNECTION_WITHOUT_EXITCODE(pCmsCon);
        return false;
    }
    bool result = NodeCheckGetResult(pCmsCon);
    FINISH_CONNECTION_WITHOUT_EXITCODE(pCmsCon);

    return result;
}

status_t CheckResInstInfo(uint32 *nodeId, uint32 instId)
{
    for (uint32 i = 0; i < CusResCount(); ++i) {
        for (uint32 j = 0; j < g_resStatus[i].status.instanceCount; ++j) {
            if (g_resStatus[i].status.resStat[j].cmInstanceId != instId) {
                continue;
            }
            if (*nodeId == 0) {
                *nodeId = g_resStatus[i].status.resStat[j].nodeId;
                return CM_SUCCESS;
            }
            if (g_resStatus[i].status.resStat[j].nodeId != *nodeId) {
                write_runlog(FATAL, "resource(%s) instance(%u) is in node(%u) not in node(%u).\n",
                    g_resStatus[i].status.resName, instId, g_resStatus[i].status.resStat[j].nodeId, *nodeId);
                return CM_ERROR;
            }
            return CM_SUCCESS;
        }
    }
    write_runlog(FATAL, "instanceId(%u) is not a resource instanceId.\n", instId);
    return CM_ERROR;
}