#include <library/cpp/getopt/last_getopt.h>
#include <util/stream/output.h>
#include <util/stream/str.h>
#include <util/string/printf.h>
#include <util/system/thread.h>

#include "args.h"
#include "log.h"

namespace NWebmaster {

ELogPriority TLogger::Level;

struct TThreadedLogBackendEx : public TThreadedLogBackend {
    TThreadedLogBackendEx(TAutoPtr<TLogBackend> slave)
        : TThreadedLogBackend(slave.Get())
        , Slave(slave)
    {
    }

    ~TThreadedLogBackendEx() override {
        Y_UNUSED(Slave.Release());
    }

public:
    TAutoPtr<TLogBackend> Slave;
};

TLog *TLogger::Instance(ELogPriority level) {
    static TAutoPtr<TLog> Log;

    if (!Log) {
        Level = level;

        TString &logPath = TArgs::Instance().LogPath;
        if (logPath.empty()) {
            Log.Reset(new TLog(MakeHolder<TStreamLogBackend>(&Cerr)));
        } else {
            Log.Reset(new TLog(MakeHolder<TThreadedLogBackendEx>(new TFileLogBackend(logPath))));
        }

#if !defined(_win_)
        signal(SIGHUP, OnLogRotate);
#endif
    }

    return Log.Get();
}

TString SimplifyFunctionName(const char *functionName) {
    TString res(functionName);
    size_t parInd = res.find('(');
    if (parInd != TString::npos) {
        res = res.substr(0, parInd);
    }
    parInd = res.find('<');
    if (parInd != TString::npos) {
        size_t parRightInd = res.find('>');
        if (parRightInd != TString::npos) {
            res = res.replace(parInd, parRightInd - parInd + 1, "");
        }
    }
    size_t wsInd = res.find_last_of(' ');
    if (wsInd != TString::npos) {
        res = res.substr(wsInd + 1);
    }
    return res;
}

TString MyTimeToString(const struct tm &theTm) {
    char buf[DATE_8601_LEN - 1]; // don't need 'Z' in the end
    sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d", theTm.tm_year+1900, theTm.tm_mon+1, theTm.tm_mday, theTm.tm_hour, theTm.tm_min, theTm.tm_sec);
    return TString(buf, DATE_8601_LEN - 2);
}

void TLogger::Write(const char *functionName, ELogPriority prio, const char *msg, ...) {
    TLog *Log = Instance();

    if (!Log) {
        return;
    }

    if (prio > Level) {
        return;
    }

    va_list args;
    va_start(args, msg);
    TString stream;
    struct tm localTime;
    Now().LocalTime(&localTime);
    stream.append("[");
    stream.append(MyTimeToString(localTime));
    stream.append(" ");
    stream.append(Sprintf("%04X", static_cast<ui16>(TThread::CurrentThreadId())));
    stream.append(" ");
    stream.append(LogLevelToString(prio));
    if (Level == TLOG_DEBUG) {
        stream.resize(DATE_8601_LEN + 13, ' ');
        stream.append(SimplifyFunctionName(functionName) + "] ");
    } else {
        stream.append("]");
        stream.resize(DATE_8601_LEN + 14, ' ');
    }
    TString formatted;
    vsprintf(formatted, msg, args);
    stream.append(formatted);
    va_end(args);
    stream.append("\n");
    Log->Write(prio, stream.data(), stream.size());
}

void TLogger::Rotate() {
    Instance()->ReopenLog();
}

void TLogger::OnLogRotate(int) {
    LOG_INFO("Log rotated");
    Rotate();
}

ELogPriority TLogger::StringToLogLevel(const TString &_level) {
    TString level(_level);

    level.to_lower();
    if (level == "emerg") {
        return TLOG_EMERG;
    } else if (level == "alert") {
        return TLOG_ALERT;
    } else if (level == "crit") {
        return TLOG_CRIT;
    } else if (level == "error") {
        return TLOG_ERR;
    } else if (level == "warning") {
        return TLOG_WARNING;
    } else if (level == "notice") {
        return TLOG_NOTICE;
    } else if (level == "info") {
        return TLOG_INFO;
    } else if (level == "debug") {
        return TLOG_DEBUG;
    } else {
        ythrow yexception() << "Unknown log level";
    }
}

TString TLogger::LogLevelToString(ELogPriority prio) {
    switch (prio) {
    case TLOG_EMERG:
        return "EMERG";
    case TLOG_ALERT:
        return "ALERT";
    case TLOG_CRIT:
        return "CRIT";
    case TLOG_ERR:
        return "ERROR";
    case TLOG_WARNING:
        return "WARNING";
    case TLOG_NOTICE:
        return "NOTICE";
    case TLOG_INFO:
        return "INFO";
    case TLOG_DEBUG:
        return "DEBUG";
    default:
        return "UNKNOWN";
    }
}

} //namespace NWebmaster
