/**
 * 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 <iostream>
#include <iostream>
#include <fstream>
#include <cstring>
#include <sys/stat.h>
#include "utils.h"
#include "acl/acl.h"
using namespace std;
namespace {
const std::string g_imagePathSeparator = ",";
const int STAT_SUCCESS = 0;
const std::string g_pathSeparator = "/";
}  // namespace
Result Utils::ReadBinFile(const std::string &fileName, void *&inputBuff, uint32_t &fileSize, bool isDevice) {
  if (CheckPathIsFile(fileName) == FAILED) {
    ERROR_LOG("%s is not a file", fileName.c_str());
    return FAILED;
  }

  std::ifstream binFile(fileName, std::ifstream::binary);
  if (binFile.is_open() == false) {
    ERROR_LOG("open file %s failed", fileName.c_str());
    return FAILED;
  }

  binFile.seekg(0, binFile.end);
  uint32_t binFileBufferLen = binFile.tellg();
  if (binFileBufferLen == 0) {
    ERROR_LOG("binfile is empty, filename is %s", fileName.c_str());
    binFile.close();
    return FAILED;
  }
  binFile.seekg(0, binFile.beg);

  aclError ret = ACL_SUCCESS;
  if (!isDevice) {  // app is running in host
    ret = aclrtMallocHost(&inputBuff, binFileBufferLen);
    if (inputBuff == nullptr) {
      ERROR_LOG("malloc binFileBufferData failed, binFileBufferLen is %u, errorCode is %d", binFileBufferLen,
                static_cast<int32_t>(ret));
      binFile.close();
      return FAILED;
    }
  } else {  // app is running in device
    ret = aclrtMalloc(&inputBuff, binFileBufferLen, ACL_MEM_MALLOC_HUGE_FIRST);
    if (ret != ACL_SUCCESS) {
      ERROR_LOG("malloc device buffer failed. size is %u, errorCode is %d", binFileBufferLen,
                static_cast<int32_t>(ret));
      binFile.close();
      return FAILED;
    }
  }
  binFile.read(static_cast<char *>(inputBuff), binFileBufferLen);
  binFile.close();
  fileSize = binFileBufferLen;
  return SUCCESS;
}

void *Utils::MemcpyToDeviceBuffer(const void *data, uint32_t size, aclrtRunMode runMode) {
  if ((data == nullptr) || (size == 0)) {
    ERROR_LOG("Copy data args invalid, data %p, size %d", data, size);
    return nullptr;
  }
  aclrtMemcpyKind policy = ACL_MEMCPY_HOST_TO_DEVICE;
  if (runMode == ACL_DEVICE) {
    policy = ACL_MEMCPY_DEVICE_TO_DEVICE;
  }
  void *buffer = nullptr;
  aclError aclRet = aclrtMalloc(&buffer, size, ACL_MEM_MALLOC_HUGE_FIRST);
  if ((aclRet != ACL_SUCCESS) || (buffer == nullptr)) {
    ERROR_LOG("Malloc memory failed, errorno:%d", aclRet);
    return nullptr;
  }
  aclRet = aclrtMemcpy(buffer, size, data, size, policy);
  if (aclRet != ACL_SUCCESS) {
    ERROR_LOG("Copy data to device failed, aclRet is %d", aclRet);
    return nullptr;
  }
  return buffer;
}

void Utils::GetAllFiles(const std::string &pathList, std::vector<string> &fileVec) {
  vector<string> pathVec;
  SplitPath(pathList, pathVec);

  for (string everyPath : pathVec) {
    // check path exist or not
    if (!IsPathExist(pathList)) {
      ERROR_LOG("Failed to deal path=%s. Reason: not exist or cannot access.", everyPath.c_str());
      continue;
    }
    // get files in path and sub-path
    GetPathFiles(everyPath, fileVec);
  }
}

bool Utils::IsPathExist(const std::string &path) {
  ifstream file(path);
  if (!file) {
    return false;
  }
  return true;
}

void Utils::SplitPath(const std::string &path, std::vector<std::string> &pathVec) {
  char *charPath = const_cast<char *>(path.c_str());
  const char *charSplit = g_imagePathSeparator.c_str();
  char *imageFile = strtok(charPath, charSplit);
  while (imageFile) {
    pathVec.emplace_back(imageFile);
    imageFile = strtok(nullptr, charSplit);
  }
}

void Utils::GetPathFiles(const std::string &path, std::vector<std::string> &fileVec) {
  struct dirent *direntPtr = nullptr;
  DIR *dir = nullptr;
  if (IsDirectory(path)) {
    dir = opendir(path.c_str());
    while ((direntPtr = readdir(dir)) != nullptr) {
      // skip . and ..
      if (direntPtr->d_name[0] == '.') {
        continue;
      }

      // file path
      string fullPath = path + g_pathSeparator + direntPtr->d_name;
      // directory need recursion
      if (IsDirectory(fullPath)) {
        GetPathFiles(fullPath, fileVec);
      } else {
        // put file
        fileVec.emplace_back(fullPath);
      }
    }
  } else {
    fileVec.emplace_back(path);
  }
}

bool Utils::IsDirectory(const std::string &path) {
  // get path stat
  struct stat buf;
  if (stat(path.c_str(), &buf) != STAT_SUCCESS) {
    return false;
  }
  // check
  return S_ISDIR(buf.st_mode);
}

Result Utils::CheckPathIsFile(const std::string &fileName) {
  struct stat sBuf;
  int fileStatus = stat(fileName.data(), &sBuf);
  if (fileStatus == -1) {
    ERROR_LOG("failed to get file");
    return FAILED;
  }
  if (S_ISREG(sBuf.st_mode) == 0) {
    ERROR_LOG("%s is not a file, please enter a file", fileName.c_str());
    return FAILED;
  }
  return SUCCESS;
}

Result Utils::MemcpyFileToDeviceBuffer(const std::string &fileName, void *&picDevBuffer, size_t inputBuffSize,
                                       bool isDevice) {
  void *inputBuff = nullptr;
  uint32_t fileSize = 0;
  auto ret = Utils::ReadBinFile(fileName, inputBuff, fileSize, isDevice);
  if (ret != SUCCESS) {
    ERROR_LOG("read bin file failed, file name is %s", fileName.c_str());
    return FAILED;
  }
  if (inputBuffSize != static_cast<size_t>(fileSize)) {
    ERROR_LOG("input image size[%u] is not equal to model input size[%zu]", fileSize, inputBuffSize);
    if (!isDevice) {
      (void)aclrtFreeHost(inputBuff);
    } else {
      (void)aclrtFree(inputBuff);
    }
    return FAILED;
  }
  if (!isDevice) {
    // if app is running in host, need copy data from host to device
    aclError aclRet = aclrtMemcpy(picDevBuffer, inputBuffSize, inputBuff, inputBuffSize, ACL_MEMCPY_HOST_TO_DEVICE);
    if (aclRet != ACL_SUCCESS) {
      ERROR_LOG("memcpy failed. buffer size is %zu, errorCode is %d", inputBuffSize, static_cast<int32_t>(aclRet));
      (void)aclrtFreeHost(inputBuff);
      return FAILED;
    }
    (void)aclrtFreeHost(inputBuff);
  } else {  // app is running in device
    aclError aclRet = aclrtMemcpy(picDevBuffer, inputBuffSize, inputBuff, inputBuffSize, ACL_MEMCPY_DEVICE_TO_DEVICE);
    if (aclRet != ACL_SUCCESS) {
      ERROR_LOG("memcpy d2d failed. buffer size is %zu, errorCode is %d", inputBuffSize, static_cast<int32_t>(aclRet));
      (void)aclrtFree(inputBuff);
      return FAILED;
    }
    (void)aclrtFree(inputBuff);
  }
  return SUCCESS;
}

Result Utils::MemcpyFilesToDeviceBuffer(const std::vector<std::string> &fileNames, void **picDevBuffer,
                                        size_t &inputBuffSize, uint64_t batchSize, bool isDevice) {
  if (fileNames.size() < batchSize) {
    ERROR_LOG("input file num less than batch size:%zu", batchSize);
    return FAILED;
  }
  aclrtMemcpyKind memcpyKind = ACL_MEMCPY_DEVICE_TO_DEVICE;
  if (!isDevice) {
    memcpyKind = ACL_MEMCPY_HOST_TO_DEVICE;
  }

  aclError aclRet = ACL_ERROR_NONE;
  for (size_t idx = 0; idx < batchSize; idx++) {
    void *picData = nullptr;
    uint32_t picDataSize = 0U;
    INFO_LOG("start to process file:%s", fileNames[idx].c_str());
    auto ret = Utils::ReadBinFile(fileNames[idx], picData, picDataSize, isDevice);
    if (ret != SUCCESS) {
      ERROR_LOG("read bin file failed, file name is %s", fileNames[idx].c_str());
      return FAILED;
    }

    if (*picDevBuffer == nullptr) {
      // finally: picDataSize * batchSize == inputBuffSize
      aclRet = aclrtMalloc(picDevBuffer, picDataSize * batchSize, ACL_MEM_MALLOC_HUGE_FIRST);
      if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("malloc device memory failed");
        return FAILED;
      }
    }

    aclRet = aclrtMemcpy(static_cast<uint8_t *>(*picDevBuffer) + inputBuffSize, picDataSize, picData, picDataSize,
                         memcpyKind);
    if (aclRet != ACL_SUCCESS) {
      ERROR_LOG("memcpy input data to device failed");
      return FAILED;
    }
    inputBuffSize += picDataSize;
  }

  return SUCCESS;
}