// Copyright 2010-2013 RethinkDB, all rights reserved.
#include <stdarg.h>

#include "errors.hpp"
#include <boost/bind.hpp>
#include <boost/tokenizer.hpp>

#include "arch/runtime/coroutines.hpp"
#include "containers/scoped.hpp"
#include "containers/scoped_regex.hpp"
#include "logger.hpp"
#include "perfmon/core.hpp"

/* Constructor and destructor register and deregister the perfmon. */
perfmon_t::perfmon_t() {
}

perfmon_t::~perfmon_t() {
}

struct stats_collection_context_t : public home_thread_mixin_t {
private:
    rwi_lock_t::read_acq_t lock_sentry;
public:
    DEBUG_ONLY(size_t size;)
    void **contexts;

    stats_collection_context_t(rwi_lock_t *constituents_lock,
                               const intrusive_list_t<perfmon_membership_t> &constituents) :
        lock_sentry(constituents_lock),
        DEBUG_ONLY(size(constituents.size()), )
        contexts(new void *[constituents.size()])
    { }

    ~stats_collection_context_t() {
        delete[] contexts;
    }
};

perfmon_collection_t::perfmon_collection_t() { }
perfmon_collection_t::~perfmon_collection_t() { }

void *perfmon_collection_t::begin_stats() {
    stats_collection_context_t *ctx;
    {
        on_thread_t thread_switcher(home_thread());
        ctx = new stats_collection_context_t(&constituents_access, constituents);
    }

    size_t i = 0;
    for (perfmon_membership_t *p = constituents.head(); p != NULL; p = constituents.next(p), ++i) {
        rassert(i < ctx->size);
        ctx->contexts[i] = p->get()->begin_stats();
    }
    return ctx;
}

void perfmon_collection_t::visit_stats(void *_context) {
    stats_collection_context_t *ctx = reinterpret_cast<stats_collection_context_t*>(_context);
    size_t i = 0;
    for (perfmon_membership_t *p = constituents.head(); p != NULL; p = constituents.next(p), ++i) {
        rassert(i < ctx->size);
        p->get()->visit_stats(ctx->contexts[i]);
    }
}

scoped_ptr_t<perfmon_result_t> perfmon_collection_t::end_stats(void *_context) {
    stats_collection_context_t *ctx = reinterpret_cast<stats_collection_context_t*>(_context);

    scoped_ptr_t<perfmon_result_t> map = perfmon_result_t::alloc_map_result();

    size_t i = 0;
    for (perfmon_membership_t *p = constituents.head(); p != NULL; p = constituents.next(p), ++i) {
        rassert(i < ctx->size);
        scoped_ptr_t<perfmon_result_t> stat = p->get()->end_stats(ctx->contexts[i]);
        if (p->splice()) {
            stat->splice_into(map.get());
        } else {
            map->insert(p->name, stat.release());
        }
    }

    {
        on_thread_t thread_switcher(home_thread());
        delete ctx; // cleans up, unlocks
    }

    return map;
}

void perfmon_collection_t::add(perfmon_membership_t *perfmon) {
    scoped_ptr_t<on_thread_t> thread_switcher;
    if (coroutines_have_been_initialized()) {
        thread_switcher.init(new on_thread_t(home_thread()));
    }

    rwi_lock_t::write_acq_t write_acq(&constituents_access);
    constituents.push_back(perfmon);
}

void perfmon_collection_t::remove(perfmon_membership_t *perfmon) {
    scoped_ptr_t<on_thread_t> thread_switcher;
    if (coroutines_have_been_initialized()) {
        thread_switcher.init(new on_thread_t(home_thread()));
    }

    rwi_lock_t::write_acq_t write_acq(&constituents_access);
    constituents.remove(perfmon);
}

perfmon_membership_t::perfmon_membership_t(perfmon_collection_t *_parent, perfmon_t *_perfmon, const char *_name, bool _own_the_perfmon)
    : name(_name != NULL ? _name : ""), parent(_parent), perfmon(_perfmon), own_the_perfmon(_own_the_perfmon)
{
    parent->add(this);
}

perfmon_membership_t::perfmon_membership_t(perfmon_collection_t *_parent, perfmon_t *_perfmon, const std::string &_name, bool _own_the_perfmon)
    : name(_name), parent(_parent), perfmon(_perfmon), own_the_perfmon(_own_the_perfmon)
{
    parent->add(this);
}

perfmon_membership_t::~perfmon_membership_t() {
    parent->remove(this);
    if (own_the_perfmon)
        delete perfmon;
}

perfmon_t *perfmon_membership_t::get() {
    return perfmon;
}

bool perfmon_membership_t::splice() {
    return name.length() == 0;
}

perfmon_multi_membership_t::perfmon_multi_membership_t(perfmon_collection_t *collection, perfmon_t *perfmon, const char *name, ...) {
    // Create membership for the first provided perfmon first
    memberships.push_back(new perfmon_membership_t(collection, perfmon, name));

    va_list args;
    va_start(args, name);

    // Now go through varargs list until we read NULL
    while ((perfmon = va_arg(args, perfmon_t *)) != NULL) {
        name = va_arg(args, const char *);
        memberships.push_back(new perfmon_membership_t(collection, perfmon, name));
    }

    va_end(args);
}

perfmon_multi_membership_t::~perfmon_multi_membership_t() {
    for (std::vector<perfmon_membership_t*>::const_iterator it = memberships.begin(); it != memberships.end(); ++it) {
        delete *it;
    }
}

perfmon_result_t::perfmon_result_t() {
    type = type_value;
}

perfmon_result_t::perfmon_result_t(const perfmon_result_t &copyee)
    : type(copyee.type), value_(copyee.value_), map_() {
    for (perfmon_result_t::internal_map_t::const_iterator it = copyee.map_.begin(); it != copyee.map_.end(); ++it) {
        perfmon_result_t *subcopy = new perfmon_result_t(*it->second);
        map_.insert(std::pair<std::string, perfmon_result_t *>(it->first, subcopy));
    }
}

perfmon_result_t::perfmon_result_t(const std::string &s) {
    type = type_value;
    value_ = s;
}

perfmon_result_t::perfmon_result_t(const std::map<std::string, perfmon_result_t *> &m) {
    type = type_map;
    map_ = m;
}

perfmon_result_t::~perfmon_result_t() {
    if (type == type_map) {
        clear_map();
    }
    rassert(map_.empty());
}

void perfmon_result_t::clear_map() {
    for (perfmon_result_t::internal_map_t::iterator it = map_.begin(); it != map_.end(); ++it) {
        delete it->second;
    }
    map_.clear();
}

void perfmon_result_t::erase(perfmon_result_t::iterator it) {
    delete it->second;
    map_.erase(it);
}

scoped_ptr_t<perfmon_result_t> perfmon_result_t::alloc_map_result() {
    return scoped_ptr_t<perfmon_result_t>(new perfmon_result_t(internal_map_t()));
}

std::string *perfmon_result_t::get_string() {
    rassert(type == type_value);
    return &value_;
}

const std::string *perfmon_result_t::get_string() const {
    rassert(type == type_value);
    return &value_;
}

perfmon_result_t::internal_map_t *perfmon_result_t::get_map() {
    rassert(type == type_map);
    return &map_;
}

const perfmon_result_t::internal_map_t *perfmon_result_t::get_map() const {
    rassert(type == type_map);
    return &map_;
}

size_t perfmon_result_t::get_map_size() const {
    rassert(type == type_map);
    return map_.size();
}

bool perfmon_result_t::is_string() const {
    return type == type_value;
}

bool perfmon_result_t::is_map() const {
    return type == type_map;
}

perfmon_result_t::perfmon_result_type_t perfmon_result_t::get_type() const {
    return type;
}

void perfmon_result_t::reset_type(perfmon_result_type_t new_type) {
    value_.clear();
    clear_map();
    type = new_type;
}

std::pair<perfmon_result_t::iterator, bool> perfmon_result_t::insert(const std::string &name, perfmon_result_t *val) {
    std::string s(name);
    perfmon_result_t::internal_map_t *map = get_map();
    rassert(map->count(name) == 0, "Duplicate perfmons for: %s\n", name.c_str());
    return map->insert(std::pair<std::string, perfmon_result_t *>(s, val));
}

perfmon_result_t::iterator perfmon_result_t::begin() {
    return map_.begin();
}

perfmon_result_t::iterator perfmon_result_t::end() {
    return map_.end();
}

perfmon_result_t::const_iterator perfmon_result_t::begin() const {
    return map_.cbegin();
}

perfmon_result_t::const_iterator perfmon_result_t::end() const {
    return map_.cend();
}

perfmon_result_t::const_iterator perfmon_result_t::cbegin() const {
    return map_.cbegin();
}

perfmon_result_t::const_iterator perfmon_result_t::cend() const {
    return map_.cend();
}

void perfmon_result_t::splice_into(perfmon_result_t *map) {
    rassert(type == type_map);

    // Transfer all elements from the internal map to the passed map.
    // Unfortunately we can't use here std::map::insert(InputIterator first, InputIterator last),
    // because that way we can overwrite an entry in the target map and thus leak a
    // perfmon_result_t value.
    for (const_iterator it = begin(); it != end(); ++it) {
        map->insert(it->first, it->second);
    }
    map_.clear();
}

/* Construct a filter from a set of paths.  Paths are of the form foo/bar/baz,
   where each of those can be a regular expression.  They act a lot like XPath
   expressions, but for perfmon_t objects.  */
perfmon_filter_t::perfmon_filter_t(const std::set<std::string> &paths) {
    typedef boost::escaped_list_separator<char> separator_t;
    typedef boost::tokenizer<separator_t> tokenizer_t;
    separator_t slashes("\\", "/", "");

    for (std::set<std::string>::const_iterator
             str = paths.begin(); str != paths.end(); ++str) {
        regexps.push_back(std::vector<scoped_regex_t *>());
        std::vector<scoped_regex_t *> *path = &regexps.back();
        try {
            tokenizer_t t(*str, slashes);
            for (tokenizer_t::const_iterator it = t.begin(); it != t.end(); ++it) {
                path->push_back(new scoped_regex_t());
                if (!path->back()->compile("^"+(*it)+"$")) {
                    logWRN("Error: regex %s failed to compile (%s), treating as empty.",
                           sanitize_for_logger(*it).c_str(),
                           sanitize_for_logger(path->back()->get_error()).c_str());
                    if (!path->back()->compile("^$")) {
                        crash("Regex '^$' failed to compile (%s).\n",
                              sanitize_for_logger(path->back()->get_error()).c_str());
                    }
                }
            }
        } catch (const boost::escaped_list_error &e) {
            logWRN("Error: Could not parse %s (%s), skipping.",
                   sanitize_for_logger(*str).c_str(), e.what());
            continue; //Skip this path
        }
    }
}

perfmon_filter_t::~perfmon_filter_t() {
    for (std::vector<std::vector<scoped_regex_t *> >::const_iterator
             it = regexps.begin(); it != regexps.end(); ++it) {
        for (std::vector<scoped_regex_t *>::const_iterator
                 regexp = it->begin(); regexp != it->end(); ++regexp) {
            delete *regexp;
        }
    }
}

void perfmon_filter_t::filter(const scoped_ptr_t<perfmon_result_t> *p) const {
    guarantee(p->has(), "perfmon_filter_t::filter needs a perfmon_result_t");
    subfilter(const_cast<scoped_ptr_t<perfmon_result_t> *>(p),
              0, std::vector<bool>(regexps.size(), true));
    guarantee(p->has(), "subfilter is not supposed to delete the top-most node.");
}

/* Filter a [perfmon_result_t].  [depth] is how deep we are in the paths that
   the [perfmon_filter_t] was constructed from, and [active] is the set of paths
   that are still active (i.e. that haven't failed a match yet).  This should
   only be called by [filter]. */
void perfmon_filter_t::subfilter(
    scoped_ptr_t<perfmon_result_t> *p_ptr, const size_t depth,
    const std::vector<bool> active) const {

    perfmon_result_t *const p = p_ptr->get();

    bool keep_this_perfmon = true;
    if (p->is_string()) {
        std::string *str = p->get_string();
        for (size_t i = 0; i < regexps.size(); ++i) {
            if (!active[i]) {
                continue;
            }
            if (depth >= regexps[i].size()) {
                return;
            }
            if (depth == regexps[i].size() && regexps[i][depth]->matches(*str)) {
                return;
            }
        }
        keep_this_perfmon = false;
    } else if (p->is_map()) {
        perfmon_result_t::iterator it = p->begin();
        while (it != p->end()) {
            std::vector<bool> subactive = active;
            bool some_subpath = false;
            for (size_t i = 0; i < regexps.size(); ++i) {
                if (!active[i]) {
                    continue;
                }
                if (depth >= regexps[i].size()) {
                    return;
                }
                subactive[i] = regexps[i][depth]->matches(it->first);
                some_subpath |= subactive[i];
            }
            if (some_subpath) {
                scoped_ptr_t<perfmon_result_t> tmp(it->second);
                subfilter(&tmp, depth + 1, subactive);
                it->second = tmp.release();
            }
            perfmon_result_t::iterator prev_it = it;
            ++it;
            if (!some_subpath || prev_it->second == NULL) {
                p->erase(prev_it);
            }
        }

        if (p->get_map_size() == 0) {
            keep_this_perfmon = false;
        }
    }

    if (!keep_this_perfmon && depth > 0) {  // Never delete the topmost node.
        p_ptr->reset();
    }
}

perfmon_collection_t &get_global_perfmon_collection() {
    // Getter function so that we can be sure that `collection` is initialized
    // before it is needed, as advised by the C++ FAQ. Otherwise, a `perfmon_t`
    // might be initialized before `collection` was initialized.

    // FIXME: probably use "new" to create the perfmon_collection_t. For more info check out C++ FAQ Lite answer 10.16.
    static perfmon_collection_t collection;
    return collection;
}

