#ifndef TILE_BASE_INTERNAL_FORMAT_H
#define TILE_BASE_INTERNAL_FORMAT_H

#pragma once

#include "tile/base/internal/meta.h"

#include <chrono>
#include <sstream>

namespace tile {
namespace detail {
template<typename S, typename T>
struct is_streamable {
    template<typename SS, typename TT>
    static auto Test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
    template<typename...>
    static auto Test(...) -> std::false_type;
    static constexpr bool value = decltype(Test<S, T>(0))::value;
};

template<typename T>
struct has_to_string {
    template<typename U>
    static auto Test(int) -> decltype(std::declval<U>().ToString(), std::true_type());
    template<typename...>
    static auto Test(...) -> std::false_type;
    static constexpr bool value = decltype(Test<T>(0))::value;
};

template<typename T>
struct can_to_string {
    template<typename U>
    static auto Test(int) -> decltype(ToString(std::declval<U>()), std::true_type());
    template<typename...>
    static auto Test(...) -> std::false_type;
    static constexpr bool value = decltype(Test<T>(0))::value;
};

template<typename Rep, typename Period>
std::string
format_as_impl(const std::chrono::duration<Rep, Period> &val, const char *suffix)
{
    std::stringstream ss;
    ss << val.count() << suffix;
    return ss.str();
}

}// namespace detail
}// namespace tile

template<typename T>
auto
format_as(const T &val)
    -> tile::enable_if_t<tile::detail::is_streamable<std::ostream, T>::value
                             && !std::is_same<std::string, tile::internal::remove_cvref_t<T>>::value,
                         std::string>
{
    std::stringstream ss;
    ss << val;
    return ss.str();
}

template<typename T>
auto
format_as(const T &val)
    -> tile::enable_if_t<!tile::detail::is_streamable<std::ostream, T>::value && tile::detail::has_to_string<T>::value
                             && !tile::detail::can_to_string<T>::value,
                         std::string>
{
    return val.ToString();
}

template<typename T>
auto
format_as(const T &val)
    -> tile::enable_if_t<!tile::detail::is_streamable<std::ostream, T>::value && !tile::detail::has_to_string<T>::value
                             && tile::detail::can_to_string<T>::value,
                         std::string>
{
    return ToString(val);
}

template<typename T>
auto
format_as(T *ptr) -> tile::enable_if_t<!std::is_void<tile::remove_cvref_t<T>>::value, void *>
{
    return reinterpret_cast<void *>(ptr);
}

#define TILE_INSTANCE_CHRONO_FORMAT_AS(type, suffix)                                                                   \
    inline std::string format_as(const type &val) { return tile::detail::format_as_impl(val, suffix); }

TILE_INSTANCE_CHRONO_FORMAT_AS(std::chrono::nanoseconds, "ns")
TILE_INSTANCE_CHRONO_FORMAT_AS(std::chrono::microseconds, "us")
TILE_INSTANCE_CHRONO_FORMAT_AS(std::chrono::milliseconds, "ms")
TILE_INSTANCE_CHRONO_FORMAT_AS(std::chrono::seconds, "s")
TILE_INSTANCE_CHRONO_FORMAT_AS(std::chrono::minutes, "m")
TILE_INSTANCE_CHRONO_FORMAT_AS(std::chrono::hours, "h")
#if __cplusplus >= 202002L
TILE_INSTANCE_CHRONO_FORMAT_AS(std::chrono::days, "d")
TILE_INSTANCE_CHRONO_FORMAT_AS(std::chrono::weeks, "w")
TILE_INSTANCE_CHRONO_FORMAT_AS(std::chrono::months, "M")
TILE_INSTANCE_CHRONO_FORMAT_AS(std::chrono::years, "y")
#endif
#undef TILE_INSTANCE_CHRONO_FORMAT_AS

#include "fmt/core.h"
#include "fmt/format.h"
#include "fmt/ostream.h"

// inline std::ostream &operator<<(std::ostream &os, std::nullptr_t) {
//   return os << "nullptr";
// }

// template <typename T,
//           typename = tile::enable_if_t<
//               !tile::detail::is_streamable<std::ostream, T *>::value &&
//               !std::is_same<tile::remove_cvref_t<T *>,
//               std::nullptr_t>::value>>
// std::ostream &operator<<(std::ostream &os, T *ptr) {
//   return os << fmt::format("{}", fmt::ptr(ptr));
// }

#endif// TILE_BASE_INTERNAL_FORMAT_H
