/**
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
 * CANN Open Software License Agreement Version 2.0 (the "License").
 * Please refer to the License for details. You may not use this file except in compliance with the License.
 * 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 FITNESS FOR A PARTICULAR PURPOSE.
 * See LICENSE in the root of the software repository for the full text of the License.
 */

#include "flow_func_statistic.h"
#include <algorithm>
#include <sstream>
#include "common/udf_log.h"
#include "common/util.h"

namespace FlowFunc {
namespace {
std::string ToStringRange(const std::vector<IOStatisticInfo> &statistic_infos, size_t start_idx, size_t end_idx) {
  std::stringstream min_size_stream;
  std::stringstream max_size_stream;
  std::stringstream min_shape_stream;
  std::stringstream max_shape_stream;
  for (size_t idx = start_idx; idx <= end_idx; ++idx) {
    const auto &statisticInfo = statistic_infos[idx];
    if (idx != start_idx) {
      min_size_stream << ", ";
      max_size_stream << ", ";
      min_shape_stream << ", ";
      max_shape_stream << ", ";
    }
    min_size_stream << statisticInfo.min_size;
    max_size_stream << statisticInfo.max_size;
    min_shape_stream << ToString(statisticInfo.min_shape);
    max_shape_stream << ToString(statisticInfo.max_shape);
  }
  std::stringstream str_stream;
  str_stream << "min_size=[" << min_size_stream.str() << "], max_size=[" << max_size_stream.str() << "], min_shape=["
             << min_shape_stream.str() << "], max_shape=[" << max_shape_stream.str() << "]";
  return str_stream.str();
}
}  // namespace

void FlowFuncStatistic::Init(const std::string &flow_func_info, size_t input_num, size_t output_num) {
  flow_func_info_ = flow_func_info;
  input_num_ = input_num;
  output_num_ = output_num;
  statistic_info_.inputs.resize(input_num_);
  statistic_info_.outputs.resize(output_num_);
}

void FlowFuncStatistic::ExecuteStart() {
  execute_start_time_ = std::chrono::steady_clock::now();
}

void FlowFuncStatistic::ExecuteFinish() {
  ++statistic_info_.total_exec_num;
  auto current_time = std::chrono::steady_clock::now();
  auto elapsed = current_time - execute_start_time_;

  int64_t elapsed_us = std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
  if (elapsed_us < 0) {
    UDF_LOG_WARN("elapsed microseconds=%ld is less than 0, ignore it, flow_func_info=%s.", elapsed_us,
                 flow_func_info_.c_str());
    return;
  }
  uint64_t elapsed_us_u64 = static_cast<uint64_t>(elapsed_us);
  if ((statistic_info_.min_exec_time == 0) || (statistic_info_.min_exec_time > elapsed_us_u64)) {
    statistic_info_.min_exec_time = elapsed_us_u64;
  }

  if (elapsed_us_u64 > statistic_info_.max_exec_time) {
    statistic_info_.sub_max_exec_time = statistic_info_.max_exec_time;
    statistic_info_.max_exec_time = elapsed_us_u64;
  } else if (elapsed_us_u64 > statistic_info_.sub_max_exec_time) {
    statistic_info_.sub_max_exec_time = elapsed_us_u64;
  } else {
    // do nothing
  }
  statistic_info_.total_exec_time += elapsed_us_u64;
}

void FlowFuncStatistic::RecordInput(size_t input_idx, const std::shared_ptr<FlowMsg> &flow_msg) {
  if (input_idx >= input_num_) {
    UDF_LOG_ERROR("input_idx=%zu must be less than input_num=%zu, ignore it, flow_func_info=%s.", input_idx, input_num_,
                  flow_func_info_.c_str());
    return;
  }
  auto &input_statistic_info = statistic_info_.inputs[input_idx];
  UpdateIOStatisticInfo(flow_msg->GetTensor(), input_statistic_info);
}

void FlowFuncStatistic::RecordOutput(size_t output_idx, const std::shared_ptr<FlowMsg> &flow_msg) {
  if (output_idx >= output_num_) {
    UDF_LOG_ERROR("output_idx=%zu must be less than output_num=%zu, ignore it, flow_func_info=%s.", output_idx,
                  output_num_, flow_func_info_.c_str());
    return;
  }
  auto &output_statistic_info = statistic_info_.outputs[output_idx];
  UpdateIOStatisticInfo(flow_msg->GetTensor(), output_statistic_info);
}

void FlowFuncStatistic::UpdateIOStatisticInfo(const Tensor *tensor, IOStatisticInfo &io_statistic_info) {
  if (tensor == nullptr) {
    UDF_LOG_DEBUG("tensor is null, ignore it");
    return;
  }
  uint64_t data_size = tensor->GetDataSize();
  const auto &shape = tensor->GetShape();
  if ((io_statistic_info.min_size == 0) || (data_size < io_statistic_info.min_size)) {
    io_statistic_info.min_size = data_size;
    io_statistic_info.min_shape.assign(shape.cbegin(), shape.cend());
  }
  if (data_size > io_statistic_info.max_size) {
    io_statistic_info.max_size = data_size;
    io_statistic_info.max_shape.assign(shape.cbegin(), shape.cend());
  }
}

void FlowFuncStatistic::DumpMetrics(bool with_queue_info) const {
  UDF_RUN_LOG_INFO(
      "model_metrics:name=%s, min_exec_time=%lu us, max_exec_time=%lu us, sub_max_exec_time=%lu us"
      ", total_exec_time=%lu us, total_exec_num=%lu.",
      flow_func_info_.c_str(), statistic_info_.min_exec_time, statistic_info_.max_exec_time,
      statistic_info_.sub_max_exec_time, statistic_info_.total_exec_time, statistic_info_.total_exec_num);
  if (!with_queue_info || (statistic_info_.total_exec_num == 0)) {
    return;
  }
  constexpr size_t print_num_per_log = 10;
  for (size_t input_idx = 0; input_idx < input_num_; input_idx += print_num_per_log) {
    size_t start_idx = input_idx;
    size_t end_idx =
        (input_idx + print_num_per_log) > input_num_ ? (input_num_ - 1) : (input_idx + print_num_per_log - 1);
    UDF_RUN_LOG_INFO("model_metrics:name=%s, input[%zu-%zu], %s.", flow_func_info_.c_str(), start_idx, end_idx,
                     ToStringRange(statistic_info_.inputs, start_idx, end_idx).c_str());
  }

  for (size_t output_idx = 0; output_idx < output_num_; output_idx += print_num_per_log) {
    size_t start_idx = output_idx;
    size_t end_idx =
        (output_idx + print_num_per_log) > output_num_ ? (output_num_ - 1) : (output_idx + print_num_per_log - 1);
    UDF_RUN_LOG_INFO("model_metrics:name=%s, output[%zu-%zu], %s.", flow_func_info_.c_str(), start_idx, end_idx,
                     ToStringRange(statistic_info_.outputs, start_idx, end_idx).c_str());
  }
}
}  // namespace FlowFunc