/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
 *
 * ubs-optimizer 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.
 */

#include "cpu_topo_tuner.h"

#include <algorithm>
#include <iostream>
#include <sstream>

#include "cmd_executor.h"
#include "log/ebpf_logger_macros.h"
#include "utils.h"

const char *CPU_INFO_CMD = "virsh vcpuinfo";

std::string CPUTopoTuner::name() const
{
    return "CPU Core Binding";
}

std::string CPUTopoTuner::category() const
{
    return "CPU BOUND";
}

std::string CPUTopoTuner::principle() const
{
    return "The topology structure does not match the physical machine, resulting in high scheduling and memory access "
           "overhead.";
}

std::string CPUTopoTuner::advice() const
{
    return "Enable CPU core binding. Synchronize the topology of physical machine and virtual machine in pass-through "
           "scenarios.";
}

bool CPUTopoTuner::check()
{
    EBPF_LOG_INFO("Checking CPU topology tunner.");
    isLastCheckSuccess = true;
    auto hostName = utils::getVmName();
    if (hostName.empty()) {
        isLastCheckSuccess = false;
        return true;
    }
    std::ostringstream cmd;
    cmd << CPU_INFO_CMD << " " << hostName;
    CmdExecutor executor;
    auto result = executor.runCommand(cmd.str());
    if (!result.first) {
        EBPF_LOG_WARN(std::string("Run '") + CPU_INFO_CMD + std::string("' failed. Unable to check CPU Topology."));
        return true;
    }
    cpu2Vcpu.clear();
    return parse(result.second);
}

bool CPUTopoTuner::parse(const std::string &output)
{
    std::istringstream iss(output);
    std::string line;
    int vcpuID = -1;
    while (std::getline(iss, line)) {
        if (line.find("VCPU:") != std::string::npos) {
            if (vcpuID != -1) {
                EBPF_LOG_WARN("Libvirt CPU info in unknown format. Unable to check.");
                isLastCheckSuccess = false;
                return true;
            }
            if (vcpuID = getValue(line); vcpuID < 0) {
                EBPF_LOG_WARN("CPUID in unknown format: '" + line + "'. Unable to check.");
                isLastCheckSuccess = false;
                return true;
            }
        } else if (line.find("CPU:") != std::string::npos) {
            if (vcpuID == -1) {
                EBPF_LOG_WARN("Libvirt CPU info in unknown format. Unable to check.");
                isLastCheckSuccess = false;
                return true;
            }
            int cpuID;
            if (cpuID = getValue(line); cpuID < 0) {
                EBPF_LOG_INFO("vCPU Core " + std::to_string(vcpuID) + " bind in the discovery range.");
                return true;
            }
            if (auto it = cpu2Vcpu.find(cpuID); it != cpu2Vcpu.end()) {
                EBPF_LOG_INFO("CPU Core " + std::to_string(cpuID) + " bind with multi vCPU: " +
                              std::to_string(it->second) + ", " + std::to_string(vcpuID) + ".");
                return true;
            }
            cpu2Vcpu.insert(std::make_pair(cpuID, vcpuID));
            vcpuID = -1;
        }
    }
    return false;
}

int CPUTopoTuner::getValue(const std::string &line)
{
    std::istringstream iss(line);
    std::string tmp;
    std::getline(iss, tmp, ':');
    int ret;
    iss >> ret;
    if (!iss.eof() || iss.fail()) {
        return -1;
    }
    return ret;
}

void CPUTopoTuner::apply()
{
    std::cout << "1. Open VM's libvirt configuration XML." << std::endl
              << "2. Bind every vCPU to a single different CPU." << std::endl;
}