//  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)

#pragma once

#include <hpx/config.hpp>

#if defined(HPX_HAVE_PAPI)

#include <hpx/modules/errors.hpp>
#include <hpx/modules/format.hpp>
#include <hpx/modules/iterator_support.hpp>
#include <hpx/modules/program_options.hpp>
#include <hpx/performance_counters/counters.hpp>

#include <cstdint>
#include <cstring>
#include <string>
#include <vector>

#include <papi.h>

#if PAPI_VERSION_MAJOR(PAPI_VERSION) > 4
#define PAPI_EXTENDED_EVENT_CODES 1
#endif

namespace hpx {
    namespace performance_counters {
        namespace papi {
            namespace util {
    using hpx::program_options::options_description;
    using hpx::program_options::variables_map;

    ///////////////////////////////////////////////////////////////////////////
    // PAPI call wrapper
    inline void papi_call(
        int rc, char const* info, char const* fname, int ok = PAPI_OK)
    {
        if (rc != ok)
        {
            HPX_THROW_EXCEPTION(hpx::error::no_success, fname,
                hpx::util::format("{} ({})", info, PAPI_strerror(rc)));
        }
    }
    inline void papi_call(
        int rc, std::string const& info, char const* fname, int ok = PAPI_OK)
    {
        papi_call(rc, info.c_str(), fname, ok);
    }

    ///////////////////////////////////////////////////////////////////////////
    // generator retrieving information on PAPI events
    class event_info_generator
    {
    protected:
        int const mask_;            // event type mask
        int const cid_;             // component ID
        int event_;                 // next event to get info about
        bool active_;               // false when generation terminated
        PAPI_event_info_t info_;    // event info to be returned

        // set event info
        virtual bool get_info()
        {
            return PAPI_get_event_info(event_, &info_) == PAPI_OK;
        }
        // get the next event in sequence according to enumeration mask
        virtual bool get_next_event()
        {
            return PAPI_enum_event(&event_, mask_) == PAPI_OK;
        }

        event_info_generator()
          : mask_(0)
          , cid_(0)
          , event_(PAPI_NULL)
          , active_(false)
        {
        }
        event_info_generator(int mask, int first, int cid = 0)
          : mask_(mask)
          , cid_(cid)
        {
#if defined(PAPI_EXTENDED_EVENT_CODES)
            reset(first);
#else
            reset(first | PAPI_COMPONENT_MASK(cid));
#endif
        }

    public:
        // required generator interface
        typedef PAPI_event_info_t const* result_type;

        PAPI_event_info_t const* operator()()
        {
            if (!active_)
                return nullptr;
            while (!get_info())
                if (!(active_ = get_next_event()))
                    return nullptr;
            active_ = get_next_event();
            return &info_;
        }

        // reset event scan sequence
        void reset(int init, bool force = false)
        {
            if (force)
            {    // init must be a valid event code
                event_ = init;
                active_ = true;
                return;
            }
            // init is the event type flag; obtain the first valid event code
#if defined(PAPI_EXTENDED_EVENT_CODES)
            active_ =
                (PAPI_enum_cmp_event(&init, PAPI_ENUM_FIRST, cid_) == PAPI_OK);
#else
            active_ = (PAPI_enum_event(&init, PAPI_ENUM_FIRST) == PAPI_OK);
#endif
            if (active_)
                event_ = init;
        }
    };

    // PAPI preset-only enumerator
    template <bool all>
    class preset_enumerator : public event_info_generator
    {
    protected:
        bool get_info()
        {    // locally available presets must not have null info_.count
            return event_info_generator::get_info() &&
                (!all || info_.count > 0);
        }

    public:
        preset_enumerator()
          : event_info_generator((all) ? PAPI_ENUM_ALL : PAPI_PRESET_ENUM_AVAIL,
                PAPI_PRESET_MASK)
        {
        }
    };

    // nested enumerator on umasks available for some native events;
    // initiated with a tracepoint event without any unit masks applied
    class native_umask_enumerator : public event_info_generator
    {
    public:
        //native_umask_enumerator():
        //    event_info_generator(PAPI_NTV_ENUM_UMASKS, PAPI_NULL) { }
        explicit native_umask_enumerator(int first = PAPI_NULL)
          : event_info_generator(PAPI_NTV_ENUM_UMASKS, first)
        {
            reset(first);
        }

        void reset(int first)
        {
            event_info_generator::reset(first, true);
            active_ = get_next_event();    // advance to the first umask event
        }
    };

    // PAPI native event enumerator
    template <bool with_umasks>
    class native_enumerator : public event_info_generator
    {
        native_umask_enumerator umask_gen_;
        hpx::util::generator_iterator<native_umask_enumerator> umask_iter_;
        unsigned const component_index_;
        bool umasks_present_, umask_seq_;

        bool get_info()
        {
            if (with_umasks && umasks_present_ && umask_seq_)
            {
                if (*umask_iter_)
                {    // nested iteration in progress
                    info_ = **umask_iter_;
                    return true;
                }
                return false;
            }
            return event_info_generator::get_info();
        }
        bool get_next_event()
        {
            if (with_umasks && umasks_present_)
            {
                if (umask_seq_)
                {
                    if (*++umask_iter_)
                        return true;
                    umask_seq_ = false;
                }
                else
                {
                    umask_gen_.reset(event_);
                    umask_iter_ =
                        hpx::util::generator_iterator<native_umask_enumerator>(
                            &umask_gen_);
                    if (*umask_iter_)
                        return umask_seq_ = true;    // not ==
                }
            }
            return event_info_generator::get_next_event();
        }

    public:
        explicit native_enumerator(unsigned comp, int mask = PAPI_ENUM_EVENTS)
          : event_info_generator(mask, PAPI_NATIVE_MASK, comp)
          , component_index_(comp)
          , umask_seq_(false)
        {
            PAPI_component_info_t const* ci = PAPI_get_component_info(comp);
            if (!ci)
                HPX_THROW_EXCEPTION(hpx::error::bad_parameter,
                    "hpx::performance_counters::papi::util::native_enumerator",
                    "invalid PAPI component index");
            umasks_present_ = ci->cntr_umasks;
        }

        void reset(int first, bool force = false)
        {
            umask_seq_ = false;
            event_info_generator::reset(first, force);
        }
    };

    // types of commonly used info generators
    typedef preset_enumerator<true> all_preset_info_gen;
    typedef preset_enumerator<false> avail_preset_info_gen;
    typedef native_enumerator<true> native_info_gen;

    ///////////////////////////////////////////////////////////////////////////
    // map domain description to a number
    int map_domain(std::string const&);

    // command line option description for PAPI counters
    options_description get_options_description();

    // get processed command line options
    variables_map get_options();

    // quick sanity check for command line options
    bool check_options(variables_map const& vm);

    // create list of event strings from command line options that are
    // relevant to this locality
    bool get_local_events(
        std::vector<std::string>& ev, std::vector<std::string> const& opt);

    // list locally available events with detailed information
    void list_events(std::string const& scope);

    // get relevant thread label and index from counter description
    std::uint32_t get_counter_thread(
        counter_path_elements const&, std::string&);

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

#endif
