
#include "ml_profiler.h"

#include <iostream>
#include <algorithm>
// for linux gcc
#include <iomanip>
#include <numeric>


using namespace std::chrono;


void MlPerfMon::turnOn() {
    perfOn = true;
    start_time = std::chrono::high_resolution_clock::now();
}

void MlPerfMon::add_method_call(const std::string &method) {
    std::thread::id this_id = std::this_thread::get_id();
    // only main thread is logged, to prevent mixing from others threads so lock not needed
    if (main_thread_id != this_id)
        return;

    call_stack.push_back(method);

    if (perfOn) {
        auto search = calls_counter.find(method);
        if (search != calls_counter.end()) {
            calls_counter[method] = search->second + 1;
        } else {
            calls_counter.insert({method, 1});
        }
    }
}

void MlPerfMon::end_method_call() {
    std::thread::id this_id = std::this_thread::get_id();
    // only main thread is looged, to prevent mixing from others threads so lock not needed
    if (main_thread_id != this_id)
        return;

    if (!call_stack.empty()){
        call_stack.pop_back();
    }    
}

std::string MlPerfMon::callstack() const {
    std::string cs {"call stack:\n"};

    int cnt = 0;
    for (auto it = call_stack.rbegin(); it != call_stack.rend() && cnt < call_stack_max_methods; ++it, ++cnt)
        cs.append( std::string(*it) + "\n");    // << std::endl;

    if (call_stack.size() > call_stack_max_methods)
        cs.append("next " + std::to_string(call_stack.size() - call_stack_max_methods) + " entries skipped..\n");

    return cs.append("\n");
}

size_t MlPerfMon::get_callstack_position() {
    return call_stack.size();
}

void MlPerfMon::restore_callstack_position(size_t to_position) {
    if (to_position < call_stack.size()) {
        call_stack.resize(to_position);
    }
}

void MlPerfMon::clear_callstack() {
    call_stack.empty();
}

void MlPerfMon::print_results() {
    if (perfOn) {
        std::chrono::time_point<std::chrono::high_resolution_clock> end_time = std::chrono::high_resolution_clock::now();

        using callcount = std::pair<std::string, long>;
        std::vector<callcount> v(begin(calls_counter), end(calls_counter));
        std::sort(std::begin(v), std::end(v), [](const callcount& a, const callcount& b) { return a.second > b.second; });
        long total_calls = 0;
        for(auto const& c : v) { total_calls += c.second; }

        std::cerr << std::endl << std::endl;
        std::cerr << "perf stats" << std::endl;
        std::cerr << "run time       : " << duration_cast<milliseconds>(end_time - start_time).count() << " ms " << std::endl;
        std::cerr << "total calls    : " << total_calls << std::endl;
        std::cerr << "methods called : " << calls_counter.size() << std::endl;
        std::cerr << std::endl;

        int i {0};
        for(auto &p : v) {
            if (p.first.size() >= method_name_print_len)
                p.first.erase(method_name_print_len, std::string::npos);
             else
                p.first.insert(p.first.end(), method_name_print_len - p.first.size(), ' ');

            std::cerr << p.first << " " << p.second << std::endl;

            i++;
            if (i > print_top_methods) break;
        }

        std::cerr << std::endl;
    }
}
