/*
 * 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.
 */

#ifndef AMD_SMI_INCLUDE_AMD_SMI_UTILS_H_
#define AMD_SMI_INCLUDE_AMD_SMI_UTILS_H_

#include <dirent.h>

#include <algorithm>
#include <cctype>
#include <charconv>
#include <cstdint>
#include <limits>
#include <optional>
#include <string>
#include <string_view>

#include "amd_smi/amdsmi.h"
#include "amd_smi/impl/amd_smi_gpu_device.h"

extern "C" {
void amdsmi_free_name_value_pairs(void* p);
}

amdsmi_status_t smi_amdgpu_find_hwmon_dir(amd::smi::AMDSmiGPUDevice* device,
                                          std::string* full_path);
amdsmi_status_t smi_amdgpu_get_board_info(amd::smi::AMDSmiGPUDevice* device,
                                          amdsmi_board_info_t* info);
amdsmi_status_t smi_amdgpu_get_power_cap(amd::smi::AMDSmiGPUDevice* device, uint32_t sensor_ind,
                                         int* cap);
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);
amdsmi_status_t smi_amdgpu_get_enabled_blocks(amd::smi::AMDSmiGPUDevice* device,
                                              uint64_t* enabled_blocks);
amdsmi_status_t smi_amdgpu_get_bad_page_info(amd::smi::AMDSmiGPUDevice* device, uint32_t* num_pages,
                                             amdsmi_retired_page_record_t* info);
amdsmi_status_t smi_amdgpu_get_bad_page_threshold(amd::smi::AMDSmiGPUDevice* device,
                                                  uint32_t* threshold);
amdsmi_status_t smi_amdgpu_validate_ras_eeprom(amd::smi::AMDSmiGPUDevice* device);
amdsmi_status_t smi_amdgpu_get_ecc_error_count(amd::smi::AMDSmiGPUDevice* device,
                                               amdsmi_error_count_t* err_cnt);
amdsmi_status_t smi_amdgpu_get_driver_version(amd::smi::AMDSmiGPUDevice* device, int* length,
                                              char* version);
amdsmi_status_t smi_amdgpu_get_pcie_speed_from_pcie_type(uint16_t pcie_type, uint32_t* pcie_speed);
amdsmi_status_t smi_amdgpu_get_market_name_from_dev_id(amd::smi::AMDSmiGPUDevice* device,
                                                       char* market_name);
amdsmi_status_t smi_amdgpu_is_gpu_power_management_enabled(amd::smi::AMDSmiGPUDevice* device,
                                                           bool* enabled);
std::string smi_split_string(std::string str, char delim);
std::vector<std::string> split_string(const std::string& line, char delim);
std::string smi_amdgpu_get_status_string(amdsmi_status_t ret, bool fullStatus);

uint32_t smi_brcm_get_value_u32(const std::string& folder, const std::string& file_name);
std::string smi_brcm_get_value_string(const std::string& folder, const std::string& file_name);
amdsmi_status_t smi_brcm_execute_cmd_get_data(const std::string& command, std::string* data);

amdsmi_status_t smi_clear_char_and_reinitialize(char buffer[], uint32_t len, std::string newString);

/**
 *  @brief Get the device index given the processor handle.
 *
 *  @details Given a processor handle @p processor_handle
 *  and a pointer to a uint32_t @p device_index will be returned.
 *
 *  @param[in] processor_handle Device which to query
 *
 *  @param[inout] device_index a pointer to uint32_t to which the matching device
 *  index will be stored
 *
 *  @retval ::AMDSMI_STATUS_SUCCESS is returned upon successful call.
 *          ::AMDSMI_STATUS_INVAL is returned if user provides a null pointer
 *          for device_index.
 *          ::AMDSMI_STATUS_API_FAILED is returned if the corresponding device
 *          index for the processor handle cannot be found.
 */
amdsmi_status_t smi_amdgpu_get_device_index(amdsmi_processor_handle processor_handle,
                                            uint32_t* device_index);

/**
 *  @brief Get total number of devices
 *
 *  @details Given a pointer to a uint32_t @p total_num_devices will be returned
 *
 *  @param[inout] total_num_devices a pointer to uint32_t to which the total number
 *  of devices will be stored
 *
 *  @retval ::AMDSMI_STATUS_SUCCESS is returned upon successful call.
 *          ::AMDSMI_STATUS_INVAL is returned if user provides a null pointer
 *          for total_num_devices.
 */
amdsmi_status_t smi_amdgpu_get_device_count(uint32_t* total_num_devices);

/**
 *  @brief Get the ainic processor handle given the device index.
 *
 *  @details Given a uint32_t @p device_index and a pointer to
 *  a ainic processor handle @p processor_handle, the device index will be used to
 *  find the processor handle of the device and store it in the provided pointer
 *
 *  @param[in] device_index a uint32_t to value to help find the corresponding
 *  ainic processor handle
 *
 *  @param[inout] processor_handle a pointer to amdsmi_processor_handle
 *  which the corresponding processor_handle will be stored
 *
 *  @retval ::AMDSMI_STATUS_SUCCESS is returned upon successful call.
 *          ::AMDSMI_STATUS_INVAL is returned if user provides a null pointer
 *          for processor_handle.
 *          ::AMDSMI_STATUS_API_FAILED is returned if the device_index is cannot
 *          be found.
 */
amdsmi_status_t smi_amdgpu_get_ainic_processor_handle_by_index(
    uint32_t device_index, amdsmi_processor_handle* processor_handle);

/**
 *  @brief Get the processor handle given the device index.
 *
 *  @details Given a uint32_t @p device_index and a pointer to
 *  a processor handle @p processor_handle, the device index will be used to
 *  find the processor handle of the device and store it in the provided pointer
 *
 *  @param[in] device_index a uint32_t to value to help find the corresponding
 *  processor handle
 *
 *  @param[inout] processor_handle a pointer to amdsmi_processor_handle
 *  which the corresponding processor_handle will be stored
 *
 *  @retval ::AMDSMI_STATUS_SUCCESS is returned upon successful call.
 *          ::AMDSMI_STATUS_INVAL is returned if user provides a null pointer
 *          for processor_handle.
 *          ::AMDSMI_STATUS_API_FAILED is returned if the device_index is cannot
 *          be found.
 */
amdsmi_status_t smi_amdgpu_get_processor_handle_by_index(uint32_t device_index,
                                                         amdsmi_processor_handle* processor_handle);

amdsmi_status_t get_gpu_device_from_handle(amdsmi_processor_handle processor_handle,
                                           amd::smi::AMDSmiGPUDevice** gpudevice);

/**
 *  @brief Get an int environment var or return default if does not exist
 *
 *  @details Given a const char* @p name and a default int @p def
 *  and call getenv with name. On any error, return default int
 *
 *  @param[in] name a const char* containing ENV var name
 *
 *  @param[in] def default int in case of error
 *
 *  @retval int of environment variable
 */
int read_env_ms(const char* name, int def);

template <typename>
constexpr bool is_dependent_false_v = false;

template <typename T>
inline constexpr bool is_supported_type_v =
    (std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::uint8_t> ||
     std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::uint16_t> ||
     std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::uint32_t> ||
     std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::uint64_t>);

template <typename T>
constexpr T get_std_num_limit() {
  if constexpr (is_supported_type_v<T>) {
    return std::numeric_limits<T>::max();
  } else {
    return std::numeric_limits<T>::min();
    static_assert(is_dependent_false_v<T>, "Error: Type not supported...");
  }
}

template <typename T>
constexpr bool is_std_num_limit(T value) {
  return (value == get_std_num_limit<T>());
}

template <typename T, typename U, typename V = T>
constexpr T translate_umax_or_assign_value(U source_value, V target_value) {
  T result{};
  if constexpr (is_supported_type_v<T> && is_supported_type_v<U>) {
    // If the source value is uint<U>::max(), then return is uint<T>::max()
    if (is_std_num_limit(source_value)) {
      result = get_std_num_limit<T>();
    } else {
      result = static_cast<T>(target_value);
    }

    return result;
  } else {
    static_assert(is_dependent_false_v<T>, "Error: Type not supported...");
  }

  return result;
}

template <typename A, typename T>
void fill_2d_array(A& arr, T value) {
  for (auto& row : arr) {
    std::fill(std::begin(row), std::end(row), value);
  }
}

std::string_view trim(std::string_view str);

template <typename Tp, typename = std::enable_if_t<std::is_integral_v<Tp>>>
std::optional<Tp> parse_number_from_string(std::string_view str) {
  auto trimmed_str = trim(str);
  if (trimmed_str.empty()) {
    return std::nullopt;
  }

  auto value = Tp{};
  auto [ptr, error_code] =
      std::from_chars(trimmed_str.data(), (trimmed_str.data() + trimmed_str.size()), value);
  if ((error_code == std::errc{}) && (ptr == (trimmed_str.data() + trimmed_str.size()))) {
    return value;
  }

  return std::nullopt;
}

/**
 *  @brief Get the product serial number given the processor handle.
 *
 *  @param[in] processor_handle a pointer to amdsmi_processor_handle
 *  which the corresponding processor_handle will be stored
 *
 *  @retval ::The serial number
 *          ::0 if it cannot be determined
 */
uint64_t get_product_serial_number(amdsmi_processor_handle processor_handle);

/*
 *  Note:
 *      - A full 128-bit UUID is 16 bytes
 *      - Therefore, the total number of hexadecimal digits needed to represent a full 128-bit UUID
 * is 32
 *          - (16 bytes * 2 hexadecimal digits per byte)
 */
constexpr auto HIP_UUID_BYTES_SIZE = size_t(16);
constexpr auto HIP_UUID_STRING_FULL_SIZE = size_t(HIP_UUID_BYTES_SIZE * 2);
typedef struct hipUUID_t {
  // Pointer to raw UUID bytes (length = HIP_UUID_BYTES_SIZE), not null-terminated
  char bytes[HIP_UUID_BYTES_SIZE];
} hipUUID_t;

const char* from_uuid_to_cstring(const hipUUID_t& uuid) noexcept;
std::optional<amdsmi_bdf_t> from_cstring_to_bdf(const char* bdf_str) noexcept;
std::optional<hipUUID_t> from_cstring_to_uuid(const char* uuid_str) noexcept;
std::string stringify_bdf(const amdsmi_bdf_t& bdf);

/**
 *  @brief Tokenize bdfid into components.
 *
 *  @param[in] bdfid a uint64_t containing the bdfid
 *
 *  @retval ::Tuple of domain, bus, device, function
 */
std::tuple<uint64_t, uint64_t, uint64_t, uint64_t> parse_bdfid(uint64_t bdfid);

/**
 *  @brief Read a pp_dpm_* sysfs file and populate an amdsmi_frequencies_t.
 *
 *  @details Parses the kernel's per-domain DPM table (e.g. pp_dpm_vclk,
 *  pp_dpm_dclk) for the given device. Lines look like:
 *      0: 200Mhz
 *      1: 400Mhz *
 *      2: 800Mhz
 *  The '*' marker is recorded into @p f->current.
 *
 *  @param[in]  device       Device whose render node sysfs is read.
 *  @param[in]  pp_dpm_file  Basename of the sysfs file (e.g. "pp_dpm_vclk").
 *  @param[out] f            Frequencies struct to populate.
 *
 *  @retval ::AMDSMI_STATUS_SUCCESS on success.
 *          ::AMDSMI_STATUS_INVAL if @p f is null.
 *          ::AMDSMI_STATUS_NOT_SUPPORTED if the sysfs file is missing or empty.
 */
amdsmi_status_t smi_amdgpu_read_clk_freq_from_pp_dpm(amd::smi::AMDSmiGPUDevice* device,
                                                     const char* pp_dpm_file,
                                                     amdsmi_frequencies_t* f);

/**
 *  @brief Map a VCLK/DCLK clock type to its pp_dpm_* sysfs filename.
 *
 *  rsmi does not expose these clock types via gpu_metrics, so amdsmi reads
 *  them directly from sysfs.
 *
 *  @param[in] clk_type Clock type.
 *
 *  @retval Pointer to a static filename string, or nullptr for clock types
 *          that are not VCLK0/VCLK1/DCLK0/DCLK1.
 */
const char* smi_amdgpu_pp_dpm_filename_for_clk_type(amdsmi_clk_type_t clk_type);

#endif  // AMD_SMI_INCLUDE_AMD_SMI_UTILS_H_
