#ifndef KNNCOLLE_ANNOY_UTILS_HPP
#define KNNCOLLE_ANNOY_UTILS_HPP

#include <string>
#include <cstddef>
#include <stdexcept>
#include <type_traits>
#include <functional>
#include <filesystem>

#include "knncolle/knncolle.hpp"

/**
 * @file utils.hpp
 * @brief Utilities for the Annoy wrappers.
 */

namespace knncolle_annoy {

/**
 * @cond
 */
template<typename Input_>
using I = std::remove_cv_t<std::remove_reference_t<Input_> >;

// Doing some SFINAE nonsense.
template<typename AnnoyDistance_, typename Other_ = int> 
struct has_name {
    static constexpr bool value = false;
};

template<typename AnnoyDistance_> 
struct has_name<AnnoyDistance_, decltype(AnnoyDistance_::name(), 0)> {
    static constexpr bool value = std::is_same<decltype(AnnoyDistance_::name()), const char*>::value;
};
/**
 * @endcond
 */

/**
 * @tparam AnnoyDistance_ An **Annoy**-compatible class to compute the distance between vectors, as used in `AnnoyBuilder()`.
 * @return Name of the distance metric, e.g., `"euclidean"`, `"manhattan"`.
 * This is taken from `AnnoyDistance_::name()` if such a method exists, otherwise `"unknown"` is returned.
 *
 * For unknown distances, consider using `custom_save_for_annoy_distance()` to add more information to the on-disk representation during a `knncolle::Prebuilt::save()` call.
 */
template<typename AnnoyDistance_> 
const char* get_distance_name() { 
    if constexpr(!has_name<AnnoyDistance_>::value) {
        return "unknown";
    } else {
        return AnnoyDistance_::name();
    }
}

/**
 * Define a global function to preserve `AnnoyIndex_` type information when saving a prebuilt Annoy index in `knncolle::Prebuilt::save()`.
 * Users should define their own function here to handle an `AnnoyIndex_` type that is unknown to `knncolle::get_numeric_type()`. 
 * The action of setting/unsetting the global function is not thread-safe and should be done in a serial section. 
 *
 * The sole argument of the global function is the same `dir` provided to `knncolle::Prebuilt::save()`.
 * If set, the global function is generally expected to write information about `AnnoyIndex_` to files inside `dir`.
 * It is recommended that the names of such files should not start with an upper-case letter to avoid conflicts with files generated by `save()`.
 *
 * @tparam AnnoyIndex_ Integer type for the observation indices in the Annoy index.
 *
 * @return Reference to a global function for saving information about `AnnoyIndex_`. 
 * By default, the global function is not set.
 * If set, the global function will be called by the `knncolle::Prebuilt::save()` method for the Annoy `knncolle::Prebuilt` subclass.
 */
template<class AnnoyIndex_>
std::function<void(const std::filesystem::path&)>& custom_save_for_annoy_index() {
    static std::function<void(const std::filesystem::path&)> fun;
    return fun;
}

/**
 * Define a saving function to preserve `AnnoyData_` type information when saving a prebuilt Annoy index in `knncolle::Prebuilt::save()`.
 * Users should define their own function here to handle an `AnnoyData_` type that is unknown to `knncolle::get_numeric_type()`. 
 * The action of setting/unsetting to the global function is not thread-safe and should be done in a serial section. 
 *
 * The sole argument of the global function is the same `dir` provided to `knncolle::Prebuilt::save()`.
 * If set, the global function is generally expected to write information about `AnnoyData_` to files inside `dir`.
 * It is recommended that the names of such files should not start with an upper-case letter to avoid conflicts with files generated by `save()`.
 *
 * @tparam AnnoyData_ Floating-point type for data in the Annoy index.
 *
 * @return Reference to a global function for saving information about `AnnoyData_`. 
 * By default, the global function is not set.
 * If set, the global function will be called by the `knncolle::Prebuilt::save()` method for the Annoy `Prebuilt` subclass.
 */
template<class AnnoyData_>
std::function<void(const std::filesystem::path&)>& custom_save_for_annoy_data() {
    static std::function<void(const std::filesystem::path&)> fun;
    return fun;
}

/**
 * Define a saving function to preserve `AnnoyDistance_` type information when saving a prebuilt Annoy index in `knncolle::Prebuilt::save()`.
 * Users should define their own function here to handle an `AnnoyDistance_` type that is unknown to `get_distance_name()`. 
 * The action of setting/unsetting to the global function is not thread-safe and should be done in a serial section. 
 *
 * The sole argument of the global function is the same `dir` provided to `knncolle::Prebuilt::save()`.
 * If set, the global function is generally expected to write information about `AnnoyDistance_` to files inside `dir`.
 * It is recommended that the names of such files should not start with an upper-case letter to avoid conflicts with files generated by `save()`.
 *
 * @tparam AnnoyDistance_ An **Annoy**-compatible class to compute the distance between vectors.
 *
 * @return Reference to a global function for saving information about `AnnoyDistance_`. 
 * By default, the global function is not set.
 * If set, the global function will be called by the `knncolle::Prebuilt::save()` method for the Annoy `Prebuilt` subclass.
 */
template<class AnnoyDistance_>
std::function<void(const std::filesystem::path&)>& custom_save_for_annoy_distance() {
    static std::function<void(const std::filesystem::path&)> fun;
    return fun;
}

}

#endif
