#include "file.h"
#include <iostream>
#include <panda/exception.h>
#include <panda/unievent/Fs.h>
#include <panda/unievent/util.h>

#include <string.h>
#include <stdio.h>
#ifdef __unix__
#include <unistd.h>
#include <fcntl.h>
#endif

namespace panda { namespace log {

#ifdef _WIN32
    static const char* EOL = "\r\n";
#else
    static const char* EOL = "\n";
#endif

using namespace unievent;

static string_view remove_filename (string_view path) {
    if (path.empty()) return path;
    auto s = path.data();
    size_t pos = path.length() - 1;
    auto is_slash = [](char c) { return c == '/' || c == '\\'; };
    while (!is_slash(s[pos]) && pos) --pos;
    if (!is_slash(s[pos])) return {};
    return path.substr(0, pos+1);
}

FileLogger::FileLogger (const Config& cfg) : file(cfg.file), buffered(cfg.buffered), check_freq(cfg.check_freq) {
    if (file.empty()) throw exception("file must be defined");
    reopen();
}

FileLogger::~FileLogger () {
    if (fh) {
        fclose(fh);
        fh = nullptr;
    }
}

bool FileLogger::reopen () {
    if (fh) {
        fclose(fh);
        fh = nullptr;
    }
    inode = 0;

    if (!Fs::exists(file)) {
        auto dir = remove_filename(file);
        if (!Fs::isdir(dir)) {
            auto ret = Fs::mkpath(dir);
            if (!ret) {
                std::cerr << "[FileLogger] logging disabled: could not create path '" << dir << "': " << ret.error().message() << std::endl;
                return false;
            }
        }
    }

    fh = fopen(file.c_str(), "a+b");
    if (!fh) {
        std::cerr << "[FileLogger] logging disabled: could not open log file '" << file << "': " << last_sys_error().message() << std::endl;
        return false;
    }

    auto fd = fileno(fh);
    if (fd < 0) {
        std::cerr << "[FileLogger] logging disabled: get file descriptor '" << file << "': " << strerror(errno) << std::endl;
        return false;
    }

    auto res = Fs::stat(fd);
    if (!res) {
        std::cerr << "[FileLogger] logging disabled: could not stat log file '" << file << "': " << strerror(errno) << std::endl;
        return false;
    }

#ifdef __unix__
    int flags = fcntl(fd, F_GETFD);
    if (flags < 0) {
        std::cerr << "[FileLogger] logging disabled: get file descriptor flags '" << file << "': " << strerror(errno) << std::endl;
        return false;
    }
    flags |= FD_CLOEXEC;
    if (fcntl(fd, F_SETFD, flags) < 0) {
        std::cerr << "[FileLogger] logging disabled: cannot apply FD_CLOEXEC '" << file << "': " << strerror(errno) << std::endl;
        return false;
    }
#endif

    inode = res.value().ino;
    return true;
}

void FileLogger::log (const string& msg, const Info& info) {
    uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(info.time.time_since_epoch()).count();
    if (now >= last_check + check_freq) {
        last_check = now;
        auto res = Fs::stat(file);
        if (!res || res.value().type() != Fs::FileType::FILE || res.value().ino != inode) reopen();
    }

    if (!fh) return; // logging not available

    if (buffered) {
        buffered_log(msg);
    } else {
        unbuffered_log(msg);
    }
}

void FileLogger::flush() {
    if (!buffered || !fh) {
        return;
    }
    auto r = fflush(fh);
    if (r != 0) {
        std::cerr << "[FileLogger] flush message to '" << file << "': " << strerror(errno) << std::endl;
    }
}

void FileLogger::buffered_log(const string& msg) {
    auto items = fwrite(msg.data(), msg.size(), 1, fh);
    if (!items) {
        std::cerr << "[FileLogger] cannot write message to '" << file << "': " << strerror(errno) << ", message: " << msg << std::endl;
        return;
    }

#ifdef _WIN32
    items = fwrite("\r\n", 2, 1, fh);
#else
    items = fwrite("\n", 1, 1, fh);
#endif
    if (!items) {
        std::cerr << "[FileLogger] cannot write message to '" << file << "': " << strerror(errno) << std::endl;
        return;
    }
}

void FileLogger::unbuffered_log(const string& msg) {
    string full = msg + EOL;
    const char* ptr = full.data();
    const char* end = ptr + full.size();
    do {
        auto ret = write(fileno(fh),  ptr, end - ptr);
        if (ret <= 0) {
            std::cerr << "[FileLogger] cannot write message to '" << file << "': " << strerror(errno) << ", message: " << msg << std::endl;
            return;
        }
        ptr += ret;
    } while (ptr != end);
};

}}