#ifndef _NVK_UTILS_
#define _NVK_UTILS_

#include <nvk_common.h>

#include <nvk/base/RefObject.h>
#include <nvk/base/RefPtr.h>
#include <nvk/base/std_containers.h>

namespace nv {

template <typename T> class Promise;

// Returns current physical RAM usage in bytes, or 0 on failure.
auto get_current_rss() -> U64;

auto base64_encode(const U8* data, size_t len) -> String;
auto base64_encode(const Vector<U8>& data) -> String;
auto base64_decode(const String& encoded_string) -> Vector<U8>;

auto toHex(const U8Vector& data) -> String;
auto fromHex(const String& hex) -> U8Vector;

auto toHexString(const String& s) -> String;
auto toUpperHexString(const String& s) -> String;

auto fromHexString(const String& hex) -> String;

// Helper function to convert glob pattern to regex
auto glob_to_regex(const String& pattern) -> String;

// Return true if 'name' matches 'pattern' using full glob rules.
// The pattern is anchored (must match the entire string), so:
//   "*"        matches any name
//   "mxs.*"    matches "mxs.backend_panel", "mxs.rcs_panel", etc.
//   "200113_*" matches "200113_09_MX20HD", "200113_anything", etc.
//   "exact"    matches only that exact string
auto matches_pattern(const String& name, const String& pattern) -> bool;

// Return true if 'name' is covered by at least one pattern in 'patterns'.
// Patterns are evaluated in order. A pattern prefixed with '!' is a deny rule:
// if the name matches it, the function immediately returns false, short-
// circuiting any remaining allow patterns. This allows expressions like:
//   { "!mxs.advanced.*", "mxs.*" }  — all mxs.* except mxs.advanced.*
//   { "!test_*", "*" }              — everything except test_* entries
auto matches_any_pattern(const String& name, const Vector<String>& patterns)
    -> bool;

// Helper function to expand file wildcards
auto expand_files_wildcard(const String& sourceDir, const String& pattern)
    -> Set<String>;

auto get_relative_path(const String& filepath, const String& parent) -> String;

// Sleep functions
void sleep_s(U32 secs);
void sleep_ms(U32 msecs);
void sleep_us(U32 usecs);

// Convert a string to upper characters:
auto to_upper(const String& str) -> String;

// Convert a string to lower characters:
auto to_lower(const String& str) -> String;

void trim(String& s);

auto trimmed(String s) -> String;

auto system_file_exists(const char* fname) -> bool;

inline auto system_file_exists(const String& fname) -> bool {
    return system_file_exists(fname.c_str());
}

auto system_dir_exists(const char* path) -> bool;

inline auto system_dir_exists(const String& fname) -> bool {
    return system_dir_exists(fname.c_str());
}

/** Check if a virtual file exists. */
auto virtual_file_exists(const String& fname, bool forceAllowSystem = false)
    -> bool;

auto get_file_last_write_time(const String& fname) -> std::time_t;

auto is_absolute_path(const String& path) -> bool;
auto is_relative_path(const String& path) -> bool;

auto get_absolute_path(const String& path) -> String;

auto read_system_file(const char* fname) -> String;
auto read_system_binary_file(const char* fname) -> U8Vector;

auto read_virtual_file(const String& fname, bool forceAllowSystem = false)
    -> String;

auto read_virtual_file_async(const String& fname, bool forceAllowSystem = false)
    -> Promise<String>;

auto get_virtual_files(const String& directory, const std::regex& pattern,
                       bool recursive) -> Vector<String>;

void write_file(const char* fname, const String& content,
                bool createFolders = true);

void write_binary_file(const char* fname, const U8Vector& content,
                       bool createFolders = true);

void remove_file(const char* fname);
void remove_file(const String& fname);
auto remove_file_if_exists(const String& fname) -> bool;

void move_path(const String& src, const String& dest);

auto read_json_string(const String& content) -> Json;

auto read_json_file(const String& fname, bool forceAllowSystem = false) -> Json;

auto read_ordered_json_string(const String& content) -> OrderedJson;

auto read_ordered_json_file(const String& fname, bool forceAllowSystem = false)
    -> OrderedJson;

void write_json_file(const char* fname, const Json& content, I32 indent = 2);

auto read_yaml_string(const String& content) -> Json;

auto read_yaml_file(const String& fname, bool forceAllowSystem = false) -> Json;

auto read_config_file(const String& fname, bool forceAllowSystem = false)
    -> Json;

auto get_file_extension(const char* filename) -> String;

auto is_yaml_file(const char* filename) -> bool;
auto is_yaml_file(const String& filename) -> bool;

auto is_json_file(const char* filename) -> bool;
auto is_json_file(const String& filename) -> bool;

auto create_folders(const String& fullpath) -> bool;

auto get_path_extension(const String& fname) -> String;

enum PathSep {
    PATHSEP_NONE,
    PATHSEP_LINUX,
    PATHSEP_WIN,
};

void normalize_path(String& path, bool isFolder, PathSep sep = PATHSEP_LINUX);

auto normalized_path(const String& path, bool isFolder,
                     PathSep sep = PATHSEP_LINUX) -> String;

auto get_parent_folder(const char* fname, PathSep sep = PATHSEP_LINUX)
    -> String;

auto get_parent_folder(const String& fname, PathSep sep = PATHSEP_LINUX)
    -> String;

auto get_filename(const String& full_path, bool withExt = true) -> String;

// Convert wide string to String:
auto toString(const std::wstring& wstr) -> String;

// Convert string to String:
auto toString(const std::string& wstr) -> String;

// Get the current working directory
auto get_cwd() -> String;

// Get the user home directory
auto get_home_dir() -> String;

auto copy_file(const char* source_path, const char* dest_path,
               U32 buffer_size = 64 * 1024, bool createFolders = true) -> bool;

auto copy_file(const String& source_path, const String& dest_path,
               U32 buffer_size = 64 * 1024, bool createFolders = true) -> bool;

auto get_files(const String& directory, bool recursive = false)
    -> Vector<String>;

auto get_files(const String& directory, const std::regex& pattern,
               bool recursive) -> Vector<String>;

auto make_extensions_regex(const Vector<String>& extensions) -> std::regex;

// Replace all instances of a given string inside another string:
void replace_all(String& str, const String& old_value, const String& new_value);

// Forward/Right/Up to LLA
auto fruToLLA(const Vec3d& xyz, F64 radius = MEAN_EARTH_RADIUS) -> Vec3d;
auto llaToFRU(const Vec3d& lla, F64 radius = MEAN_EARTH_RADIUS) -> Vec3d;

// Forward/Left/Up to LLA
auto fluToLLA(const Vec3d& xyz, F64 radius = MEAN_EARTH_RADIUS) -> Vec3d;
auto llaToFLU(const Vec3d& lla, F64 radius = MEAN_EARTH_RADIUS) -> Vec3d;

// Align a given byte size to alignment boundaries:
auto align_element_size(U32 elemSize, U32 alignment) -> U32;

auto color_to_f32(const Vec4f& col) -> F32;

auto get_current_module_path(PathSep sep = PATHSEP_LINUX) -> String;

auto get_current_module_folder(PathSep sep = PATHSEP_LINUX) -> String;

// Support to concatenate path elements:
template <typename T, typename V>
auto get_path(const T& arg0, const V& arg1) -> String {
    String p1(arg0);
    if (p1.back() == '/') {
        return p1 + String(arg1);
    }

    return p1 + "/" + String(arg1);
}

template <typename T, typename V, typename... Args>
auto get_path(const T& arg0, const V& arg1, const Args&... args) -> String {
    String p1(arg0);
    if (p1.back() == '/') {
        return get_path(p1 + String(arg1), args...);
    }

    return get_path(p1 + "/" + String(arg1), args...);
}

template <typename T> auto get_path(const T& arg) -> String {
    return String(arg);
};

inline auto get_path(String&& arg) -> String { return std::move(arg); };

template <typename... Args>
auto format_string(const String& format, Args... args) -> String {
    size_t size = snprintf(nullptr, 0, format.c_str(), args...) +
                  1; // Extra space for '\0'
    std::unique_ptr<char[]> buf(new char[size]);
    snprintf(buf.get(), size, format.c_str(), args...);
    return String(buf.get(),
                  buf.get() + size - 1); // We don't want the '\0' inside
};

template <typename T>
auto remove_vector_element(Vector<T>& vec, const T& val) -> bool {
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        if ((*it) == val) {
            vec.erase(it);
            return true;
        }
    }
    return false;
}

template <typename T>
auto remove_vector_element(Vector<RefPtr<T>>& vec, T* val) -> bool {
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        if ((*it) == val) {
            vec.erase(it);
            return true;
        }
    }
    return false;
}

template <typename T>
auto vector_contains(const Vector<T>& vec, const T& val) -> bool {
    return std::find(vec.begin(), vec.end(), val) != vec.end();
}

#if !NV_USE_STD_MEMORY
template <typename T>
auto remove_vector_element(std::vector<RefPtr<T>>& vec, T* val) -> bool {
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        if ((*it) == val) {
            vec.erase(it);
            return true;
        }
    }
    return false;
}
#endif

template <typename Container>
auto compute_data_checksum(const Container& data) -> U32 {
    U32 checksum = 0;
    for (auto byte : data) {
        // For string, we need to cast char to unsigned to avoid sign
        // extension issues
        checksum = (checksum << 1) ^ static_cast<U8>(byte);
    }
    return checksum;
}

auto compute_file_checksum(const String& filename,
                           bool forceAllowSystem = false) -> U32;

template <typename T> inline auto mean(T* ptr, U64 num) -> T {

    if (num == 0) {
        return 0.0;
    }

    T meanVal = 0.0;

    for (U64 i = 0; i < num; ++i) {
        meanVal += (*ptr++);
    }

    return meanVal / (T)num;
}

template <typename T> inline auto mean(Vector<T> vec) -> T {
    return mean(vec.data(), vec.size());
}

template <typename T> inline auto mean(T* ptr, U64 num, T& mini, T& maxi) -> T {

    if (num == 0) {
        return 0.0;
    }

    T meanVal = 0.0;
    mini = ptr[0];
    maxi = ptr[0];

    for (U64 i = 0; i < num; ++i) {
        auto val = (*ptr++);
        if (val < mini) {
            mini = val;
        }
        if (val > maxi) {
            maxi = val;
        }

        meanVal += val;
    }

    return meanVal / (T)num;
}

template <typename T> inline auto mean(Vector<T> vec, T& mini, T& maxi) -> T {
    return mean(vec.data(), vec.size(), mini, maxi);
}

template <typename T> inline auto stddev(T* ptr, U64 num) -> T {

    if (num == 0) {
        return 0.0;
    }

    T meanVal = mean(ptr, num);
    T stddevVal = 0.0;

    for (U64 i = 0; i < num; ++i) {
        T diff = (*ptr++) - meanVal;
        stddevVal += diff * diff;
    }

    return std::sqrt(stddevVal / (T)num);
}

template <typename T> inline auto stddev(Vector<T> vec) -> T {
    return stddev(vec.data(), vec.size());
}

template <typename T>
inline auto stddev(T* ptr, U64 num, T& meanVal, T& mini, T& maxi) -> T {

    if (num == 0) {
        return 0.0;
    }

    meanVal = mean(ptr, num, mini, maxi);
    T stddevVal = 0.0;

    for (U64 i = 0; i < num; ++i) {
        T diff = (*ptr++) - meanVal;
        stddevVal += diff * diff;
    }

    return std::sqrt(stddevVal / (T)num);
}

template <typename T>
inline auto stddev(Vector<T> vec, T& meanVal, T& mini, T& maxi) -> T {
    return stddev(vec.data(), vec.size(), meanVal, mini, maxi);
}

template <typename T>
inline auto percentile(const T* ptr, U64 num, F64 percentile) -> T {

    if (num == 0) {
        return 0.0;
    }

    // Ensure percentile is within [0, 100]
    percentile = std::max(0.0, std::min(100.0, percentile));

    // Create a copy of the array:
    Vector<T> sortedArray(ptr, ptr + num);

    // Sort the copied array
    std::sort(sortedArray.begin(), sortedArray.end());

    // Calculate the index corresponding to the given percentile
    F64 index = (percentile / 100.0) * (F64)(num - 1);

    // Compute the lower and upper indices for interpolation
    U64 lowerIndex = static_cast<U64>(std::floor(index));
    U64 upperIndex = static_cast<U64>(std::ceil(index));

    // Interpolate between the values at lower and upper indices
    T lowerValue = sortedArray[lowerIndex];
    T upperValue = sortedArray[upperIndex];
    F64 fraction = index - (F64)lowerIndex;

    return static_cast<T>((1.0 - fraction) * lowerValue +
                          fraction * upperValue);
}

} // namespace nv

#endif
