#include "log.h"
#include <mutex>
#include <math.h>
#include <time.h>
#include <memory>
#include <thread>
#include <iomanip>
#include <sstream>
#include <string.h>
#include <algorithm>
#include "../exception.h"
#include "../unordered_string_map.h"
#include "PatternFormatter.h"

namespace panda { namespace log {

string_view default_message = "==> MARK <==";
static bool destroy_flag = false;

ILogger::~ILogger () {}

namespace details {
    // panda-log is thread-safe and we use quite a tricky way to avoid mutexes on logging
    // and thus eliminating any perfomance penalties for thread-safety except only for a single access to a thread local variable

    using Modules = unordered_string_multimap<string, Module*>;

    struct ModuleData {
        ILoggerSP    effective_logger; // optimization to avoid traversing the parent-child tree
        ILoggerSP    logger;           // explicitly installed logger for this module
        IFormatterSP effective_formatter;
        IFormatterSP formatter;
    };

    struct Data {
        size_t rev = 0;
        std::ostringstream os;
        std::ostringstream os_tmp;
        std::map<uintptr_t, ModuleData> map;
        string program_name;

        Data& operator= (const Data& oth) {
            // ostream is never changed
            rev = oth.rev;
            map = oth.map;
            program_name = oth.program_name;
            return *this;
        }

        ModuleData& get_module_data (const Module* module) {
            return map.at(reinterpret_cast<uintptr_t>(module));
        }
    };



    struct Instance {
        bool contains(const Module* module) {
            auto iter = std::find_if(modules.begin(), modules.end(), [module](const auto& m) {
                return m.second == module;
            });
            return iter != modules.end();
        }

        std::recursive_mutex mtx;

        Modules modules;                        // modules by name index
        Data src_data;                          // main container for modules data
        std::thread::id mt_id = std::this_thread::get_id();
        Data mt_data;                           // cached data for main thread, can't use TLS because it's destroyed much earlier

        ~Instance() {
            destroy_flag = true;
        }
    };

    Instance& inst() {
        static Instance d;
        return d;
    }

#define SYNC_LOCK std::lock_guard<decltype(inst().mtx)> guard(inst().mtx);

    inline Data& get_data () {
        if (std::this_thread::get_id() == inst().mt_id) {
            return inst().mt_data;
        }

        thread_local Data* ct_data = nullptr; // cached data for child threads
        if (!ct_data) { // TLS via pointers works 3x faster in GCC
            thread_local Data _ct_data;
            ct_data = &_ct_data;
        }

        return *ct_data;
    }

    inline Data& get_synced_data () {
        auto& data = get_data();

        if (data.rev != inst().src_data.rev) { // data changed by some thread
            SYNC_LOCK;
            data = inst().src_data;
        }

        return data;
    }

    std::ostream& get_os     () { return get_data().os; }
    std::ostream& get_os_tmp () { return get_data().os_tmp; }

    static string_view default_program_name = "<unknown>";

    bool do_log (std::ostream& _stream, Level level, const Module* module, const CodePoint& cp) {
        std::ostringstream& stream = static_cast<std::ostringstream&>(_stream);
        stream.flush();
        std::string s(stream.str());
        stream.str({});

        auto& lib_data = get_synced_data();
        auto& module_data = lib_data.get_module_data(module);

        if (module_data.effective_logger) {
            string_view program_name = lib_data.program_name ? lib_data.program_name : default_program_name;
            Info info(level, module, cp.file, cp.line, cp.func, program_name);
            info.time = std::chrono::system_clock::now();
            module_data.effective_logger->log_format(s, info, *(module_data.effective_formatter));

            if (module->passthrough()) {
                while (1) {
                    module = module->parent();
                    if (!module) break;
                    auto& module_data = lib_data.get_module_data(module);
                    if (!module_data.effective_logger) break;
                    module_data.effective_logger->log_format(s, info, *(module_data.effective_formatter));
                    if (!module->passthrough()) break;
                }
            }
        }
        return true;
    }

    static std::vector<Module*>& wait_list() {
        static std::vector<Module*> inst;
        return inst;
    }

    bool try_init_waiting() {
        auto& list = wait_list();
        auto iter = std::find_if(list.begin(), list.end(), [](const auto& m) {
            return inst().contains(m->parent());
        });
        if (iter == list.end()) {
            return false;
        }

        (*iter)->init();
        wait_list().erase(iter);
        return true;
    }

    // https://stackoverflow.com/questions/9097201/how-to-get-current-process-name-in-linux
    // https://stackoverflow.com/questions/2471553/access-command-line-arguments-without-using-char-argv-in-main/24718544#24718544

    static void spy_$0(int argc, char** argv, char** /* envp */) {
        if (argc > 0) {
            if (argv[0]) {
                default_program_name = argv[0];
            }
        }
    }

    #ifdef __MACH__
    __attribute__((section("_DATA,.init_array"))) void (* p_my_cool_main)(int,char*[],char*[]) = spy_$0;
    #else
        #ifndef _MSC_VER
        __attribute__((section(".init_array"))) void (* p_my_cool_main)(int,char*[],char*[]) = spy_$0;
        #endif
    #endif
}
using namespace details;

void ILogger::log_format (std::string& s, const Info& info, const IFormatter& fmt) {
    log(fmt.format(s, info), info);
}

void ILogger::log (const string&, const Info&) {
    assert(0 && "either ILogger::log or ILogger::log_format must be implemented");
}


ILoggerSP make_logger (const logger_fn& f) {
    struct Logger : ILogger {
        logger_fn f;
        Logger (const logger_fn& f) : f(f) {}
        void log (const string& s, const Info& i) override { f(s, i); }
    };
    return new Logger(f);
}

ILoggerSP make_logger (const logger_format_fn& f) {
    struct Logger : ILogger {
        logger_format_fn f;
        Logger (const logger_format_fn& f) : f(f) {}
        void log_format (std::string& s, const Info& i, const IFormatter& fmt) override { f(s, i, fmt); }
    };
    return new Logger(f);
}

IFormatterSP make_formatter (const format_fn& f) {
    struct Formatter : IFormatter {
        format_fn f;
        Formatter (const format_fn& f) : f(f) {}
        string format (std::string& s, const Info& i) const override { return f(s, i); }
    };
    return new Formatter(f);
}

IFormatterSP make_formatter (string_view pattern) {
    return new PatternFormatter(pattern);
}

const string& Module::name        () const { return _name; }
const Module* Module::parent      () const { return _parent; }
Level         Module::level       () const { return _level; }
bool          Module::passthrough () const { return _passthrough; }

const Module::Modules& Module::children () const {
    return _children;
}

void Module::set_level (Level level) {
    SYNC_LOCK;
    this->_level = level;
    for (auto& m : _children) m->set_level(level);
}

void Module::set_logger (ILoggerFromAny _l, bool passthrough) {
    SYNC_LOCK;
    auto l = std::move(_l.value);
    ++inst().src_data.rev;
    auto& data = inst().src_data.get_module_data(this);
    data.logger = l;
    if (!l && _parent) l = inst().src_data.get_module_data(_parent).effective_logger;
    data.effective_logger = l;
    for (auto& m : _children) m->_set_effective_logger(std::move(l));
    get_synced_data(); // reset any possible loggers for current thread
    _passthrough = passthrough;
}

void Module::_set_effective_logger (const ILoggerSP& l) {
    auto& data = inst().src_data.get_module_data(this);
    if (data.logger) return; // all children already have it as effective logger
    data.effective_logger = l;
    for (auto& m : _children) m->_set_effective_logger(l);
}

void Module::set_formatter (IFormatterFromAny _f) {
    SYNC_LOCK;
    auto f = std::move(_f.value);
    ++inst().src_data.rev;
    auto& data = inst().src_data.get_module_data(this);
    data.formatter = f;
    if (!f) {
        if (_parent) f = inst().src_data.get_module_data(_parent).effective_formatter;
        else         f = data.formatter = IFormatterSP(new PatternFormatter(default_format));
    }
    data.effective_formatter = f;
    for (auto& m : _children) m->_set_effective_formatter(std::move(f));
    get_synced_data(); // reset any possible loggers for current thread
}

void Module::_set_effective_formatter (const IFormatterSP& f) {
    auto& data = inst().src_data.get_module_data(this);
    if (data.formatter) return; // all children already have it as effective formatter
    data.effective_formatter = f;
    for (auto& m : _children) m->_set_effective_formatter(f);
}

ILoggerSP Module::get_logger () {
    return get_synced_data().get_module_data(this).logger;
}

IFormatterSP Module::get_formatter () {
    return get_synced_data().get_module_data(this).formatter;
}

Module::Module (const string& name, Level level) : Module(name, panda_log_module, level) {}
Module::Module (const string& name, Module& parent, Level level) : Module(name, &parent, level) {}
Module::Module (const string& name, std::nullptr_t, Level level) : Module(name, (Module*)nullptr, level) {}

Module::Module (const string& name, Module* parent, Level level)
    : _parent(parent)
    , _level(level)
    , _name(name)
{
    if (parent && !inst().contains(parent)) {
        wait_list().push_back(this);
    } else {
        init();
        while(try_init_waiting()) {}
    }
}

void Module::init() {
    SYNC_LOCK;
    ++inst().src_data.rev;
    auto &data = inst().src_data.map[reinterpret_cast<uintptr_t>(this)]; // creates entry

    if (inst().contains(this)) {
        return;
    }

    if (_parent) {
        _parent->init();
        _parent->_children.push_back(this);
        if (!_parent->_name.empty()) {
            this->_name = _parent->_name + "::" + _name;
        }

        // inherit effective logger and formatter from parent module
        auto& parent_data = inst().src_data.get_module_data(_parent);
        data.effective_logger    = parent_data.effective_logger;
        data.effective_formatter = parent_data.effective_formatter;
    } else {
        // set default formatter for root module
        data.effective_formatter = data.formatter = IFormatterSP(new PatternFormatter(default_format));
    }

    inst().modules.emplace(this->_name, this);
}

Module::~Module () {
    SYNC_LOCK;
    if (destroy_flag) {
        return;
    }
    for (auto& m : _children) {
        m->_parent = nullptr;
        auto& data = inst().src_data.get_module_data(m);
        // we must set explicitly logger and formatter as these modules are now root modules
        data.logger = data.effective_logger;
        data.formatter = data.effective_formatter;
    }

    if (_parent) {
        auto it = std::find(_parent->_children.begin(), _parent->_children.end(), this);
        assert(it != _parent->_children.end());
        _parent->_children.erase(it);
    }

    auto range = inst().modules.equal_range(_name);
    while (range.first != range.second) {
        if (range.first->second != this) {
            ++range.first;
            continue;
        }
        inst().modules.erase(range.first);
        break;
    }

    inst().src_data.map.erase(reinterpret_cast<uintptr_t>(this));
}

void set_level (Level val, string_view modname) {
    SYNC_LOCK;
    if (!modname.length()) return ::panda_log_module.set_level(val);

    auto range = inst().modules.equal_range(modname);
    if (range.first == range.second) throw exception(string("unknown module: ") + modname);

    while (range.first != range.second) {
        range.first->second->set_level(val);
        ++range.first;
    }
}

void set_logger (ILoggerFromAny l) {
    panda_log_module.set_logger(std::move(l));
}

void set_formatter (IFormatterFromAny f) {
    panda_log_module.set_formatter(std::move(f));
}

ILoggerSP get_logger () {
    return panda_log_module.get_logger();
}

IFormatterSP get_formatter () {
    return panda_log_module.get_formatter();
}

Module* get_module (string_view name) {
    if (!name.length()) return &::panda_log_module;
    SYNC_LOCK;
    auto range = inst().modules.equal_range(name);
    if (range.first == range.second) return nullptr;
    return range.first->second;
}

std::vector<Module*> get_modules () {
    SYNC_LOCK;
    std::vector<Module*> ret;
    ret.reserve(inst().modules.size());
    for (auto& row : inst().modules) ret.push_back(row.second);
    return ret;
}

void set_program_name(const string& value) noexcept {
    SYNC_LOCK;
    ++inst().src_data.rev;
    inst().src_data.program_name = value;
}

std::ostream& operator<< (std::ostream& stream, const escaped& str) {
   for (auto c : str.src) {
       if (c > 31) {
           stream << c;
       } else {
           stream << "\\" << std::setfill('0') << std::setw(2) << uint32_t(uint8_t(c));
       }
   }
   return stream;
}

void prettify_json::set_from_os (std::ostream& os) {
    auto& ss = static_cast<std::ostringstream&>(os);
    ss.flush();
    str = ss.str();
    ss.str({});
    src = string_view(str.data(), str.size());
}

static inline char endof (char c) {
    if (c == '{') {
        return '}';
    } else {
        return ']';
    }
}

std::ostream& operator<< (std::ostream& stream, const prettify_json& info) {
    auto src = info.src;
    auto unfold_limit = info.unfold_limit;
    size_t depth = 0;
    string result;
    bool newline = false;
    result.reserve(src.size() * 1.2);
    auto ctrl = [&](char c) {
        result += c;
        newline = true;
    };
    auto tab = [&] {
        if (newline) {
            result += '\n';
            for (size_t i = 0; i < depth; ++i) {
                result += "    ";
            }
        }
    };

    for (size_t i = 0; i < src.size(); ++i) {
        char c = src[i];
        switch (c) {
            case ' ':
            case '\t': {
                if (newline) break; // ignore spaces in the beggining
                result += c;
                newline = false;
            } break;
            case '{':
            case '[':{
                tab();
                size_t another_begin = src.find(c, i+1);
                size_t end = src.find(endof(c), i+1);
                if (another_begin > end && end - i < unfold_limit) {
                    result += src.substr(i, end+1-i);
                    i = end;
                    newline = true;
                    break;
                }
                ctrl(c);
                ++depth;
            } break;
            case '}':
            case ']':{
                if (depth) --depth;
                newline = true;
                tab();
                ctrl(c);
            } break;
            case ',': {
                ctrl(c);
            } break;
            default:
                tab();
                result += c;
                newline = false;
        }
    }

    stream << result;
    return stream;
}

}}

panda::log::Module panda_log_module("", nullptr);

// compose small units
#include "PatternFormatter.icc"
#include "console.icc"
#include "multi.icc"
