/*
 * Copyright (c) 2025 Huawei Device 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.
 */

#include "perf_pipe.h"
#include "hiperf_client.h"
#include "ipc_utilities.h"
#include "utilities.h"
#if defined(is_ohos) && is_ohos
#include "hiperf_hilog.h"
#endif

using namespace std::chrono;
namespace OHOS {
namespace Developtools {
namespace HiPerf {
const std::string RECORD_CONTROL_FIFO_FILE_C2S = "/data/log/hiperflog/.hiperf_record_control_c2s";
const std::string RECORD_CONTROL_FIFO_FILE_S2C = "/data/log/hiperflog/.hiperf_record_control_s2c";
const std::string STAT_CONTROL_FIFO_FILE_C2S = "/data/log/hiperflog/.hiperf_stat_control_c2s";
const std::string STAT_CONTROL_FIFO_FILE_S2C = "/data/log/hiperflog/.hiperf_stat_control_s2c";
const std::chrono::milliseconds CONTROL_WAITREPY_TIMEOUT = 2000ms;
const std::chrono::milliseconds CONTROL_WAITREPY_TIMEOUT_CHECK = 1000ms;
static constexpr uint64_t CHECK_WAIT_TIME_MS = 200;
static constexpr uint32_t MAX_CLIENT_OUTPUT_WAIT_COUNT = 240;

void PerfPipe::SetFifoFileName(const CommandType& commandType, std::string& controlCmd,
    std::string& fifoFileC2S, std::string& fifoFileS2C)
{
    if (commandType == CommandType::RECORD) {
        fifoFileC2S_ = RECORD_CONTROL_FIFO_FILE_C2S;
        fifoFileS2C_ = RECORD_CONTROL_FIFO_FILE_S2C;
        perfCmd_ = "sampling";
    } else if (commandType == CommandType::STAT) {
        fifoFileC2S_ = STAT_CONTROL_FIFO_FILE_C2S;
        fifoFileS2C_ = STAT_CONTROL_FIFO_FILE_S2C;
        perfCmd_ = "counting";
    }
    fifoFileC2S = fifoFileC2S_;
    fifoFileS2C = fifoFileS2C_;
    controlCmd_ = controlCmd;
    HLOGD("C2S:%s, S2C:%s", fifoFileC2S.c_str(), fifoFileS2C.c_str());
    HIPERF_HILOGD(MODULE_DEFAULT, "[SetFifoFileName] C2S:%{public}s, S2C:%{public}s",
        fifoFileC2S_.c_str(), fifoFileS2C.c_str());
}

void PerfPipe::RemoveFifoFile()
{
    char errInfo[ERRINFOLEN] = { 0 };
    if (remove(fifoFileC2S_.c_str()) != 0) {
        strerror_r(errno, errInfo, ERRINFOLEN);
        HLOGE("remove %s failed, errno:(%d:%s)", fifoFileC2S_.c_str(), errno, errInfo);
        HIPERF_HILOGE(MODULE_DEFAULT, "remove %{public}s failed, errno:(%{public}d:%{public}s)",
            fifoFileC2S_.c_str(), errno, errInfo);
    }
    if (remove(fifoFileS2C_.c_str()) != 0) {
        strerror_r(errno, errInfo, ERRINFOLEN);
        HLOGE("remove %s failed, errno:(%d:%s)", fifoFileS2C_.c_str(), errno, errInfo);
        HIPERF_HILOGE(MODULE_DEFAULT, "remove %{public}s failed, errno:(%{public}d:%{public}s)",
            fifoFileS2C_.c_str(), errno, errInfo);
    }
}

bool PerfPipe::CreateFifoFile()
{
    char errInfo[ERRINFOLEN] = { 0 };
    const mode_t fifoMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
    std::string tempPath("/data/log/hiperflog/");
    if (!IsDirectoryExists(tempPath)) {
        HIPERF_HILOGI(MODULE_DEFAULT, "%{public}s not exist.", tempPath.c_str());
        if (!CreateDirectory(tempPath, HIPERF_FILE_PERM_770)) {
            HIPERF_HILOGI(MODULE_DEFAULT, "create %{public}s failed.", tempPath.c_str());
        }
    }
    if (mkfifo(fifoFileS2C_.c_str(), fifoMode) != 0 ||
        mkfifo(fifoFileC2S_.c_str(), fifoMode) != 0) {
        if (errno == EEXIST) {
            printf("another %s service is running.\n", perfCmd_.c_str());
            HIPERF_HILOGE(MODULE_DEFAULT, "another %{public}s service is running.", perfCmd_.c_str());
        } else {
            RemoveFifoFile();
        }
        strerror_r(errno, errInfo, ERRINFOLEN);
        HLOGE("create fifo file failed. %d:%s", errno, errInfo);
        return false;
    }
    return true;
}

bool PerfPipe::SendFifoAndWaitReply(const std::string &cmd, const std::chrono::milliseconds &timeOut)
{
    // need open for read first, because server maybe send reply before client wait to read
    char errInfo[ERRINFOLEN] = { 0 };
    int fdRead = open(fifoFileS2C_.c_str(), O_RDONLY | O_NONBLOCK);
    if (fdRead == -1) {
        HLOGE("can not open fifo file(%s)", fifoFileS2C_.c_str());
        HIPERF_HILOGE(MODULE_DEFAULT,
            "[SendFifoAndWaitReply] can not open fifo file: %{public}s, errno:(%{public}d:%{public}s)",
            fifoFileS2C_.c_str(), errno, errInfo);
        return false;
    }
    int fdWrite = open(fifoFileC2S_.c_str(), O_WRONLY | O_NONBLOCK);
    if (fdWrite == -1) {
        HLOGE("can not open fifo file(%s)", fifoFileC2S_.c_str());
        HIPERF_HILOGE(MODULE_DEFAULT,
            "[SendFifoAndWaitReply] can not open fifo file: %{public}s, errno:(%{public}d:%{public}s)",
            fifoFileC2S_.c_str(), errno, errInfo);
        close(fdRead);
        return false;
    }
    ssize_t size = write(fdWrite, cmd.c_str(), cmd.size());
    if (size != static_cast<ssize_t>(cmd.size())) {
        HLOGE("failed to write fifo file(%s) command(%s)", fifoFileC2S_.c_str(), cmd.c_str());
        HIPERF_HILOGE(MODULE_DEFAULT, "failed to write fifo file(%{public}s) command(%{public}s).",
            fifoFileC2S_.c_str(), cmd.c_str());
        close(fdWrite);
        close(fdRead);
        return false;
    }
    close(fdWrite);

    bool ret = WaitFifoReply(fdRead, timeOut);
    close(fdRead);
    return ret;
}

bool PerfPipe::WaitFifoReply(const int fd, const std::chrono::milliseconds &timeOut)
{
    std::string reply;
    WaitFifoReply(fd, timeOut, reply);
    HIPERF_HILOGI(MODULE_DEFAULT, "[WaitFifoReply] WaitFifoReply reply:(%{public}s)", reply.c_str());
    return reply == HiperfClient::REPLY_OK;
}

void PerfPipe::WaitFifoReply(const int fd, const std::chrono::milliseconds &timeOut, std::string& reply)
{
    struct pollfd pollFd {
        fd, POLLIN, 0
    };
    int polled = poll(&pollFd, 1, timeOut.count());
    reply.clear();
    if (polled > 0) {
        bool exitLoop = false;
        while (!exitLoop) {
            char c;
            ssize_t result = TEMP_FAILURE_RETRY(read(fd, &c, 1));
            if (result <= 0) {
                HLOGE("[WaitFifoReply] read from fifo file(%s) failed", fifoFileS2C_.c_str());
                HIPERF_HILOGE(MODULE_DEFAULT, "[WaitFifoReply] read from fifo file(%{public}s) failed",
                    fifoFileS2C_.c_str());
                exitLoop = true;
            }
            reply.push_back(c);
            if (c == '\n') {
                exitLoop = true;
            }
        }
    } else if (polled == 0) {
        HLOGD("[WaitFifoReply] wait fifo file(%s) timeout", fifoFileS2C_.c_str());
        HIPERF_HILOGD(MODULE_DEFAULT, "[WaitFifoReply] wait fifo file(%{public}s) timeout", fifoFileS2C_.c_str());
    } else {
        HLOGD("[WaitFifoReply] wait fifo file(%s) failed", fifoFileS2C_.c_str());
        HIPERF_HILOGD(MODULE_DEFAULT, "[WaitFifoReply] wait fifo file(%{public}s) failed", fifoFileS2C_.c_str());
    }
}

void PerfPipe::SetOutPutEnd(const bool outputEnd)
{
    outputEnd_ = outputEnd;
}

void PerfPipe::ProcessStopCommand(const bool ret)
{
    if (ret) {
        // wait sampling process exit really
        static constexpr uint64_t waitCheckSleepMs = 200;
        std::this_thread::sleep_for(milliseconds(waitCheckSleepMs));
        while (SendFifoAndWaitReply(HiperfClient::REPLY_CHECK, CONTROL_WAITREPY_TIMEOUT_CHECK)) {
            std::this_thread::sleep_for(milliseconds(waitCheckSleepMs));
        }
        HLOGI("wait reply check end.");
    }

    RemoveFifoFile();
}

void PerfPipe::ProcessOutputCommand(bool ret)
{
    if (!ret) {
        HLOGI("send fifo and wait repoy fail");
        HIPERF_HILOGI(MODULE_DEFAULT, "send fifo and wait repoy fail");
        return;
    }

    std::this_thread::sleep_for(milliseconds(CHECK_WAIT_TIME_MS));
    uint32_t outputFailCount = 0;
    while (!outputEnd_) {
        ret = SendFifoAndWaitReply(HiperfClient::REPLY_OUTPUT_CHECK, CONTROL_WAITREPY_TIMEOUT_CHECK);
        if (outputFailCount++ > MAX_CLIENT_OUTPUT_WAIT_COUNT || ret) {
            break;
        }
        std::this_thread::sleep_for(milliseconds(CHECK_WAIT_TIME_MS));
    }
}

bool PerfPipe::ProcessControlCmd()
{
    bool ret = false;
    if (controlCmd_ == CONTROL_CMD_START) {
        ret = SendFifoAndWaitReply(HiperfClient::REPLY_START, CONTROL_WAITREPY_TIMEOUT);
    } else if (controlCmd_ == CONTROL_CMD_RESUME) {
        ret = SendFifoAndWaitReply(HiperfClient::REPLY_RESUME, CONTROL_WAITREPY_TIMEOUT);
    } else if (controlCmd_ == CONTROL_CMD_PAUSE) {
        ret = SendFifoAndWaitReply(HiperfClient::REPLY_PAUSE, CONTROL_WAITREPY_TIMEOUT);
    } else if (controlCmd_ == CONTROL_CMD_STOP) {
        ret = SendFifoAndWaitReply(HiperfClient::REPLY_STOP, CONTROL_WAITREPY_TIMEOUT);
        if (!ret) {
            ret = SendFifoAndWaitReply(HiperfClient::REPLY_STOP, CONTROL_WAITREPY_TIMEOUT);
        }
        ProcessStopCommand(ret);
    } else if (controlCmd_ == CONTROL_CMD_OUTPUT) {
        ret = SendFifoAndWaitReply(HiperfClient::REPLY_OUTPUT, CONTROL_WAITREPY_TIMEOUT);
        ProcessOutputCommand(ret);
    }
    if (ret) {
        printf("%s %s success.\n", controlCmd_.c_str(), perfCmd_.c_str());
        HIPERF_HILOGI(MODULE_DEFAULT, "[ProcessControlCmd] %{public}s %{public}s success.",
            controlCmd_.c_str(), perfCmd_.c_str());
    } else {
        printf("%s %s failed.\n", controlCmd_.c_str(), perfCmd_.c_str());
        HIPERF_HILOGI(MODULE_DEFAULT, "[ProcessControlCmd] %{public}s %{public}s failed.",
            controlCmd_.c_str(), perfCmd_.c_str());
    }
    return ret;
}
} // namespace HiPerf
} // namespace Developtools
} // namespace OHOS