/**
 * 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 "analyzer/analyzer.h"
#include <cinttypes>

#include <cstdlib>
#include <cstdio>
#include <iostream>

#include "framework/common/debug/ge_log.h"
#include "framework/common/util.h"
#include "graph/utils/graph_utils.h"
#include "graph/utils/node_utils.h"
#include "graph/utils/type_utils.h"
#include "mmpa/mmpa_api.h"
#include "base/err_msg.h"

namespace ge {
using json = nlohmann::json;
using namespace analyzer;

namespace {
constexpr int32_t kFileAuthority = 0640;
constexpr int32_t kJsonDumpLevel = 4;

const std::string kFilePath = "./";
const std::string kAnalyzeFile = "ge_check_op.json";

const std::string kUnknownShape = "unknownshape";
const std::string kUnsupport    = "unsupport";

const std::string kSessionId = "session_id";
const std::string kGraphId   = "graph_id";
const std::string kOpInfo    = "op_info";
const std::string kErrorType = "error_type";
const std::string kOpName    = "name";
const std::string kOpType    = "type";
const std::string kReason    = "reason";
const std::string kInput     = "input";
const std::string kOutput    = "output";
const std::string kShape     = "shape";
const std::string kDataType  = "data_type";
const std::string kLayout    = "layout";
const std::string kResult    = "result";
const std::string kOp        = "op";

std::map<analyzer::AnalyzeType, std::string> errors_map {
  {PARSER,         "paser_error"},
  {INFER_SHAPE,    "infer_shape_error"},
  {CHECKSUPPORT,   "check_support_error"},
  {GRAPH_OPTIMIZE, "graph_optimize_error"},
  {GRAPH_PARTION,  "graph_partion_error"},
  {GRAPH_BUILDER,  "graph_builder_error"}
};
}

Analyzer* Analyzer::GetInstance() {
  static Analyzer instance;
  return &instance;
}

Status Analyzer::BuildJsonObject(uint64_t session_id, uint64_t graph_id) {
  GELOGD("Start to build map. SessionId:%" PRIu64 " GraphId:%" PRIu64 "", session_id, graph_id);
  std::lock_guard<std::recursive_mutex> lg(mutex_);
  auto iter = graph_infos_.find(session_id);
  if (iter == graph_infos_.end()) {
    std::shared_ptr<GraphInfo> graph_info(new(std::nothrow) GraphInfo());
    GE_CHECK_NOTNULL(graph_info);
    std::map<uint64_t, std::shared_ptr<GraphInfo>> graph_map;
    graph_map[graph_id] = graph_info;
    graph_info->session_id = session_id;
    graph_info->graph_id = graph_id;
    graph_infos_.insert({session_id, graph_map});
  } else {
    std::map<uint64_t, std::shared_ptr<analyzer::GraphInfo>>::const_iterator iter1 = (iter->second).find(graph_id);
    if (iter1 == (iter->second).end()) {
      std::shared_ptr<GraphInfo> graph_info(new(std::nothrow) GraphInfo());
      GE_CHECK_NOTNULL(graph_info);
      graph_info->session_id = session_id;
      graph_info->graph_id = graph_id;
      (iter->second).insert({graph_id, graph_info});
    } else {
      GELOGI("session_id:%" PRIu64 " graph_id:%" PRIu64 " already existed json object", session_id, graph_id);
    }
  }
  return SUCCESS;
}

ge::Status Analyzer::Initialize() {
  // Initialize file
  std::string real_path = RealPath(kFilePath.c_str());
  if (real_path.empty()) {
    GELOGE(FAILED, "[Check][AnalyzeFilePath]File path is empty, Path invalid.");
    REPORT_INNER_ERR_MSG("E19999", "Analyze file path check invalid, it is empty");
    return FAILED;
  }
  json_file_name_ = real_path + "/" + kAnalyzeFile;

  return SUCCESS;
}

void Analyzer::Finalize() {
  GELOGD("Analyzer start to finalize!");
  std::lock_guard<std::recursive_mutex> lg(mutex_);
  for (auto &session_resource : graph_infos_) {
    session_resource.second.clear();
  }
  graph_infos_.clear();

  std::lock_guard<std::mutex> lk(file_mutex_);
  if (json_file_.is_open()) {
    json_file_.close();
  }
}

void Analyzer::DestroySessionJsonObject(uint64_t session_id) {
  std::lock_guard<std::recursive_mutex> lg(mutex_);
  std::map<uint64_t, std::map<uint64_t, std::shared_ptr<analyzer::GraphInfo>>>::const_iterator iter =
    graph_infos_.find(session_id);
  if (iter == graph_infos_.end()) {
    GELOGW("cannot find the stored object by session_id[%" PRIu64 "].Do nothing", session_id);
  } else {
    graph_infos_.erase(iter);
  }
}

void Analyzer::DestroyGraphJsonObject(uint64_t session_id, uint64_t graph_id) {
  std::lock_guard<std::recursive_mutex> lg(mutex_);
  auto iter = graph_infos_.find(session_id);
  if (iter == graph_infos_.end()) {
    GELOGW("cannot find the stored object by session_id[%" PRIu64 "].Do nothing", session_id);
  } else {
    std::map<uint64_t, std::shared_ptr<analyzer::GraphInfo>>::const_iterator iter1 = (iter->second).find(graph_id);
    if (iter1 == (iter->second).end()) {
      GELOGW("Cannot find the graph json object by session_id[%" PRIu64 "] and graph_id[%" PRIu64 "]. Do nothing.", session_id,
             graph_id);
      return;
    }
    (iter->second).erase(iter1);
  }
}

std::shared_ptr<GraphInfo> Analyzer::GetJsonObject(uint64_t session_id, uint64_t graph_id) {
  std::lock_guard<std::recursive_mutex> lg(mutex_);
  auto iter = graph_infos_.find(session_id);
  if (iter == graph_infos_.end()) {
    GELOGE(PARAM_INVALID, "[Check][SessionId]session_id:%" PRIu64 " does not exist! "
           "graph_id:%" PRIu64 "", session_id, graph_id);
    REPORT_INNER_ERR_MSG("E19999", "Sessin_id %" PRIu64 " does not exist, graph_id %" PRIu64 "",
                       session_id, graph_id);
    return nullptr;
  } else {
    auto iter1 = (iter->second).find(graph_id);
    if (iter1 == (iter->second).end()) {
      GELOGE(PARAM_INVALID, "[Check][GraphId]graph_id:%" PRIu64 " does not exist! "
             "session_id:%" PRIu64 ".", graph_id, session_id);
      REPORT_INNER_ERR_MSG("E19999", "Graph_id %" PRIu64 " does not exist, session_id %" PRIu64 "",
                         graph_id, session_id);
      return nullptr;
    }
    GELOGI("GetJsonObject Success!session_id:%" PRIu64 " graph_id:%" PRIu64 "", session_id, graph_id);
    return iter1->second;
  }
}

void Analyzer::ClearHistoryFile() const {
  GELOGD("Analyzer start to clear history file!");

  // Remove history files
  int32_t res = remove(json_file_name_.c_str());
  GELOGD("remove file %s, result:%d", json_file_name_.c_str(), res);
}

ge::Status Analyzer::CreateAnalyzerFile() {
  if (is_json_file_create_) {
    GELOGD("analyzer file has been created!No necessary to create again!");
    return SUCCESS;
  }
  GELOGD("start to create analyzer file!");

  std::lock_guard<std::mutex> lg(file_mutex_);
  int32_t fd = open(json_file_name_.c_str(), O_WRONLY | O_CREAT | O_TRUNC, kFileAuthority);
  if (fd < 0) {
    GELOGE(INTERNAL_ERROR, "[FileOpen][AnalyzeFile]Fail to open the analyze file: %s.",
           json_file_name_.c_str());
    REPORT_INNER_ERR_MSG("E19999", "Failed to open analyze file %s", json_file_name_.c_str());
    return INTERNAL_ERROR;
  }
  if (close(fd) != 0) {
    GELOGE(INTERNAL_ERROR, "[FileClose][AnalyzeFile]Fail to close the analyze file: %s.",
           json_file_name_.c_str());
    REPORT_INNER_ERR_MSG("E19999", "Failed to clsoe analyze file %s", json_file_name_.c_str());
    return INTERNAL_ERROR;
  }
  is_json_file_create_ = true;

  GELOGD("success to create analyzer file[%s]!", json_file_name_.c_str());
  return SUCCESS;
}

ge::Status Analyzer::SaveAnalyzerDataToFile(uint64_t session_id, uint64_t graph_id) {
  GELOGD("start to save analyze file");

  auto graph_info = GetJsonObject(session_id, graph_id);
  GE_CHECK_NOTNULL(graph_info);
  if (graph_info->op_info.size() == 0) {
    GELOGD("session_id:%" PRIu64 " graph_id:%" PRIu64 " does not owner op info, break it!", session_id, graph_id);
    return SUCCESS;
  }
  std::lock_guard<std::mutex> lg(file_mutex_);
  json_file_.open(json_file_name_, std::ios::app);
  if (!json_file_.is_open()) {
    GELOGE(FAILED, "[Check][AnalyzeFile]analyze file does not exist[%s]",
           json_file_name_.c_str());
    REPORT_INNER_ERR_MSG("E19999", "Analyze file %s does not exist", json_file_name_.c_str());
    return PARAM_INVALID;
  }

  json jsn;
  GraphInfoToJson(jsn, *graph_info);
  bool ret_failed = false;
  try {
    json_file_ << jsn.dump(kJsonDumpLevel) << std::endl;
  } catch (nlohmann::detail::type_error &e) {
    GELOGE(FAILED,
           "[Json.dump][GraphInfo]Dump analyze file [%s] failed because [%s],"
           "session_id:%" PRIu64 ", graph_id:%" PRIu64 "",
           json_file_name_.c_str(), e.what(), session_id, graph_id);
    REPORT_INNER_ERR_MSG("E19999", "Dump analyze file %s failed because %s, "
                       "session_id %" PRIu64 ", graph_id %" PRIu64 "",
                       json_file_name_.c_str(), e.what(), session_id, graph_id);
    ret_failed = true;
  }
  json_file_.close();
  return ret_failed ? FAILED : SUCCESS;
}

ge::Status Analyzer::DoAnalyze(const DataInfo &data_info) {
  GELOGD("start to do analyzer process");

  auto pnode = data_info.node_ptr;
  GE_CHECK_NOTNULL(pnode);
  auto desc = pnode->GetOpDesc();
  GE_CHECK_NOTNULL(desc);
  // buff analyze data
  std::lock_guard<std::recursive_mutex> lg(mutex_);
  auto graph_info = GetJsonObject(data_info.session_id, data_info.graph_id);
  GE_CHECK_NOTNULL(graph_info);
  auto status = SaveOpInfo(desc, data_info, graph_info);
  if (status != SUCCESS) {
    GELOGE(status,
           "[Check][SaveOpInfo]save op info: desc_name [%s] desc_type [%s] failed!",
           desc->GetName().c_str(), desc->GetType().c_str());
    REPORT_INNER_ERR_MSG("E19999", "Save op info: desc_name %s, desc_type %s failed",
                      desc->GetName().c_str(), desc->GetType().c_str());
    return FAILED;
  }
  // create json file
  return CreateAnalyzerFile();
}

ge::Status Analyzer::SaveOpInfo(ge::OpDescPtr desc, const DataInfo &data_info,
                                std::shared_ptr<analyzer::GraphInfo> graph_info) const {
  auto iter = errors_map.find(data_info.analyze_type);
  if (iter == errors_map.end()) {
    return PARAM_INVALID;
  }
  OpInfo op_info;
  op_info.error_type = iter->second;
  op_info.op_name = desc->GetName();
  op_info.op_type = desc->GetType();
  op_info.reason  = data_info.reason;

  for (const auto &ptr : desc->GetAllInputsDescPtr()) {
    TensorInfo tensor_info;
    tensor_info.shape  = ptr->GetShape().GetDims();
    tensor_info.d_type = ge::TypeUtils::DataTypeToSerialString(ptr->GetDataType());
    tensor_info.layout = ge::TypeUtils::FormatToSerialString(ptr->GetFormat());
    op_info.input_info.emplace_back(tensor_info);
  }
  for (const auto &ptr : desc->GetAllOutputsDescPtr()) {
    TensorInfo tensor_info;
    tensor_info.shape  = ptr->GetShape().GetDims();
    tensor_info.d_type = ge::TypeUtils::DataTypeToSerialString(ptr->GetDataType());
    tensor_info.layout = ge::TypeUtils::FormatToSerialString(ptr->GetFormat());
    op_info.output_info.emplace_back(tensor_info);
  }
  graph_info->op_info.emplace_back(op_info);

  return SUCCESS;
}

void Analyzer::TensorInfoToJson(json& j, const TensorInfo &tensor_info) const {
  j[kShape] = tensor_info.shape;
  j[kDataType] = tensor_info.d_type;
  j[kLayout] = tensor_info.layout;
}

void Analyzer::OpInfoToJson(json& j, const OpInfo &op_info) const {
  j[kErrorType] = op_info.error_type;
  j[kOpName] = op_info.op_name;
  j[kOpType] = op_info.op_type;
  j[kReason] = op_info.reason;
  for (size_t i = 0; i < op_info.input_info.size(); i++) {
    json json_tensor_info;
    TensorInfoToJson(json_tensor_info, op_info.input_info.at(i));
    j[kInput + std::to_string(i)] = json_tensor_info;
  }
  for (size_t i = 0; i < op_info.output_info.size(); i++) {
    json json_tensor_info;
    TensorInfoToJson(json_tensor_info, op_info.output_info.at(i));
    j[kOutput + std::to_string(i)] = json_tensor_info;
  }
}

void Analyzer::GraphInfoToJson(json& j, const GraphInfo &graph_info) {
  GELOGD("start to buff graph info!");
  j[kSessionId] = graph_info.session_id;
  j[kGraphId] = graph_info.graph_id;
  std::vector<json> json_op_infos;
  for (size_t i = 0; i < graph_info.op_info.size(); i++) {
    json json_op_info;
    OpInfoToJson(json_op_info, graph_info.op_info.at(i));
    json_op_infos.emplace_back(json_op_info);
  }
  j[kOp] = json_op_infos;
}
} // namespace ge