/*
 * Copyright (c) Advanced Micro Devices, Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "amd_smi/impl/amd_smi_utils.h"

#include <dirent.h>
#include <fcntl.h>
#include <libdrm/amdgpu.h>
#include <libdrm/drm.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>

#include <algorithm>
#include <cctype>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fstream>
#include <iostream>
#include <iterator>
#include <limits>
#include <memory>
#include <mutex>
#include <optional>
#include <regex>
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_map>

#include "amd_smi/impl/amd_smi_common.h"
#include "amd_smi/impl/amd_smi_gpu_mutex.h"
#include "amd_smi/impl/amd_smi_system.h"
#include "amd_smi/impl/scoped_fd.h"
#include "rocm_smi/rocm_smi_logger.h"
#include "rocm_smi/rocm_smi_utils.h"

std::string leftTrim(const std::string& s) {
  if (!s.empty()) {
    return std::regex_replace(s, std::regex("^\\s+"), "");
  }
  return s;
}

std::string rightTrim(const std::string& s) {
  if (!s.empty()) {
    return std::regex_replace(s, std::regex("\\s+$"), "");
  }
  return s;
}

std::string removeNewLines(const std::string& s) {
  if (!s.empty()) {
    return std::regex_replace(s, std::regex("\n+"), "");
  }
  return s;
}

std::string trim(const std::string& s) {
  if (!s.empty()) {
    // remove new lines -> trim white space at ends
    std::string noNewLines = removeNewLines(s);
    return leftTrim(rightTrim(noNewLines));
  }
  return s;
}

std::string_view trim(std::string_view str) {
  if (str.empty()) {
    return str;
  }

  auto first_itr = std::find_if_not(
      str.begin(), str.end(), [](unsigned char character) { return std::isspace(character); });
  if (first_itr == str.end()) {
    return {};
  }

  auto last_itr = std::find_if_not(str.rbegin(), str.rend(),
                                   [](unsigned char character) { return std::isspace(character); });

  return str.substr(first_itr - str.begin(), last_itr.base() - first_itr);
}

// Given original string and string to remove (removeMe)
// Return will provide the resulting modified string with the removed string(s)
std::string removeString(const std::string origStr, const std::string& removeMe) {
  std::string modifiedStr = origStr;
  std::string::size_type l = removeMe.length();
  for (std::string::size_type i = modifiedStr.find(removeMe); i != std::string::npos;
       i = modifiedStr.find(removeMe)) {
    modifiedStr.erase(i, l);
  }
  return modifiedStr;
}

amdsmi_status_t smi_clear_char_and_reinitialize(char buffer[], uint32_t len,
                                                std::string newString) {
  char* begin = &buffer[0];
  char* end = &buffer[len];
  std::fill(begin, end, 0);

  // Safer approach - copy directly with length limit
  size_t copy_len = std::min(static_cast<size_t>(len - 1), newString.length());
  if (copy_len > 0) {
    std::memcpy(buffer, newString.c_str(), copy_len);
  }
  buffer[copy_len] = '\0';
  return AMDSMI_STATUS_SUCCESS;
}

int openFileAndModifyBuffer(std::string path, char* buff, size_t sizeOfBuff,
                            bool trim_whitespace = true) {
  bool errorDiscovered = false;
  std::ifstream file(path, std::ifstream::in);
  std::string contents = {std::istreambuf_iterator<char>{file}, std::istreambuf_iterator<char>{}};
  smi_clear_char_and_reinitialize(buff, static_cast<uint32_t>(sizeOfBuff), contents);
  if (!file.is_open()) {
    errorDiscovered = true;
  } else {
    if (trim_whitespace) {
      contents = amd::smi::trimAllWhiteSpace(contents);
    }
    // remove all new lines
    contents.erase(std::remove(contents.begin(), contents.end(), '\n'), contents.cend());
  }

  file.close();
  if (!errorDiscovered && file.good() && !file.bad() && !file.fail() && !file.eof() &&
      !contents.empty()) {
    std::strncpy(buff, contents.c_str(), sizeOfBuff - 1);
    buff[sizeOfBuff - 1] = '\0';
    return 0;
  } else {
    return -1;
  }
}

static const uint32_t kAmdGpuId = 0x1002;

static bool isAMDGPU(std::string dev_path) {
  std::string vend_path = dev_path + "/device/vendor";

  if (!amd::smi::FileExists(vend_path.c_str())) {
    return false;
  }

  std::ifstream fs;
  fs.open(vend_path);

  if (!fs.is_open()) {
    return false;
  }

  uint32_t vendor_id;

  fs >> std::hex >> vendor_id;

  fs.close();

  if (vendor_id == kAmdGpuId) {
    return true;
  }
  return false;
}

amdsmi_status_t smi_amdgpu_find_hwmon_dir(amd::smi::AMDSmiGPUDevice* device,
                                          std::string* full_path) {
  if (full_path == nullptr) {
    return AMDSMI_STATUS_API_FAILED;
  }
  SMIGPUDEVICE_MUTEX(device->get_mutex())
  DIR* dh;
  struct dirent* contents;
  std::string device_path = "/sys/class/drm/" + device->get_gpu_path();
  std::string directory_path = device_path + "/device/hwmon/";

  if (!isAMDGPU(device_path)) {
    return AMDSMI_STATUS_NOT_SUPPORTED;
  }

  dh = opendir(directory_path.c_str());
  if (!dh) {
    return AMDSMI_STATUS_NOT_SUPPORTED;
  }

  /*
     First directory is '.', second directory is '..' and third directory is
     valid directory for reading sysfs node
     */
  while ((contents = readdir(dh)) != NULL) {
    std::string name = contents->d_name;
    if (name.find("hwmon", 0) != std::string::npos) *full_path = directory_path + name;
  }

  closedir(dh);

  return AMDSMI_STATUS_SUCCESS;
}

amdsmi_status_t smi_amdgpu_get_board_info(amd::smi::AMDSmiGPUDevice* device,
                                          amdsmi_board_info_t* info) {
  SMIGPUDEVICE_MUTEX(device->get_mutex())
  std::string model_number_path =
      "/sys/class/drm/" + device->get_gpu_path() + std::string("/device/product_number");
  std::string product_serial_path =
      "/sys/class/drm/" + device->get_gpu_path() + std::string("/device/serial_number");
  std::string fru_id_path =
      "/sys/class/drm/" + device->get_gpu_path() + std::string("/device/fru_id");
  std::string manufacturer_name_path =
      "/sys/class/drm/" + device->get_gpu_path() + std::string("/device/manufacturer");
  std::string product_name_path =
      "/sys/class/drm/" + device->get_gpu_path() + std::string("/device/product_name");

  auto ret_mod =
      openFileAndModifyBuffer(model_number_path, info->model_number, AMDSMI_MAX_STRING_LENGTH);
  auto ret_ser =
      openFileAndModifyBuffer(product_serial_path, info->product_serial, AMDSMI_MAX_STRING_LENGTH);
  auto ret_fru = openFileAndModifyBuffer(fru_id_path, info->fru_id, AMDSMI_MAX_STRING_LENGTH);
  auto ret_man = openFileAndModifyBuffer(manufacturer_name_path, info->manufacturer_name,
                                         AMDSMI_MAX_STRING_LENGTH);
  auto ret_prod = openFileAndModifyBuffer(product_name_path, info->product_name,
                                          AMDSMI_MAX_STRING_LENGTH, false);

  std::ostringstream ss;
  ss << __PRETTY_FUNCTION__ << "[Before correction] "
     << "Returning status = AMDSMI_STATUS_SUCCESS"
     << " | model_number_path = |" << model_number_path << "|\n"
     << "; info->model_number: |" << info->model_number << "|\n"
     << "; ret_mod = " << ret_mod << "|\n"
     << "\n product_serial_path = |" << product_serial_path << "|\n"
     << "; info->product_serial: |" << info->product_serial << "|\n"
     << "; ret_ser = " << ret_ser << "|\n"
     << "\n fru_id_path = |" << fru_id_path << "|\n"
     << "; info->fru_id: |" << info->fru_id << "|\n"
     << "; ret_fru = " << ret_fru << "|\n"
     << "\n manufacturer_name_path = |" << manufacturer_name_path << "|\n"
     << "; info->manufacturer_name: |" << info->manufacturer_name << "|\n"
     << "; ret_man = " << ret_man << "|\n"
     << "\n product_name_path = |" << product_name_path << "|\n"
     << "; info->product_name: |" << info->product_name << "|"
     << "; ret_prod = " << ret_prod << "|\n";
  LOG_INFO(ss);

  return AMDSMI_STATUS_SUCCESS;
}

amdsmi_status_t smi_amdgpu_get_power_cap(amd::smi::AMDSmiGPUDevice* device, uint32_t sensor_ind,
                                         int* cap) {
  constexpr int DATA_SIZE = 16;
  char val[DATA_SIZE];
  std::string fullpath;
  amdsmi_status_t ret = AMDSMI_STATUS_SUCCESS;

  ret = smi_amdgpu_find_hwmon_dir(device, &fullpath);

  SMIGPUDEVICE_MUTEX(device->get_mutex())

  if (ret) return ret;

  fullpath += "/power" + std::to_string(sensor_ind + 1) + "_cap";
  std::ifstream file(fullpath.c_str(), std::ifstream::in);
  if (!file.is_open()) {
    return AMDSMI_STATUS_NOT_SUPPORTED;
  }

  file.getline(val, DATA_SIZE);

  if (sscanf(val, "%d", cap) < 0) {
    return AMDSMI_STATUS_API_FAILED;
  }

  return AMDSMI_STATUS_SUCCESS;
}

amdsmi_status_t smi_amdgpu_get_ranges(amd::smi::AMDSmiGPUDevice* device, amdsmi_clk_type_t domain,
                                      int* max_freq, int* min_freq, int* num_dpm,
                                      int* sleep_state_freq) {
  SMIGPUDEVICE_MUTEX(device->get_mutex())
  std::string fullpath = "/sys/class/drm/" + device->get_gpu_path() + "/device";

  std::string smclk_min_max_fullpath = "";

  bool sclk = false;
  bool mclk = false;
  bool fclk = false;
  switch (domain) {
    case AMDSMI_CLK_TYPE_GFX:
      smclk_min_max_fullpath = fullpath + "/pp_od_clk_voltage";
      fullpath += "/pp_dpm_sclk";
      sclk = true;
      break;
    case AMDSMI_CLK_TYPE_MEM:
      smclk_min_max_fullpath = fullpath + "/pp_od_clk_voltage";
      fullpath += "/pp_dpm_mclk";
      mclk = true;
      break;
    case AMDSMI_CLK_TYPE_VCLK0:
      fullpath += "/pp_dpm_vclk";
      break;
    case AMDSMI_CLK_TYPE_VCLK1:
      fullpath += "/pp_dpm_vclk1";
      break;
    case AMDSMI_CLK_TYPE_DCLK0:
      fullpath += "/pp_dpm_dclk";
      break;
    case AMDSMI_CLK_TYPE_DCLK1:
      fullpath += "/pp_dpm_dclk1";
      break;
    case AMDSMI_CLK_TYPE_SOC:
      fullpath += "/pp_dpm_socclk";
      break;
    case AMDSMI_CLK_TYPE_DF:
      smclk_min_max_fullpath = fullpath + "/pp_od_clk_voltage";
      fullpath += "/pp_dpm_fclk";
      fclk = true;
      break;
    default:
      return AMDSMI_STATUS_INVAL;
  }

  std::ifstream ranges(fullpath.c_str());
  if (ranges.fail()) {
    return AMDSMI_STATUS_NOT_SUPPORTED;
  }

  unsigned int max, min, dpm, sleep_freq, current_freq;
  char str[10];
  char single_char;
  max = 0;
  min = UINT_MAX;
  dpm = 0;
  sleep_freq = UINT_MAX;
  current_freq = 0;
  // if getting sclk, mclk or fclk info, read pp_od_clk_voltage for min and max info
  if (sclk || mclk || fclk) {
    std::ifstream smclk_ranges(smclk_min_max_fullpath.c_str());
    unsigned int smax = 0;
    unsigned int mmax = 0;
    unsigned int fmax = 0;
    unsigned int smin = UINT_MAX;
    unsigned int mmin = UINT_MAX;
    unsigned int fmin = UINT_MAX;

    // if pp_od_clk_voltage is not found, then go back to using the original pp_dpm files
    if (!smclk_ranges.is_open()) {
      sclk = false;
      mclk = false;
      fclk = false;
    } else {
      // using enum to switch between recording for sclk, mclk, or fclk
      enum ClkType { PARSING_SCLK = 0, PARSING_MCLK = 1, PARSING_FCLK = 2 };
      ClkType current_clk_type = PARSING_SCLK;
      unsigned int dpm_level, freq;
      for (std::string line; getline(smclk_ranges, line);) {
        if (line.compare("GFXCLK:") == 0 || line.compare("OD_SCLK:") == 0) {
          current_clk_type = PARSING_SCLK;
          continue;
        } else if (line.compare("MCLK:") == 0 || line.compare("OD_MCLK:") == 0) {
          current_clk_type = PARSING_MCLK;
          continue;
        } else if (line.compare("FCLK:") == 0 || line.compare("OD_FCLK:") == 0) {
          current_clk_type = PARSING_FCLK;
          continue;
        }
        if (sscanf(line.c_str(), "%u: %d%9s", &dpm_level, &freq, str) <= 2) {
          // skip lines that don't conform to the format
          continue;
        }
        if (current_clk_type == PARSING_SCLK) {
          if (freq > smax) smax = freq;
          if (freq < smin) smin = freq;
        } else if (current_clk_type == PARSING_MCLK) {
          if (freq > mmax) mmax = freq;
          if (freq < mmin) mmin = freq;
        } else if (current_clk_type == PARSING_FCLK) {
          if (freq > fmax) fmax = freq;
          if (freq < fmin) fmin = freq;
        }
      }

      if (sclk) {
        max = smax;
        min = smin;
      } else if (mclk) {
        max = mmax;
        min = mmin;
      } else if (fclk) {
        max = fmax;
        min = fmin;
      }

      smclk_ranges.close();
    }
  }
  // obtain rest of info from regular pp_dpm_* files.
  for (std::string line; getline(ranges, line);) {
    unsigned int dpm_level, freq;

    char firstChar = line[0];
    if (firstChar == 'S') {
      if (sscanf(line.c_str(), "%c: %d%9s", &single_char, &sleep_freq, str) <= 2) {
        ranges.close();
        return AMDSMI_STATUS_NO_DATA;
      }
    } else {
      /**
       * if the first line contains '*', then
       * we are saving that value as current_freq then checking
       * for other dpm levels if none are found then we
       * set min and max to current_freq as per Driver
       * We then skip to the next line to avoid getting
       * incorrect min value.
       */

      if (sscanf(line.c_str(), "%u: %d%c", &dpm_level, &freq, str) <= 2) {
        ranges.close();
        return AMDSMI_STATUS_IO;
      }

      char lastChar = line.back();
      if (lastChar == '*') {
        current_freq = freq;
      }

      // not * was detected so check for the min max if not sclk, mclk, or fclk, which are user
      // defined
      if (!sclk && !mclk && !fclk) {
        max = freq > max ? freq : max;
        min = freq < min ? freq : min;
      }
      dpm = dpm_level > dpm ? dpm_level : dpm;
    }
  }
  if (dpm == 0 && current_freq > 0) {
    // if the dpm level is 0, then the current frequency is the min/max frequency
    max = current_freq;
    min = current_freq;
  }
  if (num_dpm) *num_dpm = dpm;
  if (max_freq) *max_freq = max;
  if (min_freq) *min_freq = min;
  if (sleep_state_freq) *sleep_state_freq = sleep_freq;

  ranges.close();
  return AMDSMI_STATUS_SUCCESS;
}

amdsmi_status_t smi_amdgpu_get_enabled_blocks(amd::smi::AMDSmiGPUDevice* device,
                                              uint64_t* enabled_blocks) {
  SMIGPUDEVICE_MUTEX(device->get_mutex())
  std::string fullpath = "/sys/class/drm/" + device->get_gpu_path() + "/device/ras/features";
  std::ifstream f(fullpath.c_str());
  std::string tmp_str;

  if (f.fail()) {
    return AMDSMI_STATUS_NOT_SUPPORTED;
  }

  std::string line;
  getline(f, line);

  std::istringstream f1(line);

  f1 >> tmp_str;  // ignore
  f1 >> tmp_str;  // ignore
  f1 >> tmp_str;

  *enabled_blocks = strtoul(tmp_str.c_str(), nullptr, 16);
  f.close();

  if (*enabled_blocks == 0 || *enabled_blocks == ULONG_MAX) {
    return AMDSMI_STATUS_API_FAILED;
  }

  return AMDSMI_STATUS_SUCCESS;
}

amdsmi_status_t smi_amdgpu_get_bad_page_info(amd::smi::AMDSmiGPUDevice* device, uint32_t* num_pages,
                                             amdsmi_retired_page_record_t* info) {
  SMIGPUDEVICE_MUTEX(device->get_mutex())
  std::string line;
  std::vector<std::string> badPagesVec;

  std::string fullpath =
      "/sys/class/drm/" + device->get_gpu_path() + std::string("/device/ras/gpu_vram_bad_pages");
  std::ifstream fs(fullpath.c_str());

  if (fs.fail()) {
    return AMDSMI_STATUS_NOT_SUPPORTED;
  }

  while (std::getline(fs, line)) {
    badPagesVec.push_back(line);
  }

  if (badPagesVec.size() == 0) {
    *num_pages = 0;
    return AMDSMI_STATUS_SUCCESS;
  }
  // Remove any *trailing* empty (whitespace) lines
  while (badPagesVec.size() != 0 &&
         badPagesVec.back().find_first_not_of(" \t\n\v\f\r") == std::string::npos) {
    badPagesVec.pop_back();
  }

  *num_pages = static_cast<uint32_t>(badPagesVec.size());

  if (info == nullptr) {
    return AMDSMI_STATUS_SUCCESS;
  }

  char status_code;
  amdsmi_memory_page_status_t tmp_stat;
  std::string junk;

  for (uint32_t i = 0; i < *num_pages; ++i) {
    std::istringstream fs1(badPagesVec[i]);

    fs1 >> std::hex >> info[i].page_address;
    fs1 >> junk;
    fs1 >> std::hex >> info[i].page_size;
    fs1 >> junk;
    fs1 >> status_code;

    switch (status_code) {
      case 'P':
        tmp_stat = AMDSMI_MEM_PAGE_STATUS_PENDING;
        break;
      case 'F':
        tmp_stat = AMDSMI_MEM_PAGE_STATUS_UNRESERVABLE;
        break;
      case 'R':
        tmp_stat = AMDSMI_MEM_PAGE_STATUS_RESERVED;
        break;
      default:
        return AMDSMI_STATUS_API_FAILED;
    }
    info[i].status = tmp_stat;
  }

  return AMDSMI_STATUS_SUCCESS;
}

amdsmi_status_t smi_amdgpu_get_bad_page_threshold(amd::smi::AMDSmiGPUDevice* device,
                                                  uint32_t* threshold) {
  SMIGPUDEVICE_MUTEX(device->get_mutex())

  // TODO: Accessing the node requires root privileges, and its interface may need to be exposed in
  // another path
  uint32_t index = device->get_card_id();
  std::string fullpath =
      "/sys/kernel/debug/dri/" + std::to_string(index) + std::string("/ras/bad_page_cnt_threshold");
  std::ifstream fs(fullpath.c_str());

  if (fs.fail()) {
    return AMDSMI_STATUS_NOT_SUPPORTED;
  }

  std::string line;
  getline(fs, line);
  if (sscanf(line.c_str(), "%d", threshold) < 0) {
    return AMDSMI_STATUS_API_FAILED;
  }

  fs.close();

  return AMDSMI_STATUS_SUCCESS;
}

amdsmi_status_t smi_amdgpu_validate_ras_eeprom(amd::smi::AMDSmiGPUDevice* device) {
  SMIGPUDEVICE_MUTEX(device->get_mutex())

  // TODO: need to expose the corresponding interface to validate the checksum of ras eeprom table.
  // verify fail: return AMDSMI_STATUS_CORRUPTED_EEPROM
  return AMDSMI_STATUS_NOT_SUPPORTED;
}

amdsmi_status_t smi_amdgpu_get_ecc_error_count(amd::smi::AMDSmiGPUDevice* device,
                                               amdsmi_error_count_t* err_cnt) {
  SMIGPUDEVICE_MUTEX(device->get_mutex())
  char str[10];

  std::string fullpath =
      "/sys/class/drm/" + device->get_gpu_path() + std::string("/device/ras/umc_err_count");
  std::ifstream f(fullpath.c_str());

  if (f.fail()) {
    // fall back to aca file
    fullpath = "/sys/class/drm/" + device->get_gpu_path() + std::string("/device/ras/aca_umc");
    f.open(fullpath.c_str());
    if (f.fail()) {
      return AMDSMI_STATUS_NOT_SUPPORTED;
    }
  }

  std::string line;
  getline(f, line);
  sscanf(line.c_str(), "%9s%ld", str, &(err_cnt->uncorrectable_count));

  getline(f, line);
  sscanf(line.c_str(), "%9s%ld", str, &(err_cnt->correctable_count));

  f.close();

  return AMDSMI_STATUS_SUCCESS;
}
amdsmi_status_t smi_amdgpu_get_driver_version(amd::smi::AMDSmiGPUDevice* device, int* length,
                                              char* version) {
  SMIGPUDEVICE_MUTEX(device->get_mutex())
  amdsmi_status_t status = AMDSMI_STATUS_SUCCESS;
  size_t len;
  if (*length <= 0 || version == nullptr) {
    return AMDSMI_STATUS_INVAL;
  } else {
    len = static_cast<size_t>(*length);
  }

  std::string empty = "";
  std::strncpy(version, empty.c_str(), len - 1);
  openFileAndModifyBuffer("/sys/module/amdgpu/version", version, static_cast<size_t>(len));
  if (version[0] == '\0') {
    std::strncpy(version, "N/A", len - 1);
    version[len - 1] = '\0';
  }

  return status;
}

amdsmi_status_t smi_amdgpu_get_pcie_speed_from_pcie_type(uint16_t pcie_type, uint32_t* pcie_speed) {
  switch (pcie_type) {
    case 1:
      *pcie_speed = 2500;
      break;
    case 2:
      *pcie_speed = 5000;
      break;
    case 3:
      *pcie_speed = 8000;
      break;
    case 4:
      *pcie_speed = 16000;
      break;
    case 5:
      *pcie_speed = 32000;
      break;
    case 6:
      *pcie_speed = 64000;
      break;
    default:
      return AMDSMI_STATUS_API_FAILED;
  }
  return AMDSMI_STATUS_SUCCESS;
}

amdsmi_status_t smi_amdgpu_get_market_name_from_dev_id(amd::smi::AMDSmiGPUDevice* device,
                                                       char* market_name) {
  SMIGPUDEVICE_MUTEX(device->get_mutex())
  if (market_name == nullptr || device == nullptr) {
    return AMDSMI_STATUS_ARG_PTR_NULL;
  }
  // initialize the market_name to empty string
  std::string empty = "";
  std::strncpy(market_name, empty.c_str(), AMDSMI_MAX_STRING_LENGTH - 1);

  std::ostringstream ss;
  std::string render_name = device->get_gpu_path();
  std::string path = "/dev/dri/" + render_name;
  if (render_name.empty()) {
    return AMDSMI_STATUS_NOT_SUPPORTED;
  }

  ScopedFD fd(path.c_str(), O_RDWR | O_CLOEXEC);
  if (!fd.valid()) {
    ss << __PRETTY_FUNCTION__ << " | Render Name: " << render_name << "; path: " << path
       << "; fd: " << (fd < 0 ? "less than 0" : std::to_string(fd)) << "\n"
       << "; Returning: " << smi_amdgpu_get_status_string(AMDSMI_STATUS_FILE_ERROR, false) << "\n";
    LOG_INFO(ss);
    return AMDSMI_STATUS_FILE_ERROR;
  }

  amd::smi::AMDSmiLibraryLoader libdrm_amdgpu_;
  amdsmi_status_t status = libdrm_amdgpu_.load(amd::smi::libdrm_amdgpu_sonames());
  if (status != AMDSMI_STATUS_SUCCESS) {
    libdrm_amdgpu_.unload();
    return status;
  }

  // Function pointer typedefs
  typedef int (*amdgpu_device_initialize_t)(int fd, uint32_t* major_version,
                                            uint32_t* minor_version,
                                            amdgpu_device_handle* device_handle);
  typedef int (*amdgpu_device_deinitialize_t)(amdgpu_device_handle device_handle);
  typedef const char* (*amdgpu_get_marketing_name_t)(amdgpu_device_handle device_handle);
  amdgpu_device_initialize_t amdgpu_device_initialize = nullptr;
  amdgpu_device_deinitialize_t amdgpu_device_deinitialize = nullptr;
  amdgpu_get_marketing_name_t amdgpu_get_marketing_name = nullptr;

  status = libdrm_amdgpu_.load_symbol(reinterpret_cast<void**>(&amdgpu_device_deinitialize),
                                      "amdgpu_device_deinitialize");
  if (status != AMDSMI_STATUS_SUCCESS) {
    libdrm_amdgpu_.unload();
    return status;
  }

  status = libdrm_amdgpu_.load_symbol(reinterpret_cast<void**>(&amdgpu_device_initialize),
                                      "amdgpu_device_initialize");
  if (status != AMDSMI_STATUS_SUCCESS) {
    libdrm_amdgpu_.unload();
    return status;
  }

  amdgpu_device_handle device_handle = nullptr;
  uint32_t major_version, minor_version;
  int ret = amdgpu_device_initialize(fd, &major_version, &minor_version, &device_handle);
  if (ret != 0) {
    amdgpu_device_deinitialize(device_handle);
    libdrm_amdgpu_.unload();
    return AMDSMI_STATUS_DRM_ERROR;
  }

  status = libdrm_amdgpu_.load_symbol(reinterpret_cast<void**>(&amdgpu_get_marketing_name),
                                      "amdgpu_get_marketing_name");
  if (status != AMDSMI_STATUS_SUCCESS) {
    amdgpu_device_deinitialize(device_handle);
    libdrm_amdgpu_.unload();
    return status;
  }

  // Get the marketing name using libdrm's API
  const char* name = amdgpu_get_marketing_name(device_handle);
  if (name != nullptr) {
    std::strncpy(market_name, name, AMDSMI_MAX_STRING_LENGTH - 1);
    market_name[AMDSMI_MAX_STRING_LENGTH - 1] = '\0';
    amdgpu_device_deinitialize(device_handle);
    libdrm_amdgpu_.unload();
    return AMDSMI_STATUS_SUCCESS;
  }

  amdgpu_device_deinitialize(device_handle);
  libdrm_amdgpu_.unload();
  ss << __PRETTY_FUNCTION__ << " | path: " << path << "\n"
     << " | fd: " << std::dec << fd << "\n"
     << " | Marketing Name: " << market_name << "\n"
     << " | Returning: " << smi_amdgpu_get_status_string(AMDSMI_STATUS_DRM_ERROR, false) << "\n";
  LOG_INFO(ss);
  return AMDSMI_STATUS_DRM_ERROR;
}

amdsmi_status_t smi_amdgpu_is_gpu_power_management_enabled(amd::smi::AMDSmiGPUDevice* device,
                                                           bool* enabled) {
  if (enabled == nullptr) {
    return AMDSMI_STATUS_API_FAILED;
  }

  SMIGPUDEVICE_MUTEX(device->get_mutex())
  std::string fullpath =
      "/sys/class/drm/" + device->get_gpu_path() + std::string("/device/pp_features");
  std::ifstream fs(fullpath.c_str());

  if (fs.fail()) {
    return AMDSMI_STATUS_NOT_SUPPORTED;
  }

  // ANY line must end with "enabled" and have space before it
  const std::regex regex(R"(.*\senabled$)");
  std::string line;
  while (std::getline(fs, line)) {
    // match the whole line against regex, not just substrings
    if (std::regex_match(line, regex)) {
      *enabled = true;
      return AMDSMI_STATUS_SUCCESS;
    }
  }
  *enabled = false;
  return AMDSMI_STATUS_SUCCESS;
}

amdsmi_status_t smi_amdgpu_get_vcn_busy_percent(amd::smi::AMDSmiGPUDevice* device,
                                                uint32_t* vcn_busy_percent) {
  if (vcn_busy_percent == nullptr) {
    return AMDSMI_STATUS_INVAL;
  }

  SMIGPUDEVICE_MUTEX(device->get_mutex())
  std::string fullpath = "/sys/class/drm/" + device->get_gpu_path() + "/device/vcn_busy_percent";

  std::ifstream fs(fullpath.c_str());
  if (fs.fail()) {
    return AMDSMI_STATUS_NOT_SUPPORTED;
  }

  std::string line;
  if (std::getline(fs, line)) {
    try {
      uint32_t line_value = std::stoul(std::string(trim(line)));
      if (line_value > 100) {
        // max of uint32_t is used to indicate the erroneous value
        *vcn_busy_percent = std::numeric_limits<uint32_t>::max();
        return AMDSMI_STATUS_UNEXPECTED_DATA;
      }
      *vcn_busy_percent = line_value;
      return AMDSMI_STATUS_SUCCESS;
    } catch (const std::exception&) {
      *vcn_busy_percent = std::numeric_limits<uint32_t>::max();
      return AMDSMI_STATUS_UNEXPECTED_DATA;
    }
  }

  *vcn_busy_percent = std::numeric_limits<uint32_t>::max();
  return AMDSMI_STATUS_UNEXPECTED_DATA;
}

std::string smi_amdgpu_split_string(std::string str, char delim) {
  std::vector<std::string> tokens;
  std::stringstream ss(str);
  std::string token;

  if (str.empty()) {
    return "";
  }

  while (std::getline(ss, token, delim)) {
    tokens.push_back(token);
    return token;  // return 1st match
  }
  return "";
}

// Split string at delimiter and return strings in vector
std::vector<std::string> split_string(const std::string& line, char delim) {
  std::vector<std::string> out;
  std::size_t start = 0;

  while (start < line.size()) {
    auto pos = line.find(delim, start);
    if (pos == std::string::npos) {
      pos = line.size();
    }
    std::string token = trim(line.substr(start, pos - start));
    if (!token.empty()) {
      out.push_back(token);
    }
    start = pos + 1;
  }
  return out;
}

// wrapper to return string expression of a rsmi_status_t return
// rsmi_status_t ret - return value of RSMI API function
// bool fullStatus - defaults to true, set to false to chop off description
// Returns:
// string - if fullStatus == true, returns full description of return value
//      ex. 'RSMI_STATUS_SUCCESS: The function has been executed successfully.'
// string - if fullStatus == false, returns a minimalized return value
//      ex. 'RSMI_STATUS_SUCCESS'
std::string smi_amdgpu_get_status_string(amdsmi_status_t ret, bool fullStatus = true) {
  const char* err_str;
  amdsmi_status_code_to_string(ret, &err_str);
  if (!fullStatus) {
    return smi_amdgpu_split_string(std::string(err_str), ':');
  }
  return std::string(err_str);
}

uint32_t smi_brcm_get_value_u32(const std::string& folder, const std::string& file_name) {
  std::string file_path = folder + "/" + file_name;
  std::ifstream file(file_path.c_str(), std::ifstream::in);
  if (!file.is_open()) {
    return 0xFFFF;
  } else {
    std::string line;
    getline(file, line);
    return static_cast<uint32_t>(stoi(line));
  }

  return 0;
}

std::string smi_brcm_get_value_string(const std::string& folder, const std::string& file_name) {
  std::stringstream temp;
  std::string file_path = folder + "/" + file_name;
  std::ifstream file(file_path.c_str(), std::ifstream::in);
  if (!file.is_open()) {
    return "N/A";
  } else {
    std::string line;
    while (std::getline(file, line)) {
      if (line.empty()) {
        break;
      }
      temp << line;
    }
  }

  return temp.str();
}

amdsmi_status_t smi_brcm_execute_cmd_get_data(const std::string& command, std::string* data) {
  std::string result;
  char buffer[128];

  // Open a pipe to execute the command
  std::shared_ptr<FILE> pipe(popen(command.c_str(), "r"), [](FILE* f) {
    if (f) pclose(f);
  });
  if (!pipe) {
    return AMDSMI_STATUS_API_FAILED;
  }

  // Read the output of the command into the buffer
  while (fgets(buffer, sizeof(buffer), pipe.get()) != nullptr) {
    result += buffer;
  }
  *data = result;

  return AMDSMI_STATUS_SUCCESS;
}

// TODO(amdsmi_team): Do we want to include these functions in header?
amdsmi_status_t smi_amdgpu_get_device_index(amdsmi_processor_handle processor_handle,
                                            uint32_t* device_index) {
  uint32_t socket_count;
  std::vector<amdsmi_socket_handle> sockets;
  std::ostringstream ss;

  if (device_index == nullptr) {
    return AMDSMI_STATUS_INVAL;
  }
  *device_index = std::numeric_limits<uint32_t>::max();  // set to max value for invalid readings

  auto ret = amdsmi_get_socket_handles(&socket_count, nullptr);
  if (ret != AMDSMI_STATUS_SUCCESS) {
    return ret;
  }
  // allocate memory
  sockets.resize(socket_count);
  ret = amdsmi_get_socket_handles(&socket_count, sockets.data());
  if (ret != AMDSMI_STATUS_SUCCESS) {
    return ret;
  }

  uint32_t current_device_index = 0;
  for (uint32_t i = 0; i < socket_count; i++) {
    // Get Socket info
    char socket_info[128];
    ret = amdsmi_get_socket_info(sockets[i], 128, socket_info);
    ss << __PRETTY_FUNCTION__ << " | Socket " << socket_info << "\n";
    LOG_DEBUG(ss);

    // Get the device count available for the socket.
    uint32_t device_count = 0;
    ret = amdsmi_get_processor_handles(sockets[i], &device_count, nullptr);

    // Allocate the memory for the device handlers on the socket
    std::vector<amdsmi_processor_handle> processor_handles(device_count);
    // Get all devices of the socket
    ret = amdsmi_get_processor_handles(sockets[i], &device_count, processor_handles.data());
    ss << __PRETTY_FUNCTION__ << " | Processor Count: " << device_count << "\n";
    LOG_DEBUG(ss);

    for (uint32_t j = 0; j < device_count; j++) {
      if (processor_handles[j] == processor_handle) {
        *device_index = current_device_index;
        ss << __PRETTY_FUNCTION__ << " | AMDSMI_STATUS_SUCCESS "
           << "Returning device_index: " << *device_index << "\nSocket #: " << i
           << "; Device #: " << j << "; current_device_index #: " << current_device_index << "\n";
        LOG_DEBUG(ss);
        return AMDSMI_STATUS_SUCCESS;
      }
      current_device_index++;
    }
  }
  ss << __PRETTY_FUNCTION__ << " | AMDSMI_STATUS_API_FAILED "
     << "Returning device_index: " << *device_index << "\n";
  LOG_DEBUG(ss);
  return AMDSMI_STATUS_API_FAILED;
}

// TODO(amdsmi_team): Do we want to include these functions in header?
amdsmi_status_t smi_amdgpu_get_device_count(uint32_t* total_num_devices) {
  uint32_t socket_count;
  std::vector<amdsmi_socket_handle> sockets;
  std::ostringstream ss;

  if (total_num_devices == nullptr) {
    return AMDSMI_STATUS_INVAL;
  }
  // set to max value for invalid readings
  *total_num_devices = std::numeric_limits<uint32_t>::max();

  auto ret = amdsmi_get_socket_handles(&socket_count, nullptr);
  if (ret != AMDSMI_STATUS_SUCCESS) {
    return ret;
  }
  // allocate memory
  sockets.resize(socket_count);
  ret = amdsmi_get_socket_handles(&socket_count, sockets.data());
  if (ret != AMDSMI_STATUS_SUCCESS) {
    return ret;
  }

  uint32_t device_num = 0;
  for (uint32_t i = 0; i < socket_count; i++) {
    // Get Socket info
    char socket_info[128];
    ret = amdsmi_get_socket_info(sockets[i], 128, socket_info);
    ss << __PRETTY_FUNCTION__ << " | Socket " << socket_info << "\n";
    LOG_DEBUG(ss);

    // Get the processor count available for the socket.
    uint32_t processor_count = 0;
    ret = amdsmi_get_processor_handles(sockets[i], &processor_count, nullptr);

    // Allocate the memory for the device handlers on the socket
    std::vector<amdsmi_processor_handle> processor_handles(processor_count);
    // Get all devices of the socket
    ret = amdsmi_get_processor_handles(sockets[i], &processor_count, processor_handles.data());
    ss << __PRETTY_FUNCTION__ << " | Processor Count: " << processor_count << "\n";
    LOG_DEBUG(ss);

    for (uint32_t j = 0; j < processor_count; j++) {
      device_num++;
    }
  }
  *total_num_devices = device_num;
  ss << __PRETTY_FUNCTION__ << " | AMDSMI_STATUS_SUCCESS "
     << "Returning device_index: " << *total_num_devices << "\n";
  LOG_DEBUG(ss);
  return AMDSMI_STATUS_SUCCESS;
}

amdsmi_status_t smi_amdgpu_get_ainic_processor_handle_by_index(
    uint32_t device_index, amdsmi_processor_handle* processor_handle) {
  if (!processor_handle) {
    return AMDSMI_STATUS_INVAL;
  }
  for (const auto& socket : amd::smi::AMDSmiSystem::getInstance().get_sockets()) {
    uint32_t idx = 0;
    for (const auto& processor : socket->get_processors(AMDSMI_PROCESSOR_TYPE_AMD_NIC)) {
      if (device_index == idx) {
        *processor_handle = processor;
        return AMDSMI_STATUS_SUCCESS;
      }
      idx++;
    }
  }
  return AMDSMI_STATUS_API_FAILED;
}

// TODO(amdsmi_team): Do we want to include these functions in header?
amdsmi_status_t smi_amdgpu_get_processor_handle_by_index(
    uint32_t device_index, amdsmi_processor_handle* processor_handle) {
  uint32_t socket_count;
  std::vector<amdsmi_socket_handle> sockets;
  std::ostringstream ss;

  if (processor_handle == nullptr) {
    return AMDSMI_STATUS_INVAL;
  }

  auto ret = amdsmi_get_socket_handles(&socket_count, nullptr);
  if (ret != AMDSMI_STATUS_SUCCESS) {
    return ret;
  }
  // allocate memory
  sockets.resize(socket_count);
  ret = amdsmi_get_socket_handles(&socket_count, sockets.data());
  if (ret != AMDSMI_STATUS_SUCCESS) {
    return ret;
  }

  uint32_t current_device_index = 0;
  for (uint32_t i = 0; i < socket_count; i++) {
    // Get Socket info
    char socket_info[128];
    ret = amdsmi_get_socket_info(sockets[i], 128, socket_info);
    ss << __PRETTY_FUNCTION__ << " | Socket " << socket_info << "\n";
    LOG_DEBUG(ss);

    // Get the device count available for the socket.
    uint32_t device_count = 0;
    ret = amdsmi_get_processor_handles(sockets[i], &device_count, nullptr);

    // Allocate the memory for the device handlers on the socket
    std::vector<amdsmi_processor_handle> processor_handles(device_count);
    // Get all devices of the socket
    ret = amdsmi_get_processor_handles(sockets[i], &device_count, processor_handles.data());
    ss << __PRETTY_FUNCTION__ << " | Processor Count: " << device_count << "\n";
    LOG_DEBUG(ss);

    for (uint32_t j = 0; j < device_count; j++) {
      if (current_device_index == device_index) {
        *processor_handle = processor_handles[j];
        ss << __PRETTY_FUNCTION__ << " | AMDSMI_STATUS_SUCCESS"
           << "\nReturning processor_handle for device_index: " << device_index
           << "\nSocket #: " << i << "; Device #: " << j
           << "; current_device_index #: " << current_device_index
           << "; processor_handle: " << *processor_handle
           << "; processor_handles[j]: " << processor_handles[j] << "\n";
        LOG_DEBUG(ss);
        return AMDSMI_STATUS_SUCCESS;
      }
      current_device_index++;
    }
  }
  ss << __PRETTY_FUNCTION__ << " | AMDSMI_STATUS_API_FAILED "
     << "Could not find matching processor_handle for device_index: " << device_index << "\n";
  LOG_DEBUG(ss);
  return AMDSMI_STATUS_API_FAILED;
}

amdsmi_status_t get_gpu_device_from_handle(amdsmi_processor_handle processor_handle,
                                           amd::smi::AMDSmiGPUDevice** gpudevice) {
  AMDSMI_CHECK_INIT();
  std::ostringstream ss;
  if (processor_handle == nullptr || gpudevice == nullptr) {
    ss << __PRETTY_FUNCTION__ << " | processor_handle is NULL; returning: AMDSMI_STATUS_INVAL";
    LOG_ERROR(ss);
    return AMDSMI_STATUS_INVAL;
  }
  amd::smi::AMDSmiProcessor* device = nullptr;
  amdsmi_status_t r =
      amd::smi::AMDSmiSystem::getInstance().handle_to_processor(processor_handle, &device);
  if (r != AMDSMI_STATUS_SUCCESS) return r;
  if (device->get_processor_type() == AMDSMI_PROCESSOR_TYPE_AMD_GPU) {
    *gpudevice = static_cast<amd::smi::AMDSmiGPUDevice*>(device);
    return AMDSMI_STATUS_SUCCESS;
  }
  ss << __PRETTY_FUNCTION__ << " | returning AMDSMI_STATUS_NOT_SUPPORTED";
  LOG_ERROR(ss);
  return AMDSMI_STATUS_NOT_SUPPORTED;
}

int read_env_ms(const char* name, int def) {
  if (const char* s = std::getenv(name)) {
    try {
      return std::max(0, std::stoi(s));
    } catch (...) {
      // Ignore error, fallback to passed in def
    }
  }
  return def;
}

uint64_t get_product_serial_number(amdsmi_processor_handle processor_handle) {
  uint64_t serial_number = 0;
  amdsmi_board_info_t board_info = {};
  amdsmi_status_t status = amdsmi_get_gpu_board_info(processor_handle, &board_info);
  if (status != AMDSMI_STATUS_SUCCESS) {
    std::ostringstream ss;
    ss << __PRETTY_FUNCTION__ << "\n:" << __LINE__
       << "Failed to retrieve product serial number! error: " << static_cast<int>(status);
    LOG_DEBUG(ss);
    return serial_number;
  }
  if (!*board_info.product_serial) {
    std::ostringstream ss;
    ss << __PRETTY_FUNCTION__ << "\n:" << __LINE__ << " Product serial string is empty.";
    LOG_DEBUG(ss);
    return serial_number;
  }
  try {
    serial_number = std::stoull(board_info.product_serial, nullptr, 10);
  } catch (const std::invalid_argument& e) {
    std::ostringstream ss;
    ss << __PRETTY_FUNCTION__ << "\n:" << __LINE__
       << " Invalid product serial string. Exception: " << e.what();
    LOG_DEBUG(ss);
    serial_number = 0;
  } catch (const std::out_of_range& e) {
    std::ostringstream ss;
    ss << __PRETTY_FUNCTION__ << "\n:" << __LINE__
       << " Product serial out of range, Exception: " << e.what();
    LOG_DEBUG(ss);
    serial_number = 0;
  }
  return serial_number;
}

/**
 *  Important points to be pay attention to:
 *      - BDF is a struct in AMDSMI (amdsmi_bdf_t), and a char* in HIP (hipDeviceGetPCIBusId())
 *      - To convert from BDF to string, use: AMDSmiGPUDevice::bdf_to_string()
 *      - For HIP, UUID seems to be the best approach to identify the device, use:
 *        amdsmi_get_processor_handle_from_uuid()

 */

/**
 * Returns a pointer to the raw 16 bytes of the UUID.
 *
 *  Note:
 *      - This is NOT a null-terminated C string. The pointer refers to exactly
 *        HIP_UUID_BYTES_SIZE (16) bytes
 *      - Do not use with strlen(), printf("%s", ...), or APIs that expect a null-terminated string
 */
const char* from_uuid_to_cstring(const hipUUID_t& uuid) noexcept { return uuid.bytes; }

std::optional<amdsmi_bdf_t> from_cstring_to_bdf(const char* bdf_str) noexcept {
  if (!bdf_str) {
    return std::nullopt;
  }

  using uchar_t = unsigned char;
  constexpr auto HEX_BASE = std::int32_t(16);
  auto bdf = amdsmi_bdf_t{};
  bdf.as_uint = 0;

  const auto* ptr_str_bdf = bdf_str;
  /* Try parsing the domain (optional) */
  auto domain = std::uint64_t(0);
  auto [ptr_domain, error_code_domain] =
      std::from_chars(ptr_str_bdf, std::strchr(ptr_str_bdf, '\0'), domain, HEX_BASE);
  if ((error_code_domain == std::errc{}) && (ptr_domain != ptr_str_bdf) && (*ptr_domain == ':')) {
    bdf.bdf.domain_number = static_cast<std::uint64_t>((domain) & ((1ULL << 48) - 1));
    ptr_str_bdf = (ptr_domain + 1); /* If ':' is present, skip it */
  } else {
    ptr_str_bdf = bdf_str;
    bdf.bdf.domain_number = 0;
  }

  /* Try parsing the bus */
  auto bus = std::uint64_t(0);
  auto [ptr_bus, error_code_bus] =
      std::from_chars(ptr_str_bdf, std::strchr(ptr_str_bdf, '\0'), bus, HEX_BASE);
  /* If the bus is not valid (including only 8 bits) return nullopt */
  if (((error_code_bus != std::errc{}) || (ptr_bus == ptr_str_bdf) || (*ptr_bus != ':')) ||
      (bus > std::uint8_t(0xFF))) {
    return std::nullopt;
  }
  bdf.bdf.bus_number = static_cast<std::uint8_t>(bus);
  ptr_str_bdf = (ptr_bus + 1); /* If ':' is present, skip it */

  /* Try parsing the device */
  auto device = std::uint64_t(0);
  auto [ptr_device, error_code_device] =
      std::from_chars(ptr_str_bdf, std::strchr(ptr_str_bdf, '\0'), device, HEX_BASE);
  /* If the device is not valid (including only 5 bits) return nullopt */
  if (((error_code_device != std::errc{}) || (ptr_device == ptr_str_bdf) || (*ptr_device != '.')) ||
      (device > std::uint8_t(0x1F))) {
    return std::nullopt;
  }
  bdf.bdf.device_number = static_cast<std::uint8_t>((device) & ((1ULL << 5) - 1));
  ptr_str_bdf = (ptr_device + 1); /* If '.' is present, skip it */

  /* Try parsing the function */
  auto function = std::uint64_t(0);
  auto [ptr_function, error_code_function] =
      std::from_chars(ptr_str_bdf, std::strchr(ptr_str_bdf, '\0'), function, HEX_BASE);
  /* If the function is not valid (including only 3 bits) return nullopt */
  if (((error_code_function != std::errc{}) || (ptr_function == ptr_str_bdf)) ||
      (function > std::uint8_t(0x7))) {
    return std::nullopt;
  }
  bdf.bdf.function_number = static_cast<std::uint8_t>((function) & ((1ULL << 3) - 1));
  ptr_str_bdf = ptr_function;

  /* Allow trailing whitespace or nothing, but nothing else (optional) */
  while (*ptr_str_bdf != '\0') {
    /* Anything after the function is garbage */
    if (!std::isspace(static_cast<uchar_t>(*ptr_str_bdf))) {
      return std::nullopt;
    }
    ++ptr_str_bdf;
  }

  return bdf;
}

std::optional<hipUUID_t> from_cstring_to_uuid(const char* uuid_str) noexcept {
  if (!uuid_str) {
    return std::nullopt;
  }

  using uchar_t = unsigned char;
  auto hip_uuid = hipUUID_t{};
  auto char_pos = size_t(0);
  auto half_byte = std::size_t(0);

  /*
   *  So when we count half_bytes (nibbles; each valid hex character = one nibble),
   *  we expect exactly 32 of them after skipping all formatting characters (-, {, }, spaces, ...)
   */
  while ((uuid_str[char_pos] != '\0') && (half_byte < HIP_UUID_STRING_FULL_SIZE)) {
    auto character = char(uuid_str[char_pos++]);
    if (character == '-' || character == '{' || character == '}' ||
        std::isspace(static_cast<uchar_t>(character))) {
      continue;
    }

    if (!std::isxdigit(static_cast<uchar_t>(character))) {
      return std::nullopt;
    }

    auto character_value = static_cast<std::uint8_t>(
        (character >= '0' && character <= '9')   ? (character - '0')
        : (character >= 'a' && character <= 'f') ? (10 + (character - 'a'))
                                                 : (10 + (character - 'A')));

    /*
     *  Each half_byte corresponds to one byte in the UUID.
     *  - If the half_byte is even, we are processing the high nibble of the byte
     *  - If the half_byte is odd, we are processing the low nibble of the byte
     */
    constexpr auto HALF_BYTE_IN_BITS = static_cast<std::size_t>(CHAR_BIT / 2);

    /*
     *  (half_byte / 2) is the index of the byte in the UUID
     *  ((half_byte % 2) == 0) is where check for even/odd so high nibble or low nibble
     */
    auto* byte = reinterpret_cast<uchar_t*>(&hip_uuid.bytes[(half_byte / 2)]);
    if ((half_byte % 2) == 0) {
      *byte = static_cast<uchar_t>(character_value << HALF_BYTE_IN_BITS);
    } else {
      *byte |= character_value;
    }

    half_byte++;
  }

  return (half_byte == HIP_UUID_STRING_FULL_SIZE) ? std::optional<hipUUID_t>{hip_uuid}
                                                  : std::nullopt;
}

std::string stringify_bdf(const amdsmi_bdf_t& bdf) {
  std::ostringstream bdf_outstream;
  bdf_outstream << std::setfill('0') << std::hex << std::setw(4) << bdf.domain_number << ":"
                << std::setw(2) << static_cast<uint32_t>(bdf.bus_number) << ":" << std::setw(2)
                << static_cast<uint32_t>(bdf.device_number) << "."
                << static_cast<uint32_t>(bdf.function_number);
  return bdf_outstream.str();
}

std::tuple<uint64_t, uint64_t, uint64_t, uint64_t> parse_bdfid(uint64_t bdfid) {
  uint64_t domain = (bdfid >> 32) & 0xffffffff;
  uint64_t bus = (bdfid >> 8) & 0xff;
  uint64_t device_id = (bdfid >> 3) & 0x1f;
  uint64_t function = bdfid & 0x7;
  return std::tuple<uint64_t, uint64_t, uint64_t, uint64_t>(domain, bus, device_id, function);
}

amdsmi_status_t smi_amdgpu_read_clk_freq_from_pp_dpm(amd::smi::AMDSmiGPUDevice* device,
                                                     const char* pp_dpm_file,
                                                     amdsmi_frequencies_t* f) {
  if (f == nullptr || device == nullptr || pp_dpm_file == nullptr) {
    return AMDSMI_STATUS_INVAL;
  }

  uint32_t drm_render = device->get_drm_render_minor();
  std::string sysfs_path =
      "/sys/class/drm/renderD" + std::to_string(drm_render) + "/device/" + pp_dpm_file;

  std::ifstream file(sysfs_path);
  if (!file.good()) {
    return AMDSMI_STATUS_NOT_SUPPORTED;
  }

  f->num_supported = 0;
  f->current = 0;
  f->has_deep_sleep = 0;

  std::string line;
  uint32_t level_index = 0;

  while (std::getline(file, line) && level_index < AMDSMI_MAX_NUM_FREQUENCIES) {
    // Parse line format: "0: 200Mhz" or "1: 400Mhz *"
    size_t colon_pos = line.find(':');
    if (colon_pos == std::string::npos) {
      continue;
    }

    std::string freq_str = line.substr(colon_pos + 1);

    // Check if this is the current level (marked with *)
    bool is_current = (freq_str.find('*') != std::string::npos);
    if (is_current) {
      f->current = level_index;
    }

    // Remove asterisk and surrounding whitespace
    freq_str.erase(std::remove(freq_str.begin(), freq_str.end(), '*'), freq_str.end());
    freq_str.erase(0, freq_str.find_first_not_of(" \t"));
    size_t end = freq_str.find_last_not_of(" \t");
    if (end != std::string::npos) {
      freq_str.erase(end + 1);
    }

    // Parse "200Mhz" / "200 Mhz" / "200MHz"
    uint64_t freq_value = 0;
    char unit = 'M';  // Default to MHz

    size_t unit_pos = freq_str.find_first_not_of("0123456789 ");
    if (unit_pos != std::string::npos) {
      std::string value_str = freq_str.substr(0, unit_pos);
      value_str.erase(std::remove(value_str.begin(), value_str.end(), ' '), value_str.end());
      try {
        freq_value = std::stoull(value_str);
      } catch (...) {
        continue;  // Skip invalid lines
      }
      std::string unit_str = freq_str.substr(unit_pos);
      if (!unit_str.empty()) {
        unit = static_cast<char>(std::toupper(static_cast<unsigned char>(unit_str[0])));
      }
    }

    f->frequency[level_index] = freq_value * amd::smi::get_multiplier_from_char(unit);
    level_index++;
  }

  f->num_supported = level_index;
  return (f->num_supported > 0) ? AMDSMI_STATUS_SUCCESS : AMDSMI_STATUS_NOT_SUPPORTED;
}

const char* smi_amdgpu_pp_dpm_filename_for_clk_type(amdsmi_clk_type_t clk_type) {
  switch (clk_type) {
    case AMDSMI_CLK_TYPE_VCLK0:
      return "pp_dpm_vclk";
    case AMDSMI_CLK_TYPE_VCLK1:
      return "pp_dpm_vclk1";
    case AMDSMI_CLK_TYPE_DCLK0:
      return "pp_dpm_dclk";
    case AMDSMI_CLK_TYPE_DCLK1:
      return "pp_dpm_dclk1";
    default:
      return nullptr;
  }
}

// Gfx activity can be silenced (forced to the uint-max N/A sentinel). The
// affected graphics/RLC-firmware combo is flagged once per handle and cached.
namespace {
constexpr uint64_t kFlaggedGfxVersion = 0x1250;  // gfx1250
constexpr uint64_t kFlaggedRlcFwMin = 0x18;      // RLC fw 24..29
constexpr uint64_t kFlaggedRlcFwMax = 0x1d;

bool has_flagged_gfx_fw(amdsmi_processor_handle processor_handle) {
  static std::unordered_map<amdsmi_processor_handle, bool> cache;
  static std::mutex mtx;
  std::lock_guard<std::mutex> lock(mtx);
  auto it = cache.find(processor_handle);
  if (it != cache.end()) return it->second;

  bool flagged = false;
  amdsmi_asic_info_t asic{};
  if (amdsmi_get_gpu_asic_info(processor_handle, &asic) == AMDSMI_STATUS_SUCCESS &&
      asic.target_graphics_version == kFlaggedGfxVersion) {
    amdsmi_fw_info_t fw{};
    if (amdsmi_get_fw_info(processor_handle, &fw) == AMDSMI_STATUS_SUCCESS) {
      for (uint8_t i = 0; i < fw.num_fw_info; ++i) {
        if (fw.fw_info_list[i].fw_id == AMDSMI_FW_ID_RLC) {
          uint64_t rlc = fw.fw_info_list[i].fw_version;
          flagged = rlc >= kFlaggedRlcFwMin && rlc <= kFlaggedRlcFwMax;
          break;
        }
      }
    }
  }
  cache[processor_handle] = flagged;
  return flagged;
}
}  // namespace

bool is_gfx_activity_silenced(amdsmi_processor_handle processor_handle) {
  const char* v = std::getenv("AMDSMI_SILENCE_GFX_ACTIVITY");
  if (v != nullptr) return std::string(v) == "1";
  return has_flagged_gfx_fw(processor_handle);
}

void apply_gfx_activity_overrides(amdsmi_processor_handle processor_handle,
                                  amdsmi_gpu_metrics_t* metrics) {
  if (metrics == nullptr) return;
  if (!is_gfx_activity_silenced(processor_handle)) return;

  metrics->average_gfx_activity = std::numeric_limits<uint16_t>::max();
  metrics->gfx_activity_acc = std::numeric_limits<uint32_t>::max();
  // Per-XCP busy fields read the same source as the whole-GPU value.
  for (auto& xcp : metrics->xcp_stats) {
    for (auto& busy_inst : xcp.gfx_busy_inst) {
      busy_inst = std::numeric_limits<uint32_t>::max();
    }
    for (auto& busy_acc : xcp.gfx_busy_acc) {
      busy_acc = std::numeric_limits<uint64_t>::max();
    }
  }
}
