/**
 * Copyright (c) 2026 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 "om2_utils.h"
#include <cctype>
#include <regex>
#include <sstream>
#include <sys/syscall.h>
#include <unistd.h>
#include <vector>
#include "mmpa/mmpa_api.h"
#include "common/ge_common/debug/ge_log.h"
#include "common/checker.h"
#include "framework/common/scope_guard.h"

namespace ge {
namespace {
struct MemFdFile {
  int32_t fd = -1;
  std::string file_name;
  std::string fd_path;
};

void CloseMemFdFile(MemFdFile &file) {
  if (file.fd >= 0) {
    (void)close(file.fd);
    file.fd = -1;
  }
}

void CloseMemFdFiles(std::vector<MemFdFile> &files) {
  for (auto &file : files) {
    CloseMemFdFile(file);
  }
}

Status CreateMemFdFile(const std::string &name, const std::string &data, MemFdFile &file) {
  file.fd = static_cast<int32_t>(syscall(__NR_memfd_create, name.c_str(), 0));
  GE_ASSERT_TRUE(file.fd >= 0, "[OM2] memfd_create failed for %s", name.c_str());
  GE_DISMISSABLE_GUARD(memfd_file_cleanup, [&file]() { CloseMemFdFile(file); });

  if (!data.empty()) {
    const auto write_count = mmWrite(file.fd, const_cast<char_t *>(data.data()), data.size());
    GE_ASSERT_TRUE(write_count == static_cast<int64_t>(data.size()),
                   "[OM2] Failed to write memfd %s, expected=%zu, actual=%lld", name.c_str(), data.size(),
                   static_cast<long long>(write_count));
  }
  (void)lseek(file.fd, 0, SEEK_SET);
  file.file_name = name;
  file.fd_path = "/proc/self/fd/" + std::to_string(file.fd);
  GE_DISMISS_GUARD(memfd_file_cleanup);
  return SUCCESS;
}

bool IsCppFile(const std::string &file_name) {
  constexpr const char_t *kCppSuffix = ".cpp";
  constexpr size_t kCppSuffixSize = 4U;
  return (file_name.size() >= kCppSuffixSize) &&
         (file_name.compare(file_name.size() - kCppSuffixSize, kCppSuffixSize, kCppSuffix) == 0);
}

Status FindArtifact(const Om2CodegenArtifacts &artifacts, const std::string &file_name,
                    const Om2CodegenArtifact *&artifact) {
  artifact = nullptr;
  for (const auto &item : artifacts) {
    if (item.file_name == file_name) {
      artifact = &item;
      return SUCCESS;
    }
  }
  GELOGE(FAILED, "[OM2] Artifact %s not found", file_name.c_str());
  return FAILED;
}

Status ReadFdToString(const int32_t fd, std::string &data) {
  data.clear();
  (void)lseek(fd, 0, SEEK_SET);
  constexpr size_t kChunkSize = 64U * 1024U;
  std::vector<char_t> buffer(kChunkSize);
  while (true) {
    const auto read_size = read(fd, buffer.data(), buffer.size());
    GE_ASSERT_TRUE(read_size >= 0, "[OM2] Failed to read fd %d", fd);
    if (read_size == 0) {
      break;
    }
    data.append(buffer.data(), static_cast<size_t>(read_size));
  }
  return SUCCESS;
}

Status CheckSafePath(const std::string &path) {
  static const std::regex kSafePathRegex(R"(^(?!.*\.{2})[A-Za-z0-9./+\-_]+$)");
  GE_ASSERT_TRUE(std::regex_match(path, kSafePathRegex), "[OM2] Unsafe compile path: %s", path.c_str());
  return SUCCESS;
}

std::string JoinFdPaths(const std::vector<MemFdFile> &files) {
  std::ostringstream oss;
  bool is_first = true;
  for (const auto &file : files) {
    if (!is_first) {
      oss << ' ';
    }
    oss << file.fd_path;
    is_first = false;
  }
  return oss.str();
}

bool IsMakefileLineContinued(const std::string &data, const size_t line_begin, const size_t line_end) {
  size_t pos = line_end;
  while ((pos > line_begin) && std::isspace(static_cast<unsigned char>(data[pos - 1U]))) {
    --pos;
  }
  return (pos > line_begin) && (data[pos - 1U] == '\\');
}

size_t FindMakefileVariableReplaceEnd(const std::string &data, size_t line_begin) {
  size_t line_end = data.find('\n', line_begin);
  while ((line_end != std::string::npos) && IsMakefileLineContinued(data, line_begin, line_end)) {
    line_begin = line_end + 1U;
    line_end = data.find('\n', line_begin);
  }
  return line_end;
}

Status ReplaceMakefileVariable(std::string &makefile_data, const std::string &variable_name,
                               const std::string &value) {
  const std::string variable_prefix = variable_name + " :=";
  size_t line_begin = 0U;
  while (line_begin < makefile_data.size()) {
    const bool is_target_line = (makefile_data.compare(line_begin, variable_prefix.size(), variable_prefix) == 0);
    const size_t line_end = makefile_data.find('\n', line_begin);
    if (is_target_line) {
      const size_t value_begin = line_begin + variable_prefix.size();
      const size_t replace_end = FindMakefileVariableReplaceEnd(makefile_data, line_begin);
      const size_t value_size = (replace_end == std::string::npos) ? std::string::npos : (replace_end - value_begin);
      makefile_data.replace(value_begin, value_size, " " + value);
      return SUCCESS;
    }
    if (line_end == std::string::npos) {
      break;
    }
    line_begin = line_end + 1U;
  }
  GELOGE(FAILED, "[OM2] Makefile variable %s not found", variable_name.c_str());
  return FAILED;
}

Status BuildCompileMakefileData(const Om2CodegenArtifact &makefile_artifact, const std::string &so_fd_path,
                                const std::vector<MemFdFile> &cpp_files, std::string &compile_makefile_data) {
  GE_ASSERT_SUCCESS(CheckSafePath(so_fd_path));
  for (const auto &cpp_file : cpp_files) {
    GE_ASSERT_SUCCESS(CheckSafePath(cpp_file.fd_path));
  }

  compile_makefile_data = makefile_artifact.data;
  GE_ASSERT_SUCCESS(ReplaceMakefileVariable(compile_makefile_data, "TARGET", so_fd_path));
  GE_ASSERT_SUCCESS(ReplaceMakefileVariable(compile_makefile_data, "SRC_FILES", JoinFdPaths(cpp_files)));
  // 仅用于内存编译:fd 路径没有 .cpp 后缀,且 so fd 在 make 前已经存在。
  compile_makefile_data += "\nCXXFLAGS += -x c++\n";
  compile_makefile_data += ".PHONY: $(TARGET)\n";
  GE_ASSERT_TRUE(compile_makefile_data.find("TARGET := lib") == std::string::npos,
                 "[OM2] Compile Makefile target still points to package so name");
  return SUCCESS;
}

Status CreateCompileCppFiles(const Om2CodegenArtifacts &artifacts, const std::string &interface_name,
                             const std::string &header_fd_path, std::vector<MemFdFile> &cpp_files) {
  const std::string old_include = "#include \"" + interface_name + "\"";
  const std::string new_include = "#include \"" + header_fd_path + "\"";
  for (const auto &artifact : artifacts) {
    if (!IsCppFile(artifact.file_name)) {
      continue;
    }
    std::string compile_data = artifact.data;
    size_t pos = 0U;
    bool replaced = false;
    while ((pos = compile_data.find(old_include, pos)) != std::string::npos) {
      compile_data.replace(pos, old_include.size(), new_include);
      pos += new_include.size();
      replaced = true;
    }
    GE_ASSERT_TRUE(replaced, "[OM2] Interface include %s not found in %s", interface_name.c_str(),
                   artifact.file_name.c_str());

    MemFdFile cpp_file;
    GE_ASSERT_SUCCESS(CreateMemFdFile(artifact.file_name, compile_data, cpp_file));
    cpp_files.push_back(std::move(cpp_file));
  }
  return SUCCESS;
}

Status CompileWithMemFdMakefile(const Om2CodegenArtifact &makefile_artifact, const std::vector<MemFdFile> &cpp_files,
                                const bool is_release, Om2CodegenArtifact &so_artifact, MemFdFile &so_file,
                                MemFdFile &makefile_file) {
  GE_ASSERT_SUCCESS(CreateMemFdFile(so_artifact.file_name, "", so_file));

  std::string compile_makefile_data;
  GE_ASSERT_SUCCESS(BuildCompileMakefileData(makefile_artifact, so_file.fd_path, cpp_files, compile_makefile_data));
  GE_ASSERT_SUCCESS(CreateMemFdFile("Makefile", compile_makefile_data, makefile_file));
  GE_ASSERT_SUCCESS(CheckSafePath(makefile_file.fd_path));

  const char_t *const use_stub_lib = is_release ? "0" : "1";
  std::ostringstream oss;
  oss << "make -s -f " << makefile_file.fd_path << " USE_STUB_LIB=" << use_stub_lib;
  const std::string command = oss.str();
  GELOGI("[OM2] Compile generated cpp artifacts to so by Makefile, command: %s", command.c_str());
  GE_CHK_BOOL_RET_STATUS(system(command.c_str()) == 0, FAILED, "[OM2] Failed to compile so artifact: %s",
                         so_artifact.file_name.c_str());
  GE_ASSERT_SUCCESS(ReadFdToString(so_file.fd, so_artifact.data));
  GE_ASSERT_TRUE(!so_artifact.data.empty(), "[OM2] Compiled so artifact is empty: %s", so_artifact.file_name.c_str());
  return SUCCESS;
}

}  // namespace
Status Om2Utils::CompileGeneratedCppToSo(const Om2CodegenArtifacts &artifacts, const std::string &model_name,
                                         Om2CodegenArtifact &so_artifact, const bool is_release) {
  const std::string interface_name = model_name + "_interface.h";
  const Om2CodegenArtifact *interface_artifact = nullptr;
  GE_ASSERT_SUCCESS(FindArtifact(artifacts, interface_name, interface_artifact));
  const Om2CodegenArtifact *makefile_artifact = nullptr;
  GE_ASSERT_SUCCESS(FindArtifact(artifacts, "Makefile", makefile_artifact));

  MemFdFile header_file;
  std::vector<MemFdFile> cpp_files;
  MemFdFile so_file;
  MemFdFile makefile_file;
  GE_MAKE_GUARD(memfd_cleanup, [&]() {
    CloseMemFdFile(header_file);
    CloseMemFdFiles(cpp_files);
    CloseMemFdFile(so_file);
    CloseMemFdFile(makefile_file);
  });
  GE_ASSERT_SUCCESS(CreateMemFdFile(interface_name, interface_artifact->data, header_file));
  GE_ASSERT_SUCCESS(CreateCompileCppFiles(artifacts, interface_name, header_file.fd_path, cpp_files));
  GE_CHK_BOOL_RET_STATUS(!cpp_files.empty(), FAILED, "[OM2] No generated cpp artifacts found for model %s",
                         model_name.c_str());

  so_artifact.file_name = "lib" + model_name + "_om2.so";
  GE_ASSERT_SUCCESS(CompileWithMemFdMakefile(*makefile_artifact, cpp_files, is_release, so_artifact, so_file,
                                             makefile_file));
  return GRAPH_SUCCESS;
}

}  // namespace ge