/*
 * Copyright (c) 2021 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 "metrics.h"
#include <regex>
#include "string_help.h"

namespace SysTuning {
namespace TraceStreamer {
const uint32_t EXTRA_CHAR = 4;
const uint32_t SEND_FINISH = 1;
Metrics ::Metrics()
{
    metricsFunction_ = {
        {TRACE_MEM, std::bind(&Metrics::InitMemoryStrategy, this, std::placeholders::_1)},
        {TRACE_MEM_TOP_TEN, std::bind(&Metrics::InitMemoryStrategy, this, std::placeholders::_1)},
        {TRACE_MEM_UNAGG, std::bind(&Metrics::InitMemoryUnAggStrategy, this, std::placeholders::_1)},
        {TRACE_TASK_NAMES, std::bind(&Metrics::InitMemoryTaskNameStrategy, this, std::placeholders::_1)},
        {TRACE_STATS, std::bind(&Metrics::InitTraceStatsStrategy, this, std::placeholders::_1)},
        {TRACE_METADATA, std::bind(&Metrics::InitTraceMetaDataStrategy, this, std::placeholders::_1)},
        {SYS_CALLS, std::bind(&Metrics::InitSysCallStrategy, this, std::placeholders::_1)}};
    initMetricsMap_ = {
        {METRICS_TRACE_MEM, TRACE_MEM},
        {METRICS_TRACE_MEM_TOP_TEN, TRACE_MEM_TOP_TEN},
        {METRICS_TRACE_MEM_UNAGG, TRACE_MEM_UNAGG},
        {METRICS_TRACE_TASK_NAMES, TRACE_TASK_NAMES},
        {METRICS_TRACE_STATS, TRACE_STATS},
        {METRICS_TRACE_METADATA, TRACE_METADATA},
        {METRICS_SYS_CALLS, SYS_CALLS},
    };
}

void Metrics::ParserJson(const std::string& metrics, std::string& result)
{
    result = result.substr(EXTRA_CHAR, result.size());
    auto it = metricsFunction_.find(metrics);
    if (it == metricsFunction_.end()) {
        TS_LOGE("Not support metrics!");
        return;
    }
    it->second(result);
}

void Metrics::InitMemoryStrategy(const std::string& result)
{
    json jMessage = json::parse(result);
    const uint32_t TYPE_INFO_ITEM_MAX = 0;
    const uint32_t TYPE_INFO_ITEM_MIN = 1;
    const uint32_t TYPE_INFO_ITEM_AVG = 2;
    const uint32_t PROCESS_METRICES_ITEMS_NAME = 4;
    for (int i = 0; i < jMessage.at("values").size(); i++) {
        TypeInfoItem typeInfoItem;
        typeInfoItem.max = jMessage.at("values")[i].at(TYPE_INFO_ITEM_MAX);
        typeInfoItem.min = jMessage.at("values")[i].at(TYPE_INFO_ITEM_MIN);
        typeInfoItem.avg = jMessage.at("values")[i].at(TYPE_INFO_ITEM_AVG);
        ProcessMetricsItems processMetricsItems;
        processMetricsItems.overallCounters = typeInfoItem;
        processMetricsItems.processName = jMessage.at("values")[i].at(PROCESS_METRICES_ITEMS_NAME);
        memStrategy_.emplace_back(std::move(processMetricsItems));
    }
    return;
}
void Metrics::InitMemoryUnAggStrategy(const std::string& result)
{
    json jMessage = json::parse(result);
    const uint32_t PROCESS_VALUES_ITEM_NAME = 0;
    const uint32_t NAMES = 1;
    const uint32_t VALUES = 2;
    const uint32_t TIMES = 3;
    for (int i = 0; i < jMessage.at("values").size(); i++) {
        ProcessValuesItem processValuesItem;
        if (jMessage.at("values")[i].at(0).is_null()) {
            processValuesItem.processName = "";
        } else {
            processValuesItem.processName = jMessage.at("values")[i].at(PROCESS_VALUES_ITEM_NAME);
        }
        auto names = base::SplitStringToVec(jMessage.at("values")[i].at(NAMES), ",");
        auto values = base::SplitStringToVec(jMessage.at("values")[i].at(VALUES), ",");
        auto times = base::SplitStringToVec(jMessage.at("values")[i].at(TIMES), ",");
        auto oomScoreValue = 0;
        for (auto index = 0; index < names.size(); index++) {
            if (names[index] == "oom_score_adj") {
                oomScoreValue = atoi(values.at(index).c_str());
            }
            TypeItem typeItem;
            typeItem.ts = atoll(times.at(index).c_str());
            typeItem.oomScore = oomScoreValue;
            typeItem.value = atoi(values.at(index).c_str());
            if (names.at(index) == "mem.rss.anon") {
                processValuesItem.anonRss = typeItem;
            } else if (names.at(index) == "mem.swap") {
                processValuesItem.swap = typeItem;
            } else if (names.at(index) == "mem.rss.file") {
                processValuesItem.fileRss = typeItem;
            } else if (names.at(index) == "oom_score_adj") {
                processValuesItem.anonAndSwap = typeItem;
            }
        }
        memAggStrategy_.emplace_back(processValuesItem);
    }
    return;
}
void Metrics::InitMemoryTaskNameStrategy(const std::string& result)
{
    json jMessage = json::parse(result);
    const uint32_t JMESSAGE_VALUE_SIZE_ONE = 1;
    const uint32_t JMESSAGE_VALUE_SIZE_TWO = 2;
    const uint32_t JMESSAGE_VALUE_SIZE_THREE = 3;
    for (int i = 0; i < jMessage.at("values").size(); i++) {
        TaskProcessItem taskProcessItem;
        taskProcessItem.pid = jMessage.at("values")[i].at(JMESSAGE_VALUE_SIZE_ONE);
        if (jMessage.at("values")[i].at(JMESSAGE_VALUE_SIZE_TWO).is_null()) {
            taskProcessItem.processName = "";
        } else {
            taskProcessItem.processName = jMessage.at("values")[i].at(JMESSAGE_VALUE_SIZE_TWO);
        }
        if (!jMessage.at("values")[i].at(JMESSAGE_VALUE_SIZE_THREE).is_null()) {
            taskProcessItem.threadName =
                base::SplitStringToVec(jMessage.at("values")[i].at(JMESSAGE_VALUE_SIZE_THREE), ",");
        }
        taskNameStrategy_.emplace_back(taskProcessItem);
    }
    return;
}
void Metrics::InitTraceStatsStrategy(const std::string& result)
{
    json jMessage = json::parse(result);
    const uint32_t STAT_ITEM_NAME = 0;
    const uint32_t STAT_ITEM_COUNT = 2;
    const uint32_t STAT_ITEM_SOURCE = 3;
    const uint32_t STAT_ITEM_SEVERITY = 4;
    for (int i = 0; i < jMessage.at("values").size(); i++) {
        StatItem statItem;
        statItem.name = jMessage.at("values")[i].at(STAT_ITEM_NAME);
        statItem.count = jMessage.at("values")[i].at(STAT_ITEM_COUNT);
        statItem.source = jMessage.at("values")[i].at(STAT_ITEM_SOURCE);
        statItem.severity = jMessage.at("values")[i].at(STAT_ITEM_SEVERITY);
        statStrategy_.emplace_back(statItem);
    }
    return;
}
void Metrics::InitTraceMetaDataStrategy(const std::string& result)
{
    json jMessage = json::parse(result);
    const uint32_t TRACE_METADATA_ITEM_NAME = 0;
    const uint32_t TRACE_METADATA_ITEM_VALUE = 1;
    for (int i = 0; i < jMessage.at("values").size(); i++) {
        TraceMetadataItem traceMetadataItem;
        traceMetadataItem.name = jMessage.at("values")[i].at(TRACE_METADATA_ITEM_NAME);
        traceMetadataItem.value = jMessage.at("values")[i].at(TRACE_METADATA_ITEM_VALUE);
        metaDataStrategy_.emplace_back(traceMetadataItem);
    }
    return;
}
void Metrics::InitSysCallStrategy(const std::string& result)
{
    json jMessage = json::parse(result);
    const uint32_t FUNCTION_ITEM_DUR_MIN = 1;
    const uint32_t FUNCTION_ITEM_DUR_MAX = 2;
    const uint32_t FUNCTION_ITEM_DUR_AVG = 3;
    const uint32_t FUNCTION_ITEM_FUNCTION_NAME = 4;
    for (int i = 0; i < jMessage.at("values").size(); i++) {
        FunctionItem functionItem;
        functionItem.functionName = jMessage.at("values")[i].at(FUNCTION_ITEM_FUNCTION_NAME);
        functionItem.durMax = jMessage.at("values")[i].at(FUNCTION_ITEM_DUR_MAX);
        functionItem.durMin = jMessage.at("values")[i].at(FUNCTION_ITEM_DUR_MIN);
        functionItem.durAvg = jMessage.at("values")[i].at(FUNCTION_ITEM_DUR_AVG);
        sysCallStrategy_.emplace_back(functionItem);
    }
    return;
}
void Metrics::PrintMetricsResult(uint32_t metricsIndex, ResultCallBack callback)
{
    std::string res = "\r\n";
    std::string metricsName = "";
    std::string repeateValue = "";
    switch (metricsIndex) {
        case METRICS_TRACE_MEM:
            UpdataRepeateValueByTraceMem(repeateValue, metricsName);
            break;
        case METRICS_TRACE_MEM_TOP_TEN:
            UpdataRepeateValueByTopTen(repeateValue, metricsName);
            break;
        case METRICS_TRACE_MEM_UNAGG:
            UpdataRepeateValueByMemUnagg(repeateValue, metricsName);
            break;
        case METRICS_TRACE_TASK_NAMES:
            UpdataRepeateValueByTaskNames(repeateValue, metricsName);
            break;
        case METRICS_TRACE_STATS:
            UpdataRepeateValueByStats(repeateValue, metricsName);
            break;
        case METRICS_TRACE_METADATA:
            UpdataRepeateValueByMetadata(repeateValue, metricsName);
            break;
        case METRICS_SYS_CALLS:
            UpdataRepeateValueBySysCalls(repeateValue, metricsName);
            break;
        default:
            break;
    }
    if (repeateValue != "") {
        repeateValue.pop_back();
    }
    res += metricsName + ": {" + repeateValue + "}";
    res = JsonFormat(res) + "\r\n";
    std::regex strRegex(",");
    auto str = std::regex_replace(res, strRegex, "");
#ifndef IS_WASM
    printf("%s", str.c_str());
#else
    callback(str, SEND_FINISH);
#endif
    return;
}
void Metrics::UpdataRepeateValueByTraceMem(std::string& repeateValue, std::string& metricsName)
{
    metricsName = TRACE_MEM;
    for (auto item : memStrategy_) {
        repeateValue += PROCESS_METRICES + PROCESS_NAME + "\"" + item.processName + "\"," + OVERALL_COUNTERS +
                        ANON_RSS + MIN + std::to_string(item.overallCounters.min) + "," + MAX +
                        std::to_string(item.overallCounters.max) + "," + AVG +
                        std::to_string(item.overallCounters.avg) + "}}},";
    }
}
void Metrics::UpdataRepeateValueByTopTen(std::string& repeateValue, std::string& metricsName)
{
    metricsName = TRACE_MEM_TOP_TEN;
    for (auto item : memStrategy_) {
        repeateValue += PROCESS_METRICES + PROCESS_NAME + "\"" + item.processName + "\"," + OVERALL_COUNTERS +
                        ANON_RSS + MIN + std::to_string(item.overallCounters.min) + "," + MAX +
                        std::to_string(item.overallCounters.max) + "," + AVG +
                        std::to_string(item.overallCounters.avg) + "}}},";
    }
}
void Metrics::UpdataRepeateValueByMemUnagg(std::string& repeateValue, std::string& metricsName)
{
    metricsName = TRACE_MEM_UNAGG;
    for (auto item : memAggStrategy_) {
        repeateValue += PROCESS_VALUES + PROCESS_NAME + "\"" + item.processName + "\"," + ANON_RSS + TS +
                        std::to_string(item.anonRss.ts) + "," + OOM_SCORE + std::to_string(item.anonRss.oomScore) +
                        "," + VALUE + std::to_string(item.anonRss.value) + "}," + FILE_RSS + TS +
                        std::to_string(item.fileRss.ts) + "," + OOM_SCORE + std::to_string(item.fileRss.oomScore) +
                        "," + VALUE + std::to_string(item.fileRss.value) + "}," + SWAP + TS +
                        std::to_string(item.swap.ts) + "," + OOM_SCORE + std::to_string(item.swap.oomScore) + "," +
                        VALUE + std::to_string(item.swap.value) + "}},";
    }
}
void Metrics::UpdataRepeateValueByTaskNames(std::string& repeateValue, std::string& metricsName)
{
    metricsName = TRACE_TASK_NAMES;
    for (auto item : taskNameStrategy_) {
        repeateValue += PROCESS + PID + std::to_string(item.pid) + "," + PROCESS_NAME + "\"" + item.processName + "\",";
        for (auto threadItem : item.threadName) {
            repeateValue += THREAD_NAME + "\"" + threadItem + "\",";
        }
        repeateValue.pop_back();
        repeateValue += "},";
    }
}
void Metrics::UpdataRepeateValueByStats(std::string& repeateValue, std::string& metricsName)
{
    metricsName = TRACE_STATS;
    for (auto item : statStrategy_) {
        repeateValue += STAT + NAME + "\"" + item.name + "\"," + COUNT + std::to_string(item.count) + "," + SOURCE +
                        "\"" + item.source + "\"," + SEVERITY + "\"" + item.severity + "\"" + "},";
    }
}
void Metrics::UpdataRepeateValueByMetadata(std::string& repeateValue, std::string& metricsName)
{
    metricsName = TRACE_METADATA;
    for (auto item : metaDataStrategy_) {
        repeateValue +=
            TRACE_METADATA + ":{" + NAME + "\"" + item.name + "\"," + VALUE + "\"" + item.value + "\"" + "},";
    }
}
void Metrics::UpdataRepeateValueBySysCalls(std::string& repeateValue, std::string& metricsName)
{
    metricsName = SYS_CALLS;
    for (auto item : sysCallStrategy_) {
        repeateValue += FUNCTION + FUNCTION_NAME + "\"" + item.functionName + "\"," + DUR_MAX +
                        std::to_string(item.durMax) + "," + DUR_MIN + std::to_string(item.durMin) + "," + DUR_AVG +
                        std::to_string(item.durAvg) + "},";
    }
}
std::string Metrics::GetLevelSpace(int level)
{
    std::string levelStr = "";
    for (int i = 0; i < level; i++) {
        levelStr += "    ";
    }
    return levelStr;
}
std::string Metrics::JsonFormat(std::string json)
{
    std::string result = "";
    int level = 0;
    for (std::string::size_type index = 0; index < json.size(); index++) {
        char value = json[index];
        if (level > 0 && json[json.size() - 1] == '\n') {
            result += GetLevelSpace(level);
        }
        switch (value) {
            case '{':
            case '[':
                result = result + value + "\n";
                level++;
                result += GetLevelSpace(level);
                break;
            case ',':
                result = result + value + "\n";
                result += GetLevelSpace(level);
                break;
            case '}':
            case ']':
                result += "\n";
                level--;
                result += GetLevelSpace(level);
                result += value;
                break;
            default:
                result += value;
                break;
        }
    }
    return result;
}
} // namespace TraceStreamer
} // namespace SysTuning