#ifndef _TILE_BASE_INTERNAL_LOG_H
#define _TILE_BASE_INTERNAL_LOG_H

#pragma once
#include "tile/base/chrono.h"
#include "tile/base/internal/format.h"
#include "tile/base/internal/macro.h"
#include "tile/base/internal/utility.h"

#include <cstdint>
#include <ctime>
#include <set>
#include <sstream>
#include <vector>

#include <memory>
#include <string>
#include <type_traits>

namespace tile {
// typedef int LogSeverity;
// const int TILE_INFO = 0, TILE_WARNING = 1, TILE_ERROR = 2, TILE_FATAL = 3;
enum LogSeverity {
    TILE_INFO             = 0,
    TILE_WARNING          = 1,
    TILE_ERROR            = 2,
    TILE_FATAL            = 3,
    TILE_MAX_LOG_SEVERITY = TILE_FATAL
};

bool VLogIsOn(int n);

class LogStreamBuf : public std::streambuf {
public:
    LogStreamBuf(char *buf, int len) { setp(buf, buf + len - 2); }

    int_type overflow(int_type ch) { return ch; }

    size_t pcount() const { return static_cast<size_t>(pptr() - pbase()); }

    char *pbase() const { return std::streambuf::pbase(); }
};

class LogSink;
class LogMessageTime;

class LogMessage {
public:
    class LogStream : public std::ostream {
    public:
        LogStream(char *buf, int len, int64_t ctr) : std::ostream(nullptr), streambuf_(buf, len), ctr_(ctr), self_(this)
        {
            rdbuf(&streambuf_);
        }

        LogStream(LogStream &other) noexcept
            : std::ostream(nullptr),
              streambuf_(std::move(other.streambuf_)),
              ctr_(internal::Exchange(other.ctr_, 0)),
              self_(this)
        {
            rdbuf(&streambuf_);
        }

        LogStream &operator=(LogStream &&other) noexcept
        {
            streambuf_ = std::move(other.streambuf_);
            ctr_       = internal::Exchange(other.ctr_, 0);
            self_      = this;
            rdbuf(&streambuf_);
            return *this;
        }

        int64_t ctr() const { return ctr_; }

        void set_ctr(int64_t ctr) { ctr_ = ctr; }

        LogStream *self() { return self_; }

        size_t pcount() const { return streambuf_.pcount(); }

        char *pbase() const { return streambuf_.pbase(); }

        char *str() const { return pbase(); }

        LogStream(const LogStream &)            = delete;
        LogStream &operator=(const LogStream &) = delete;

    private:
        LogStreamBuf streambuf_;
        int64_t ctr_;
        LogStream *self_;
    };

public:
    static const size_t kMaxLogMessageLen = 30000;
    LogMessage(const char *file, int line);
    LogMessage(const char *file, int line, LogSeverity severity);
    LogMessage(const char *file, int line, LogSeverity severity, LogSink *sink, bool also_send_to_log = true);

    ~LogMessage();
    std::ostream &stream();
    void Flush();

    LogSeverity severity() const noexcept;
    int line() const noexcept;
    const char *fullname() const noexcept;
    const char *basename() const noexcept;
    const LogMessageTime &time() const noexcept;
    int preserved_errno() const;

private:
    class Impl;
    std::unique_ptr<Impl> impl_;
};

class LogMessageFatal : public LogMessage {
public:
    LogMessageFatal(const char *file, int line);
    ~LogMessageFatal();
    std::ostream &stream();
};

class LogMessageVoidify {
public:
    LogMessageVoidify() {}

    // This has to be an operator with a precedence lower than << but
    // higher than ?:
    void operator&(std::ostream &) {}
};

class ErrnoLogMessage : public LogMessage {
public:
    ErrnoLogMessage(const char *file, int line, int severity, std::int64_t ctr, void (LogMessage::*send_method)());

    // Postpends ": strerror(errno) [errno]".
    ~ErrnoLogMessage();

private:
    ErrnoLogMessage(const ErrnoLogMessage &);
    void operator=(const ErrnoLogMessage &);
};

template<typename T>
inline auto
GetRef(const T &t) -> enable_if_t<!std::is_integral<T>::value, const T &>
{
    return t;
}

template<typename T>
inline auto
GetRef(T t) -> enable_if_t<std::is_integral<T>::value, remove_cvref_t<T>>
{
    return t;
}

#define DEFINE_CHECK_OP_IMPL(name, op)                                                                                 \
    template<typename T1, typename T2>                                                                                 \
    inline std::string *name##Impl(const T1 &v1, const T2 &v2, const char *exprtext)                                   \
    {                                                                                                                  \
        if ((v1) op(v2)) {                                                                                             \
            return nullptr;                                                                                            \
        } else {                                                                                                       \
            std::ostringstream oss;                                                                                    \
            oss << exprtext << " (" << v1 << " vs. " << v2 << "). ";                                                   \
            return new std::string(oss.str());                                                                         \
        }                                                                                                              \
    }                                                                                                                  \
    inline std::string *name##Impl(int v1, int v2, const char *exprtext)                                               \
    {                                                                                                                  \
        return name##Impl<int, int>(v1, v2, exprtext);                                                                 \
    }

DEFINE_CHECK_OP_IMPL(Check_EQ, ==)
DEFINE_CHECK_OP_IMPL(Check_NE, !=)
DEFINE_CHECK_OP_IMPL(Check_LE, <=)
DEFINE_CHECK_OP_IMPL(Check_LT, <)
DEFINE_CHECK_OP_IMPL(Check_GE, >=)
DEFINE_CHECK_OP_IMPL(Check_GT, >)
#undef DEFINE_CHECK_OP_IMPL

}// namespace tile

#define TILE_INTERNAL_LOG_INFO() ::tile::LogMessage(__FILE__, __LINE__, ::tile::TILE_INFO).stream()
#define TILE_INTERNAL_LOG_WARNING() ::tile::LogMessage(__FILE__, __LINE__, ::tile::TILE_WARNING).stream()
#define TILE_INTERNAL_LOG_ERROR() ::tile::LogMessage(__FILE__, __LINE__, ::tile::TILE_ERROR).stream()
#define TILE_INTERNAL_LOG_FATAL() ::tile::LogMessageFatal(__FILE__, __LINE__).stream()

#define TILE_INTERNAL_LOG_TILE_INFO() ::tile::LogMessage(__FILE__, __LINE__, ::tile::TILE_INFO).stream()
#define TILE_INTERNAL_LOG_TILE_WARNING() ::tile::LogMessage(__FILE__, __LINE__, ::tile::TILE_WARNING).stream()
#define TILE_INTERNAL_LOG_TILE_ERROR() ::tile::LogMessage(__FILE__, __LINE__, ::tile::TILE_ERROR).stream()
#define TILE_INTERNAL_LOG_TILE_FATAL() ::tile::LogMessageFatal(__FILE__, __LINE__).stream()

#define TILE_INTERNAL_LOG_COMPACT(severity) TILE_INTERNAL_LOG_##severity()

#define TILE_LOG(severity) TILE_INTERNAL_LOG_COMPACT(severity)

#define TILE_LOG_IF(severity, condition, ...)                                                                          \
    TILE_INTERNAL_LOG_IF(severity, (condition))                                                                        \
        << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_INTERNAL_LOG_IF(severity, condition)                                                                      \
    static_cast<void>(0), !(condition) ? (void) 0 : tile::LogMessageVoidify() & TILE_INTERNAL_LOG_COMPACT(severity)
#define TILE_VLOG_IS_ON(n) (::tile::VLogIsOn(n))
#define TILE_VLOG(n, ...)                                                                                              \
    TILE_LOG_IF(TILE_INFO, TILE_VLOG_IS_ON(n))                                                                         \
        << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_CHECK_OP_LOG(name, op, val1, val2, severity)                                                              \
    while (std::string *_result =                                                                                      \
               tile::Check##name##Impl(tile::GetRef(val1), tile::GetRef(val2), #val1 " " #op " " #val2))               \
    TILE_INTERNAL_LOG_FATAL() << *_result

#define TILE_CHECK_OP(name, op, val1, val2) TILE_CHECK_OP_LOG(name, op, val1, val2, tile::LogMessageFatal)

#define TILE_CHECK(condition, ...)                                                                                     \
    TILE_INTERNAL_LOG_IF(TILE_FATAL, (!(condition)))                                                                   \
        << "Check failed: " #condition " " << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_CHECK_EQ(val1, val2, ...)                                                                                 \
    TILE_CHECK_OP(_EQ, ==, val1, val2) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_CHECK_NE(val1, val2, ...)                                                                                 \
    TILE_CHECK_OP(_NE, !=, val1, val2) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_CHECK_LE(val1, val2, ...)                                                                                 \
    TILE_CHECK_OP(_LE, <=, val1, val2) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_CHECK_LT(val1, val2, ...)                                                                                 \
    TILE_CHECK_OP(_LT, <, val1, val2) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_CHECK_GE(val1, val2, ...)                                                                                 \
    TILE_CHECK_OP(_GE, >=, val1, val2) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_CHECK_GT(val1, val2, ...)                                                                                 \
    TILE_CHECK_OP(_GE, >, val1, val2) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_CHECK_NEAR(val1, val2, margin, ...)                                                                       \
    do {                                                                                                               \
        TILE_CHECK_LE((val1), (val2) + margin, ##__VA_ARGS__)                                                          \
        TILE_CHECK_GE((val1), (val2) - margin, ##__VA_ARGS__)                                                          \
    } while (0)

#define TILE_LOG_INFO(...)                                                                                             \
    TILE_LOG(TILE_INFO) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)
#define TILE_LOG_WARNING(...)                                                                                          \
    TILE_LOG(TILE_WARNING) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)
#define TILE_LOG_ERROR(...)                                                                                            \
    TILE_LOG(TILE_ERROR) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)
#define TILE_LOG_FATAL(...)                                                                                            \
    TILE_LOG(TILE_FATAL) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_LOG_INFO_IF(expr, ...)                                                                                    \
    TILE_INTERNAL_LOG_IF(TILE_INFO, (expr)) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)
#define TILE_LOG_WARNING_IF(expr, ...)                                                                                 \
    TILE_INTERNAL_LOG_IF(TILE_WARNING, (expr))                                                                         \
        << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)
#define TILE_LOG_ERROR_IF(expr, ...)                                                                                   \
    TILE_INTERNAL_LOG_IF(TILE_ERROR, (expr)) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)
#define TILE_LOG_FATAL_IF(expr, ...)                                                                                   \
    TILE_INTERNAL_LOG_IF(TILE_FATAL, (expr)) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_LOG_EVERY_N_VARNAME(base, line) TILE_INTERNAL_PP_CAT(base, line)
#define TILE_LOG_OCCURRENCES() TILE_LOG_EVERY_N_VARNAME(tile_log_occurrences_, __LINE__)
#define TILE_LOG_OCCURRENCES_MOD_N() TILE_LOG_EVERY_N_VARNAME(tile_log_occurrences_mod_n_, __LINE__)

#define TILE_LOG_EVERY_N_IMPL(severity, N, ...)                                                                        \
    do {                                                                                                               \
        static std::atomic<int> TILE_LOG_OCCURRENCES_MOD_N{};                                                          \
        if ((TILE_LOG_OCCURRENCES_MOD_N = (TILE_LOG_OCCURRENCES_MOD_N + 1) % N) == (1 % N)) {                          \
            TILE_LOG(severity) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__);             \
        }                                                                                                              \
    } while (0)

#define TILE_LOG_INFO_EVERY_N(N, ...) TILE_LOG_EVERY_N_IMPL(TILE_INFO, N, ##__VA_ARGS__)
#define TILE_LOG_WARNING_EVERY_N(N, ...) TILE_LOG_EVERY_N_IMPL(TILE_WARNING, N, ##__VA_ARGS__)
#define TILE_LOG_ERROR_EVERY_N(N, ...) TILE_LOG_EVERY_N_IMPL(TILE_WARNING, N, ##__VA_ARGS__)
#define TILE_LOG_FATAL_EVERY_N(N, ...) TILE_LOG_EVERY_N_IMPL(TILE_FATAL, N, ##__VA_ARGS__)

#define TILE_LOG_IF_EVERY_N_IMPL(severity, expr, N, ...)                                                               \
    do {                                                                                                               \
        if (expr) { TILE_LOG_EVERY_N_IMPL(severity, N, ##__VA_ARGS__) }                                                \
    } while (0)

#define TILE_LOG_INFO_IF_EVERY_N(expr, N, ...) TILE_LOG_IF_EVERY_N_IMPL(TILE_INFO, N, ##__VA_ARGS__)
#define TILE_LOG_WARNING_IF_EVERY_N(expr, N, ...) TILE_LOG_IF_EVERY_N_IMPL(TILE_WARNING, N, ##__VA_ARGS__)
#define TILE_LOG_ERROR_IF_EVERY_N(expr, N, ...) TILE_LOG_IF_EVERY_N_IMPL(TILE_WARNING, N, ##__VA_ARGS__)
#define TILE_LOG_FATAL_IF_EVERY_N(expr, N, ...) TILE_LOG_IF_EVERY_N_IMPL(TILE_FATAL, N, ##__VA_ARGS__)

#define TILE_LOG_ONCE_VARNAME(base, line) TILE_INTERNAL_PP_CAT(base, line)
#define TILE_LOG_ONCE_FLAG TILE_LOG_ONCE_VARNAME(tile_log_once_flag_, __LINE__)
#define TILE_LOG_ONCE_FLAG_EXPECTED TILE_LOG_ONCE_VARNAME(tile_log_once_flag_expected_, __LINE__)

#define TILE_LOG_ONCE_IMPL(severity, ...)                                                                              \
    do {                                                                                                               \
        static std::atomic<bool> TILE_LOG_ONCE_FLAG(false);                                                            \
        bool TILE_LOG_ONCE_FLAG_EXPECTED(false);                                                                       \
        if (!TILE_LOG_ONCE_FLAG.load()                                                                                 \
            && TILE_LOG_ONCE_FLAG.compare_exchange_strong(TILE_LOG_ONCE_FLAG_EXPECTED, true)) {                        \
            TILE_LOG(severity) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__);             \
        }                                                                                                              \
    } while (0)

#define TILE_LOG_INFO_ONCE(...) TILE_LOG_ONCE_IMPL(TILE_INFO, ##__VA_ARGS__)
#define TILE_LOG_WARNING_ONCE(...) TILE_LOG_ONCE_IMPL(TILE_WARNING, ##__VA_ARGS__)
#define TILE_LOG_ERROR_ONCE(...) TILE_LOG_ONCE_IMPL(TILE_WARNING, ##__VA_ARGS__)
#define TILE_LOG_FATAL_ONCE(...) TILE_LOG_ONCE_IMPL(TILE_FATAL, ##__VA_ARGS__)

#define TILE_LOG_IF_ONCE_IMPL(severity, expr, ...)                                                                     \
    do {                                                                                                               \
        if (expr) { TILE_LOG_ONCE_IMPL(severity, ##__VA_ARGS__); }                                                     \
    } while (0)

#define TILE_LOG_INFO_IF_ONCE(expr, ...) TILE_LOG_IF_ONCE_IMPL(TILE_INFO, expr, ##__VA_ARGS__)
#define TILE_LOG_WARNING_IF_ONCE(expr, ...) TILE_LOG_IF_ONCE_IMPL(TILE_WARNING, expr, ##__VA_ARGS__)
#define TILE_LOG_ERROR_IF_ONCE(expr, ...) TILE_LOG_IF_ONCE_IMPL(TILE_WARNING, expr, ##__VA_ARGS__)
#define TILE_LOG_FATAL_IF_ONCE(expr, ...) TILE_LOG_IF_ONCE_IMPL(TILE_FATAL, expr, ##__VA_ARGS__)

#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
#define TILE_ENABLE_DEBUG 0
#else
#define TILE_ENABLE_DEBUG 1
#endif

#if TILE_ENABLE_DEBUG
#define TILE_DCHECK(expr, ...) TILE_CHECK(expr, ##__VA_ARGS__)
#define TILE_DCHECK_EQ(a, b, ...) TILE_CHECK_EQ(a, b, ##__VA_ARGS__)
#define TILE_DCHECK_NE(a, b, ...) TILE_CHECK_NE(a, b, ##__VA_ARGS__)
#define TILE_DCHECK_LT(a, b, ...) TILE_CHECK_LT(a, b, ##__VA_ARGS__)
#define TILE_DCHECK_LE(a, b, ...) TILE_CHECK_LE(a, b, ##__VA_ARGS__)
#define TILE_DCHECK_GT(a, b, ...) TILE_CHECK_GT(a, b, ##__VA_ARGS__)
#define TILE_DCHECK_GE(a, b, ...) TILE_CHECK_GE(a, b, ##__VA_ARGS__)
#define TILE_DCHECK_NEAR(a, b, margin, ...) TILE_CHECK_NEAR(a, b, margin, ##__VA_ARGS__)

#define TILE_DLOG_INFO(...) TILE_LOG_INFO(__VA_ARGS__)
#define TILE_DLOG_WARNING(...) TILE_LOG_WARNING(__VA_ARGS__)
#define TILE_DLOG_ERROR(...) TILE_LOG_ERROR(__VA_ARGS__)
#define TILE_DLOG_FATAL(...) TILE_LOG_FATAL(__VA_ARGS__)

#define TILE_DLOG_INFO_IF(expr, ...) TILE_LOG_INFO_IF(expr, ##__VA_ARGS__)
#define TILE_DLOG_WARNING_IF(expr, ...) TILE_LOG_WARNING_IF(expr, ##__VA_ARGS__)
#define TILE_DLOG_ERROR_IF(expr, ...) TILE_LOG_ERROR_IF(expr, ##__VA_ARGS__)
#define TILE_DLOG_FATAL_IF(expr, ...) TILE_LOG_FATAL_IF(expr, ##__VA_ARGS__)

#define TILE_DLOG_INFO_EVERY_N(N, ...) TILE_LOG_INFO_EVERY_N(N, ##__VA_ARGS__)
#define TILE_DLOG_WARNING_EVERY_N(N, ...) TILE_LOG_WARNING_EVERY_N(N, __VA_ARGS__)
#define TILE_DLOG_ERROR_EVERY_N(N, ...) TILE_LOG_ERROR_EVERY_N(N, ##__VA_ARGS__)
#define TILE_DLOG_FATAL_EVERY_N(N, ...) TILE_LOG_FATAL_EVERY_N(N, ##__VA_ARGS__)

#define TILE_DLOG_INFO_IF_EVERY_N(expr, N, ...) TILE_LOG_INFO_IF_EVERY_N(expr, N, ##__VA_ARGS__)
#define TILE_DLOG_WARNING_IF_EVERY_N(expr, N, ...) TILE_LOG_WARNING_IF_EVERY_N(expr, N, ##__VA_ARGS__)
#define TILE_DLOG_ERROR_IF_EVERY_N(expr, N, ...) TILE_LOG_ERROR_IF_EVERY_N(expr, N, ##__VA_ARGS__)
#define TILE_DLOG_FATAL_IF_EVERY_N(expr, N, ...) TILE_LOG_FATAL_IF_EVERY_N(expr, N, ##__VA_ARGS__)

#define TILE_DLOG_ONCE(...) TILE_LOG_ONCE_IMPL(TILE_INFO, ##__VA_ARGS__)
#define TILE_DLOG_WARNING_ONCE(...) TILE_LOG_ONCE_IMPL(TILE_WARNING, ##__VA_ARGS__)
#define TILE_DLOG_ERROR_ONCE(...) TILE_LOG_ONCE_IMPL(TILE_WARNING, ##__VA_ARGS__)
#define TILE_DLOG_FATAL_ONCE(...) TILE_LOG_ONCE_IMPL(TILE_FATAL, ##__VA_ARGS__)

#define TILE_DLOG_INFO_IF_ONCE(expr, ...) TILE_LOG_IF_ONCE_IMPL(TILE_INFO, expr, ##__VA_ARGS__)
#define TILE_DLOG_WARNING_IF_ONCE(expr, ...) TILE_LOG_IF_ONCE_IMPL(TILE_WARNING, expr, ##__VA_ARGS__)
#define TILE_DLOG_ERROR_IF_ONCE(expr, ...) TILE_LOG_IF_ONCE_IMPL(TILE_WARNING, expr, ##__VA_ARGS__)
#define TILE_DLOG_FATAL_IF_ONCE(expr, ...) TILE_LOG_IF_ONCE_IMPL(TILE_FATAL, expr, ##__VA_ARGS__)

#else
#define TILE_DCHECK(expr, ...)                                                                                         \
    while (0) TILE_CHECK(expr)
#define TILE_DCHECK_EQ(a, b, ...)                                                                                      \
    while (0) TILE_CHECK_EQ(a, b)
#define TILE_DCHECK_NE(a, b, ...)                                                                                      \
    while (0) TILE_CHECK_NE(a, b)
#define TILE_DCHECK_LT(a, b, ...)                                                                                      \
    while (0) TILE_CHECK_LT(a, b)
#define TILE_DCHECK_LE(a, b, ...)                                                                                      \
    while (0) TILE_CHECK_LE(a, b)
#define TILE_DCHECK_GT(a, b, ...)                                                                                      \
    while (0) TILE_CHECK_GT(a, b)
#define TILE_DCHECK_GE(a, b, ...)                                                                                      \
    while (0) TILE_CHECK_GE(a, b)
#define TILE_DCHECK_NEAR(a, b, margin, ...)                                                                            \
    while (0) TILE_CHECK_NEAR(a, b, margin)

#define TILE_DLOG_INFO(...)                                                                                            \
    while (0) TILE_LOG_INFO(__VA_ARGS__)
#define TILE_DLOG_WARNING(...)                                                                                         \
    while (0) TILE_LOG_WARNING(__VA_ARGS__)
#define TILE_DLOG_ERROR(...)                                                                                           \
    while (0) TILE_LOG_ERROR(__VA_ARGS__)
#define TILE_DLOG_FATAL(...)                                                                                           \
    while (0) TILE_LOG_FATAL(__VA_ARGS__)

#define TILE_DLOG_INFO_IF(expr, ...)                                                                                   \
    while (0) TILE_LOG_INFO_IF(expr, ##__VA_ARGS__)
#define TILE_DLOG_WARNING_IF(expr, ...)                                                                                \
    while (0) TILE_LOG_WARNING_IF(expr, ##__VA_ARGS__)
#define TILE_DLOG_ERROR_IF(expr, ...)                                                                                  \
    while (0) TILE_LOG_ERROR_IF(expr, ##__VA_ARGS__)
#define TILE_DLOG_FATAL_IF(expr, ...)                                                                                  \
    while (0) TILE_LOG_FATAL_IF(expr, ##__VA_ARGS__)

#define TILE_DLOG_INFO_EVERY_N(N, ...)                                                                                 \
    while (0) TILE_LOG_INFO_EVERY_N(N, ##__VA_ARGS__)
#define TILE_DLOG_WARNING_EVERY_N(N, ...)                                                                              \
    while (0) TILE_LOG_WARNING_EVERY_N(N, ##__VA_ARGS__)
#define TILE_DLOG_ERROR_EVERY_N(N, ...)                                                                                \
    while (0) TILE_LOG_ERROR_EVERY_N(N, ##__VA_ARGS__)
#define TILE_DLOG_FATAL_EVERY_N(N, ...)                                                                                \
    while (0) TILE_LOG_FATAL_EVERY_N(N, ##__VA_ARGS__)

#define TILE_DLOG_INFO_IF_EVERY_N(expr, N, ...)                                                                        \
    while (0) TILE_LOG_INFO_IF_EVERY_N(expr, N, ##__VA_ARGS__)
#define TILE_DLOG_WARNING_IF_EVERY_N(expr, N, ...)                                                                     \
    while (0) TILE_LOG_WARNING_IF_EVERY_N(expr, N, ##__VA_ARGS__)
#define TILE_DLOG_ERROR_IF_EVERY_N(expr, N, ...)                                                                       \
    while (0) TILE_LOG_ERROR_IF_EVERY_N(expr, N, ##__VA_ARGS__)
#define TILE_DLOG_FATAL_IF_EVERY_N(expr, N, ...)                                                                       \
    while (0) TILE_LOG_FATAL_IF_EVERY_N(expr, N, ##__VA_ARGS__)

#define TILE_DLOG_ONCE(...)                                                                                            \
    while (0) TILE_LOG_ONCE_IMPL(TILE_INFO, ##__VA_ARGS__)
#define TILE_DLOG_WARNING_ONCE(...)                                                                                    \
    while (0) TILE_LOG_ONCE_IMPL(TILE_WARNING, ##__VA_ARGS__)
#define TILE_DLOG_ERROR_ONCE(...)                                                                                      \
    while (0) TILE_LOG_ONCE_IMPL(TILE_WARNING, ##__VA_ARGS__)
#define TILE_DLOG_FATAL_ONCE(...)                                                                                      \
    while (0) TILE_LOG_ONCE_IMPL(TILE_FATAL, ##__VA_ARGS__)

#define TILE_DLOG_INFO_IF_ONCE(expr, ...)                                                                              \
    while (0) TILE_LOG_IF_ONCE_IMPL(TILE_INFO, expr, ##__VA_ARGS__)
#define TILE_DLOG_WARNING_IF_ONCE(expr, ...)                                                                           \
    while (0) TILE_LOG_IF_ONCE_IMPL(TILE_WARNING, expr, ##__VA_ARGS__)
#define TILE_DLOG_ERROR_IF_ONCE(expr, ...)                                                                             \
    while (0) TILE_LOG_IF_ONCE_IMPL(TILE_WARNING, expr, ##__VA_ARGS__)
#define TILE_DLOG_FATAL_IF_ONCE(expr, ...)                                                                             \
    while (0) TILE_LOG_IF_ONCE_IMPL(TILE_FATAL, expr, ##__VA_ARGS__)
#endif

#define TILE_INTERNAL_PLOG(severity) ::tile::LogMessageFatal(__FILE__, __LINE__).stream()

#define TILE_INTERNAL_PLOG_IF(severity, expr, ...)                                                                     \
    static_cast<void>(0), !(expr) ? (void) 0 : tile::LogMessageVoidify() & TILE_INTERNAL_PLOG(severity)

#define TILE_PCHECK(expr, ...)                                                                                         \
    TILE_INTERNAL_PLOG_IF(::tile::TILE_FATAL, (!(expr)))                                                               \
        << "Check failed: " #expr " " << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__)

#define TILE_PLOG_INFO(...) TILE_INTERNAL_PLOG(TILE_INFO, ##__VA_ARGS__)
#define TILE_PLOG_WARNING(...) TILE_INTERNAL_PLOG(TILE_WARNING, ##__VA_ARGS__)
#define TILE_PLOG_ERROR(...) TILE_INTERNAL_PLOG(TILE_WARNING, ##__VA_ARGS__)
#define TILE_PLOG_FATAL(...) TILE_INTERNAL_PLOG(TILE_FATAL, ##__VA_ARGS__)

#define TILE_PLOG_INFO_IF(expr, ...) TILE_INTERNAL_PLOG_IF(TILE_INFO, expr, ##__VA_ARGS__)
#define TILE_PLOG_WARNING_IF(expr, ...) TILE_INTERNAL_PLOG_IF(TILE_WARNING, expr, ##__VA_ARGS__)
#define TILE_PLOG_ERROR_IF(expr, ...) TILE_INTERNAL_PLOG_IF(TILE_WARNING, expr, ##__VA_ARGS__)
#define TILE_PLOG_FATAL_IF(expr, ...) TILE_INTERNAL_PLOG_IF(TILE_FATAL, expr, ##__VA_ARGS__)

#define TILE_UNREACHABLE(...)                                                                                          \
    do {                                                                                                               \
        TILE_INTERNAL_LOG_TILE_FATAL() << "Unreachable code reached. "                                                 \
                                       << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__);     \
    } while (1)
#define TILE_NOT_IMPLEMENTED(...)                                                                                      \
    do {                                                                                                               \
        TILE_INTERNAL_LOG_TILE_FATAL() << "Not implemented. "                                                          \
                                       << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__);     \
    } while (1)
#define TILE_UNEXPECTED(...)                                                                                           \
    do {                                                                                                               \
        TILE_INTERNAL_LOG_TILE_FATAL() << "Unexpected condition. "                                                     \
                                       << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__);     \
    } while (1)

#define TILE_LOG_EVERY_SECOND_IMPL(severity, N, ...)                                                                   \
    do {                                                                                                               \
        static ::std::chrono::nanoseconds tile_anonmous_log_last_occurs{};                                             \
        if (::tile::ReadCoarseSteadyClock().time_since_epoch()                                                         \
            > std::chrono::seconds(N) + tile_anonmous_log_last_occurs) {                                               \
            static ::std::atomic<bool> tile_anonymous_logging{false};                                                  \
            if (::tile::internal::TestAndSet(tile_anonymous_logging, false, true)) {                                   \
                TILE_LOG(severity) << ::tile::internal::logging::FormatLog(__FILE__, __LINE__, ##__VA_ARGS__);         \
                tile_anonmous_log_last_occurs = ::tile::ReadCoarseSteadyClock().time_since_epoch();                    \
                tile_anonymous_logging.store(false);                                                                   \
            }                                                                                                          \
        }                                                                                                              \
    } while (0)

#define TILE_LOG_INFO_EVERY_SECOND(...) TILE_LOG_EVERY_SECOND_IMPL(TILE_INFO, 1, ##__VA_ARGS__)
#define TILE_LOG_WARNING_EVERY_SECOND(...) TILE_LOG_EVERY_SECOND_IMPL(TILE_WARNING, 1, ##__VA_ARGS__)
#define TILE_LOG_ERROR_EVERY_SECOND(...) TILE_LOG_EVERY_SECOND_IMPL(TILE_WARNING, 1, ##__VA_ARGS__)
#define TILE_LOG_FATAL_EVERY_SECOND(...) TILE_LOG_EVERY_SECOND_IMPL(TILE_FATAL, 1, ##__VA_ARGS__)

#define TILE_LOG_IF_EVERY_SECOND_IMPL(severity, expr, N, ...)                                                          \
    do {                                                                                                               \
        if (expr) { TILE_LOG_EVERY_SECOND_IMPL(severity, N, ##__VA_ARGS__); }                                          \
    } while (0)

#define TILE_LOG_INFO_IF_EVERY_SECOND(expr, ...) TILE_LOG_IF_EVERY_SECOND_IMPL(TILE_INFO, expr, 1, ##__VA_ARGS__)
#define TILE_LOG_WARNING_IF_EVERY_SECOND(expr, ...) TILE_LOG_IF_EVERY_SECOND_IMPL(TILE_WARNING, expr, 1, ##__VA_ARGS__)
#define TILE_LOG_ERROR_IF_EVERY_SECOND(expr, ...) TILE_LOG_IF_EVERY_SECOND_IMPL(TILE_WARNING, expr, 1, ##__VA_ARGS__)
#define TILE_LOG_FATAL_IF_EVERY_SECOND(expr, ...) TILE_LOG_IF_EVERY_SECOND_IMPL(TILE_FATAL, expr, 1, ##__VA_ARGS__)

namespace tile {
namespace internal {
namespace logging {
namespace details {
template<typename T>
std::string
ToString(const T &value)
{
    return fmt::format("{}", value);
}

std::string DescribeFormatArguments(const std::vector<std::string> &args);

}// namespace details

using PrefixAppender = void(std::string *to);

void InstallPrefixProvider(PrefixAppender *writer);
void WritePrefixTo(std::string *to);

#define TILE_INTERNAL_LOGGING_REGISTER_PREFIX_PROVIDER(priority, cb)                                                   \
    __attribute__((constructor(priority + 101))) static void TILE_INTERNAL_PP_CAT(                                     \
        tile_reserved_logging_prefix_installer_, __COUNTER__)()                                                        \
    {                                                                                                                  \
        ::tile::internal::logging::InstallPrefixProvider(cb);                                                          \
    }

template<typename... Args>
void
FormatLogImpl(std::string *str, const char *file, int line, Args &&...args)
{
    try {
        *str += fmt::format(std::forward<Args>(args)...);
    } catch (const std::exception &xcpt) {
        *str = fmt::format("FAILED TO FORMAT LOG AT [{}:{}] WITH ARGUMENTS ({}): {}", file, line,
                           details::DescribeFormatArguments({details::ToString(args)...}), xcpt.what());
    }
}

inline void
FormatLogImpl(std::string *str, const char *file, int line)
{}

template<typename... Ts>
std::string
FormatLog(const char *file, int line, Ts &&...args) noexcept
{
    std::string result;
    WritePrefixTo(&result);
    FormatLogImpl(&result, file, line, std::forward<Ts>(args)...);
    return result;
}
}// namespace logging
}// namespace internal
}// namespace tile

namespace tile {
typedef double WallTime;

struct LogMessageTime {
    LogMessageTime();
    LogMessageTime(std::tm t);
    LogMessageTime(std::time_t timestamp, WallTime now);
    LogMessageTime(std::chrono::system_clock::time_point tp);

    const time_t &timestamp() const { return timestamp_; }

    const int &sec() const { return time_struct_.tm_sec; }

    const int32_t &usec() const { return usecs_; }

    const int &(min) () const { return time_struct_.tm_min; }

    const int &hour() const { return time_struct_.tm_hour; }

    const int &day() const { return time_struct_.tm_mday; }

    const int &month() const { return time_struct_.tm_mon; }

    const int &year() const { return time_struct_.tm_year; }

    const int &dayOfWeek() const { return time_struct_.tm_wday; }

    const int &dayInYear() const { return time_struct_.tm_yday; }

    const int &dst() const { return time_struct_.tm_isdst; }

    const long int &gmtoff() const { return gmtoffset_; }

    const std::tm &tm() const { return time_struct_; }

    const bool use_localtime() const { return use_localtime_; }

private:
    void init(const std::tm &t, std::time_t timestamp, WallTime now);
    std::tm time_struct_;// Time of creation of LogMessage
    time_t timestamp_;   // Time of creation of LogMessage in seconds
    int32_t usecs_;      // Time of creation of LogMessage - microseconds part
    long int gmtoffset_;
    bool use_localtime_;

    void CalcGmtOffset();
};

class LogSink {
public:
    using Ptr = std::shared_ptr<LogSink>;

    virtual ~LogSink();
    virtual const char *name() const = 0;
    virtual bool ShouldLog(LogSeverity severity);
    virtual void Send(LogSeverity severity,
                      const char *full_filename,
                      const char *base_filename,
                      int line,
                      const LogMessageTime &logmsgtime,
                      const char *message,
                      size_t message_len);
    virtual void Flush();
    std::string ToString(LogSeverity severity,
                         const char *file,
                         const char *base_file,
                         int line,
                         const LogMessageTime &logmsgtime,
                         const char *message,
                         size_t message_len);
};

void LogUseLocaltime(bool use_localtime);
void ColoredWriteToStdout(LogSeverity severity, const char *message, size_t len);
void ColoredWriteToStderr(LogSeverity severity, const char *message, size_t len);
void AddLogSink(LogSink::Ptr dest);
void RemoveLogSink(LogSink::Ptr dest);
void SetStderrLogging(LogSeverity min_severity);
void LogToStderr();
void LogToSinks(LogSeverity severity,
                const char *full_filename,
                const char *base_filename,
                int line,
                const LogMessageTime &time,
                const char *message,
                size_t message_len);
void WaitForSinks();
const char *GetLogSeverityName(LogSeverity severity);

}// namespace tile
#endif// _TILE_BASE_INTERNAL_LOG_H
