//  Copyright (c) 2011-2012 Maciej Brodowicz
//
//  SPDX-License-Identifier: BSL-1.0
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <hpx/config.hpp>

#if defined(HPX_HAVE_PAPI)

#include <hpx/components/performance_counters/papi/util/papi.hpp>
#include <hpx/modules/command_line_handling.hpp>
#include <hpx/modules/errors.hpp>
#include <hpx/modules/format.hpp>
#include <hpx/modules/iterator_support.hpp>
#include <hpx/modules/runtime_local.hpp>

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
#include <winsock2.h>
#endif
#include <asio/ip/host_name.hpp>

#include <cstdint>
#include <iostream>
#include <map>
#include <string>
#include <vector>

#define NS_STR "hpx::performance_counters::papi::util::"

///////////////////////////////////////////////////////////////////////////////
namespace hpx {
    namespace performance_counters {
        namespace papi {
            namespace util {
    ///////////////////////////////////////////////////////////////////////////
    // PAPI domain handling

    // PAPI domain description strings
    std::map<std::string, int> const papi_domain_map = {{"user", PAPI_DOM_USER},
        {"kernel", PAPI_DOM_KERNEL}, {"other", PAPI_DOM_OTHER},
        {"super", PAPI_DOM_SUPERVISOR}, {"all", PAPI_DOM_ALL},
        {"min", PAPI_DOM_MIN}, {"max", PAPI_DOM_MAX}};

    // map domain string into PAPI handle
    int map_domain(std::string const& s)
    {
        std::map<std::string, int>::const_iterator it = papi_domain_map.find(s);
        if (it == papi_domain_map.end())
            HPX_THROW_EXCEPTION(hpx::error::commandline_option_error,
                NS_STR "check_options()",
                "invalid argument " + s + " to --hpx:papi-domain");
        return it->second;
    }

    ///////////////////////////////////////////////////////////////////////////
    // option handling

    // return options_description object for PAPI counters
    options_description get_options_description()
    {
        using hpx::program_options::value;

        // description of PAPI counter specific options
        options_description papi_opts("PAPI counter options");
        papi_opts.add_options()("hpx:papi-domain",
            value<std::string>()->default_value("user"),
            "sets monitoring scope to one of:\n"
            "  user   - user context only,\n"
            "  kernel - kernel/OS context only,\n"
            "  super  - supervisor or hypervisor context,\n"
            "  other  - exception and transient mode,\n"
            "  all    - all above contexts,\n"
            "  min    - smallest available context,\n"
            "  max    - largest available context.")("hpx:papi-multiplex",
            value<long>()->implicit_value(0),
            "enable low level counter multiplexing.")("hpx:papi-event-info",
            value<std::string>()->implicit_value("preset"),
            "display detailed information about events available on local host;"
            " the optional argument is one of:\n"
            "  preset - show available predefined events,\n"
            "  native - show available native events,\n"
            "  all    - show all available events.");
        return papi_opts;
    }

    // retrieve command line options
    variables_map get_options()
    {
        using hpx::util::retrieve_commandline_arguments;

        variables_map vm;
        if (!retrieve_commandline_arguments(get_options_description(), vm))
        {
            HPX_THROW_EXCEPTION(hpx::error::commandline_option_error,
                NS_STR "get_options()",
                "failed to handle command line options");
        }
        return vm;
    }

    // scan through performance counters to see if PAPI is needed
    bool need_papi_component(variables_map const& vm)
    {
        if (vm.count("hpx:print-counter"))
        {
            std::vector<std::string> names =
                vm["hpx:print-counter"].as<std::vector<std::string>>();
            std::vector<std::string>::iterator it;
            for (it = names.begin(); it != names.end(); ++it)
                if (it->substr(0, 5) == "/papi")
                    return true;
        }
        if (vm.count("hpx:print-counter-reset"))
        {
            std::vector<std::string> names =
                vm["hpx:print-counter-reset"].as<std::vector<std::string>>();
            std::vector<std::string>::iterator it;
            for (it = names.begin(); it != names.end(); ++it)
                if (it->substr(0, 5) == "/papi")
                    return true;
        }
        return false;
    }

    // checks options for sanity; returns true if papi component is needed
    bool check_options(variables_map const& vm)
    {
        bool needed = need_papi_component(vm);
        if (vm.count("hpx:papi-event-info"))
        {
            std::string v = vm["hpx:papi-event-info"].as<std::string>();
            if (v != "preset" && v != "native" && v != "all")
                HPX_THROW_EXCEPTION(hpx::error::commandline_option_error,
                    NS_STR "check_options()",
                    "unsupported mode " + v + " in --hpx:papi-event-info");
        }
        if (vm.count("hpx:papi-domain"))
        {
            std::string v = vm["hpx:papi-domain"].as<std::string>();
            int dom = map_domain(v);    // throws if not found
            papi_call(PAPI_set_domain(dom),
                "could not switch to \"" + v + "\" domain monitoring",
                NS_STR "check_options()");
            needed = true;
        }
        // FIXME: implement multiplexing properly and uncomment below when done
        if (vm.count("hpx:papi-multiplex"))
            HPX_THROW_EXCEPTION(hpx::error::not_implemented,
                NS_STR "check_options()",
                "counter multiplexing is currently not supported");
#if 0
        if (vm.count("hpx:papi-multiplex") && vm["hpx:papi-multiplex"].as<long>() < 0)
            HPX_THROW_EXCEPTION(hpx::error::commandline_option_error,
                NS_STR "check_options()",
                "argument to --hpx:papi-multiplex must be positive");
#endif
        return needed;
    }

    ///////////////////////////////////////////////////////////////////////////
    // event listing options

    // decipher event type flags
    std::string decode_preset_type(unsigned t)
    {
        std::string s;
        if (t & PAPI_PRESET_BIT_MSC)
            s += ",OTHER";
        if (t & PAPI_PRESET_BIT_INS)
            s += ",INS";
        if (t & PAPI_PRESET_BIT_IDL)
            s += ",IDLE";
        if (t & PAPI_PRESET_BIT_BR)
            s += ",BRANCH";
        if (t & PAPI_PRESET_BIT_CND)
            s += ",COND";
        if (t & PAPI_PRESET_BIT_MEM)
            s += ",MEM";
        if (t & PAPI_PRESET_ENUM_CACH)
            s += ",CACHE";
        if (t & PAPI_PRESET_BIT_L1)
            s += ",L1";
        if (t & PAPI_PRESET_BIT_L2)
            s += ",L2";
        if (t & PAPI_PRESET_BIT_L3)
            s += ",L3";
        if (t & PAPI_PRESET_BIT_TLB)
            s += ",TLB";
        if (t & PAPI_PRESET_BIT_FP)
            s += ",FP";
        if (!s.empty())
            return s.substr(1);
        s = "-";
        return s;
    }

    // determine dependencies of a derived event
    std::string dependencies(event_info const& info)
    {
        if (info.count <= 1)
            return std::string("none");
        std::string s;
        for (size_t i = 0; i < info.count; ++i)
        {
            if (i > 0)
                s += ",";
            s += info.name[i];
        }
        return s;
    }

    void list_presets()
    {
        // collect available events and print their descriptions
        avail_preset_info_gen gen;
        for (auto it =
                 hpx::util::generator_iterator<avail_preset_info_gen>(&gen);
             *it != nullptr; ++it)
        {
            hpx::util::format_to(std::cout,
                "Event        : {}\n"
                "Type         : {}\n"
                "Code         : {:x}\n"
                "Derived from : {}\n"
                "Description  : {}\n",
                (*it)->symbol, decode_preset_type((*it)->event_type),
                (*it)->event_code, dependencies(**it), (*it)->long_descr);

            if (strnlen((*it)->note, PAPI_HUGE_STR_LEN) > 0)
            {
                // is this actually provided anywhere??
                std::cout << "Note:\n" << (*it)->note << "\n";
            }

            std::cout << std::string(79, '-') + '\n';
        }
    }

    std::string registers(PAPI_event_info_t const& info)
    {
        if (!info.count)
            return "-";
        std::string regs;
        for (unsigned i = 0; i < info.count; ++i)
        {
            if (info.name[i][0])
            {
                if (!regs.empty())
                    regs += std::string(15, ' ');
                regs +=
                    hpx::util::format("reg[{}] name: {:-20} value: {:#16x}\n",
                        i, info.name[i], info.code[i]);
            }
        }
        return regs;
    }

    void print_native_info(PAPI_event_info_t const& info)
    {
        hpx::util::format_to(std::cout,
            "Event        : {}\n"
            "Type         : native\n"
            "Code         : {:#x}\n"
            "Derived from : {}\n"
            "Description  : {}\n",
            info.symbol, info.event_code, registers(info), info.long_descr);

        if (strnlen(info.note, PAPI_HUGE_STR_LEN) > 0)
        {
            std::cout << "Note:\n" << info.note << "\n";
        }

        std::cout << std::string(79, '-') + '\n';
    }

    void list_native()
    {
        // list available events for each PAPI component
        for (int ci = 0; ci < PAPI_num_components(); ++ci)
        {
            native_info_gen gen(ci);
            for (auto it = hpx::util::generator_iterator<native_info_gen>(&gen);
                 *it != nullptr; ++it)
            {
                print_native_info(**it);
            }
        }
    }

    // list available events with descriptions
    void list_events(std::string const& scope)
    {
        using asio::ip::host_name;

        std::string host(host_name());
        // print header
        std::string hdr("PAPI events available on ");
        if (!host.empty())
        {
            hdr += hpx::util::format(
                "{} (locality {}):", host, hpx::get_locality_id());
        }
        else
        {
            hdr += hpx::util::format("locality {}:", hpx::get_locality_id());
        }
        std::cout << hdr << std::endl
                  << std::string(hdr.length(), '=') << std::endl;
        if (scope == "preset" || scope == "all")
            list_presets();
        if (scope == "native" || scope == "all")
            list_native();
    }

    std::uint32_t get_counter_thread(
        counter_path_elements const& cpe, std::string& label)
    {
        hpx::util::thread_mapper& tm = get_runtime().get_thread_mapper();
        if (cpe.instanceindex_ < 0)
        {
            label = cpe.instancename_;
        }
        else
        {
            label = hpx::util::format(
                "{}#{}", cpe.instancename_, cpe.instanceindex_);
        }
        return tm.get_thread_index(label);
    }

}}}}    // namespace hpx::performance_counters::papi::util

#endif
