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

#include "glog/logging.h"

#include <map>
#include <mutex>

namespace tile {
bool
VLogIsOn(int n)
{
    return true;
}

class LogMessage::Impl {
public:
    Impl(const char *file, int line, int severity) : lm_(file, line, severity) {}

    std::ostream &stream() { return lm_.stream(); }

private:
    google::LogMessage lm_;
};

class LogMessageFatal::Impl {
public:
    Impl(const char *file, int line) : lm_(file, line) {}

    std::ostream &stream() { return lm_.stream(); }

private:
    google::LogMessageFatal lm_;
};

LogMessage::LogMessage(const char *file, int line, int severity) : impl_(new Impl(file, line, severity)) {}

LogMessage::~LogMessage() = default;

std::ostream &
LogMessage::stream()
{
    return impl_->stream();
}

LogMessageFatal::LogMessageFatal(const char *file, int line) : impl_(new Impl(file, line)) {}

LogMessageFatal::~LogMessageFatal() = default;

std::ostream &
LogMessageFatal::stream()
{
    return impl_->stream();
}

}// namespace tile

namespace tile {
namespace internal {
namespace logging {
namespace {
std::vector<PrefixAppender *> *
GetProviers()
{
    static std::vector<PrefixAppender *> providers;
    return &providers;
}
}// namespace

namespace details {
std::string
DescribeFormatArguments(const std::vector<std::string> &args)
{
    return fmt::format("{}", fmt::join(args.begin(), args.end(), ", "));
}

}// namespace details

void
InstallPrefixProvider(PrefixAppender *writer)
{
    GetProviers()->push_back(writer);
}

void
WritePrefixTo(std::string *to)
{
    for (auto &&appender : *GetProviers()) {
        auto was = to->size();
        appender(to);
        if (to->size() != was) { to->push_back(' '); }
    }
}
}// namespace logging
}// namespace internal
}// namespace tile

namespace tile {

LogMessageTime::LogMessageTime() : time_struct_(), timestamp_(0), usecs_(0), gmtoffset_(0) {}

LogMessageTime::LogMessageTime(std::tm t)
{
    std::time_t timestamp = std::mktime(&t);
    init(t, timestamp, 0);
}

LogMessageTime::LogMessageTime(std::time_t timestamp, WallTime now)
{
    std::tm t;
    if (FLAGS_log_utc_time)
        gmtime_r(&timestamp, &t);
    else
        localtime_r(&timestamp, &t);
    init(t, timestamp, now);
}

void
LogMessageTime::init(const std::tm &t, std::time_t timestamp, WallTime now)
{
    time_struct_ = t;
    timestamp_   = timestamp;
    usecs_       = static_cast<std::int32_t>((now - timestamp) * 1000000);

    CalcGmtOffset();
}

void
LogMessageTime::CalcGmtOffset()
{
    std::tm gmt_struct;
    int isDst = 0;
    if (FLAGS_log_utc_time) {
        localtime_r(&timestamp_, &gmt_struct);
        isDst      = gmt_struct.tm_isdst;
        gmt_struct = time_struct_;
    } else {
        isDst = time_struct_.tm_isdst;
        gmtime_r(&timestamp_, &gmt_struct);
    }

    time_t gmt_sec       = mktime(&gmt_struct);
    const long hour_secs = 3600;
    // If the Daylight Saving Time(isDst) is active subtract an hour from the
    // current timestamp.
    gmtoffset_ = static_cast<long int>(timestamp_ - gmt_sec + (isDst ? hour_secs : 0));
}

LogSink::~LogSink() {}

void
LogSink::send(LogSeverity severity,
              const char *full_filename,
              const char *base_filename,
              int line,
              const LogMessageTime &logmsgtime,
              const char *message,
              size_t message_len)
{

    // do nothing
}

void
LogSink::WaitTillSent()
{}

std::string
LogSink::ToString(LogSeverity severity,
                  const char *file,
                  int line,
                  const LogMessageTime &logmsgtime,
                  const char *message,
                  size_t message_len)
{
    return google::LogSink::ToString(severity, file, line, logmsgtime.tm(), message, message_len);
}

class LogSinkWrapper : public google::LogSink {
public:
    LogSinkWrapper(tile::LogSink *dest) : dest_(dest) {}

    ~LogSinkWrapper() override {}

    void send(LogSeverity severity,
              const char *full_filename,
              const char *base_filename,
              int line,
              const google::LogMessageTime &logmsgtime,
              const char *message,
              size_t message_len) override
    {
        dest_->send(severity, full_filename, base_filename, line, logmsgtime.tm(), message, message_len);
    }

    void WaitTillSent() override { dest_->WaitTillSent(); }

private:
    tile::LogSink *dest_;
};

struct LogSinkPair {
    struct LogSinWrapper *wrapper;
    LogSink *sink;
};

static std::map<LogSink *, LogSinkWrapper *> sink_registry;
static std::mutex sink_registry_mutex;

void
AddLogSink(LogSink *dest)
{
    std::lock_guard<std::mutex> lock(sink_registry_mutex);
    if (sink_registry.find(dest) != sink_registry.end()) { return; }

    auto wrapper        = new LogSinkWrapper(dest);
    sink_registry[dest] = wrapper;
    google::AddLogSink(wrapper);
}

void
RemoveLogSink(LogSink *dest)
{
    std::lock_guard<std::mutex> lock(sink_registry_mutex);
    auto iter = sink_registry.find(dest);
    if (iter != sink_registry.end()) {
        google::RemoveLogSink(iter->second);
        sink_registry.erase(iter);
        delete iter->second;
    }
}

void
SetStderrLogging(LogSeverity min_severity)
{
    google::SetStderrLogging(min_severity);
}

void
LogToStderr()
{
    google::LogToStderr();
}

const char *
GetLogSeverityName(LogSeverity severity)
{
    return google::GetLogSeverityName(severity);
}
}// namespace tile
