#include "PatternFormatter.h"
#include "../exception.h"

#ifdef _WIN32
    #include <process.h>
    #define _PANDA_GETPID _getpid()
    #define _PANDA_LOCALTIME(epoch_ptr, tm_ptr) (localtime_s(tm_ptr, epoch_ptr) == 0)
#else
    #include <unistd.h>
    #define _PANDA_GETPID getpid()
    #define _PANDA_LOCALTIME(epoch_ptr, tm_ptr) (localtime_r(epoch_ptr, tm_ptr) != nullptr)
#endif

namespace panda { namespace log {

string_view default_format = "%1t %c[%L/%1M]%C %f:%l,%F(): %m";

#ifdef _WIN32
    static const char* colors[] = {nullptr};
#else
    static const char* colors[] = {nullptr, nullptr, nullptr, nullptr, "\e[33m", "\e[31m", "\e[41m", "\e[41m", "\e[41m"};
#endif
    
static const char  clear_color[] = "\e[0m";
static const char* dates[] = {"%Y-%m-%d %H:%M:%S", "%y-%m-%d %H:%M:%S", "%H:%M:%S", "%Y/%m/%d %H:%M:%S"};

PatternFormatter::PatternFormatter (string_view fmt) : _fmt(string(fmt)) {}

static void add_mks (string& dest, const Info::time_point& tp, unsigned prec) {
    long nsec = tp.time_since_epoch().count() % 1000000000L;
    dest += '.';
    if (nsec < 100000000) dest += '0';
    if (nsec < 10000000)  dest += '0';
    if (nsec < 1000000)   dest += '0';
    if (nsec < 100000)    dest += '0';
    if (nsec < 10000)     dest += '0';
    if (nsec < 1000)      dest += '0';
    if (nsec < 100)       dest += '0';
    if (nsec < 10)        dest += '0';
    dest += panda::to_string(nsec);
    dest.length(dest.length() - 9 + prec);
}

static inline void ymdhms (string& dest, time_t epoch, unsigned type) {
    struct tm dt;
    if (!_PANDA_LOCALTIME(&epoch, &dt)) return;

    auto cap = 23;
    string tmp(cap);

    assert(type < 4);
    auto len = strftime(tmp.buf(), cap, dates[type], &dt);
    tmp.length(len);

    dest += tmp;
}

static inline string format_multiline (const string& tmpl, size_t tmpl_pos, std::string& msg) {
    size_t nlines = 0;
    for (size_t pos = 0; pos != std::string::npos; pos = msg.find('\n', pos)) {
        ++nlines;
        ++pos;
    }
    
    string ret(tmpl.length()*nlines + msg.length());
    auto msglen = msg.length();
    size_t pos = 0, epos = 0;
    while (1) {
        epos = msg.find('\n', pos);
        if (epos == std::string::npos) epos = msglen;
        if (pos != 0) ret += '\n';
        auto ins_pos = ret.length() + tmpl_pos;
        ret += tmpl;
        ret.insert(ins_pos, msg.data() + pos, epos - pos);
        if (epos == msglen) break;
        pos = epos + 1;
    }
    
    return ret;
}

string PatternFormatter::format (const string& fmt, std::string& msg, const Info& info) {
    auto len = fmt.length();
    auto s   = fmt.data();
    auto se  = s + len;
    string ret((len > 25 ? len*2 : 50) + msg.length());
    auto multiline = string::npos;

    while (s < se) {
        char c = *s++;
        if (c != '%' || s == se) {
            ret += c;
            continue;
        }

        unsigned x = 0, y = 0;
        bool dot = false;

        SWITCH:
        switch (*s++) {
            case 'F': {
                if (info.func.length()) ret += info.func;
                else                    ret += "<top>";
                break;
            }
            case 'f': {
                if (x) ret += info.file;
                else {
                    auto file = info.file;
                    auto pos = file.rfind('/');
                    if (pos < file.length()) file = file.substr(pos+1);
                  #ifdef _WIN32
                    pos = file.rfind('\\');
                    if (pos < file.length()) file = file.substr(pos+1);
                  #endif
                    ret += file;
                }
                break;
            }
            case 'l': {
                ret += panda::to_string(info.line);
                break;
            }
            case 'm': {
                if (x == 1 && msg.find('\n') != std::string::npos) {
                    multiline = ret.length();
                } else {
                    ret += string_view(msg.data(), msg.length());
                }
                break;
            }
            case 'M': {
                if (info.module->name().length()) ret += info.module->name();
                else {
                    if (x && ret.length() >= x) ret.length(ret.length() - x);
                    s += y;
                }
                break;
            }
            case 'L': {
                switch (info.level) {
                    case Level::VerboseDebug : ret += "DEBUG";     break;
                    case Level::Debug        : ret += "debug";     break;
                    case Level::Info         : ret += "info";      break;
                    case Level::Notice       : ret += "notice";    break;
                    case Level::Warning      : ret += "warning";   break;
                    case Level::Error        : ret += "error";     break;
                    case Level::Critical     : ret += "critical";  break;
                    case Level::Alert        : ret += "alert";     break;
                    case Level::Emergency    : ret += "emergency"; break;
                    default: break;
                }
                break;
            }
            case 't': {
                if (x < 4) ymdhms(ret, std::chrono::system_clock::to_time_t(info.time), x);
                else       ret += panda::to_string(std::chrono::duration_cast<std::chrono::seconds>(info.time.time_since_epoch()).count());
                if (y) add_mks(ret, info.time, y);
                break;
            }
            case 'T': {
                std::stringstream ss;
                ss << std::this_thread::get_id();
                auto str = ss.str();
                ret.append(str.data(), str.length());
                break;
            }
            case 'p': {
                ret += panda::to_string(_PANDA_GETPID);
                break;
            }
            case 'P': {
                ret += info.program_name;
                break;
            }
            case 'c': if (colors[(size_t)info.level]) ret += colors[(size_t)info.level]; break;
            case 'C': if (colors[(size_t)info.level]) ret += clear_color; break;
            case '.': {
                if (s == se) throw exception("bad formatter pattern");
                dot = true;
                goto SWITCH;
            }
            default: {
                char c = *(s-1);
                if (s == se || c < '0' || c > '9') throw exception("bad formatter pattern");
                (dot ? y : x) = c - '0';
                goto SWITCH;
            }
        }
    }
    
    if (multiline != string::npos) return format_multiline(ret, multiline, msg);

    return ret;
}

string PatternFormatter::format (std::string& msg, const Info& info) const {
    return format(_fmt, msg, info);
}

}}