/* -*- c++ -*- */
/*
 * Copyright 2004,2009,2010,2013 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

#ifndef INCLUDED_GR_RUNTIME_BLOCK_DETAIL_H
#define INCLUDED_GR_RUNTIME_BLOCK_DETAIL_H

#include <gnuradio/api.h>
#include <gnuradio/buffer.h>
#include <gnuradio/buffer_reader.h>
#include <gnuradio/high_res_timer.h>
#include <gnuradio/logger.h>
#include <gnuradio/runtime_types.h>
#include <gnuradio/tags.h>
#include <gnuradio/tpb_detail.h>
#include <stdexcept>

namespace gr {

/*!
 * \brief Implementation details to support the signal processing abstraction
 * \ingroup internal
 *
 * This class contains implementation detail that should be "out of
 * sight" of almost all users of GNU Radio.  This decoupling also
 * means that we can make changes to the guts without having to
 * recompile everything.
 */
class GR_RUNTIME_API block_detail
{
public:
    ~block_detail();

    int ninputs() const { return d_ninputs; }
    int noutputs() const { return d_noutputs; }
    bool sink_p() const { return d_noutputs == 0; }
    bool source_p() const { return d_ninputs == 0; }

    void set_done(bool done);
    bool done() const { return d_done; }

    void set_input(unsigned int which, buffer_reader_sptr reader);
    buffer_reader_sptr input(unsigned int which)
    {
        if (which >= d_ninputs)
            throw std::invalid_argument("block_detail::input");
        return d_input[which];
    }

    void set_output(unsigned int which, buffer_sptr buffer);
    buffer_sptr output(unsigned int which)
    {
        if (which >= d_noutputs)
            throw std::invalid_argument("block_detail::output");
        return d_output[which];
    }

    /*!
     * \brief Tell the scheduler \p how_many_items of input stream \p
     * which_input were consumed.
     */
    void consume(int which_input, int how_many_items);

    /*!
     * \brief Tell the scheduler \p how_many_items were consumed on
     * each input stream.
     */
    void consume_each(int how_many_items);

    /*!
     * \brief Tell the scheduler \p how_many_items were produced on
     * output stream \p which_output.
     */
    void produce(int which_output, int how_many_items);

    /*!
     * \brief Tell the scheduler \p how_many_items were produced on
     * each output stream.
     */
    void produce_each(int how_many_items);

    // Return the number of items read on input stream which_input
    uint64_t nitems_read(unsigned int which_input);

    // Return the number of items written on output stream which_output
    uint64_t nitems_written(unsigned int which_output);

    // sets nitems_read and nitems_written to 0 for all input/output
    // buffers.
    void reset_nitem_counters();

    // Clears all tags from the input buffers.
    void clear_tags();

    /*!
     * \brief  Adds a new tag to the given output stream.
     *
     * Calls gr::buffer::add_item_tag(),
     * which appends the tag onto its deque.
     *
     * \param which_output  an integer of which output stream to attach the tag
     * \param tag the tag object to add
     */
    void add_item_tag(unsigned int which_output, const tag_t& tag);

    /*!
     * \brief  Removes a tag from the given input stream.
     *
     * Calls gr::buffer::remove_item_tag().
     * The tag in question will then no longer appear on subsequent calls of
     * get_tags_in_range().
     *
     * \param which_input  an integer of which input stream to remove the tag from
     * \param tag the tag object to add
     * \param id The unique block ID (use gr::block::unique_id())
     */
    void remove_item_tag(unsigned int which_input, const tag_t& tag, long id);

    /*!
     * \brief Given a [start,end), returns a vector of all tags in the range.
     *
     * Pass-through function to gr::buffer_reader to get a vector of
     * tags in given range. Range of counts is from start to end-1.
     *
     * Tags are tuples of:
     *      (item count, source id, key, value)
     *
     * \param v            a vector reference to return tags into
     * \param which_input  an integer of which input stream to pull from
     * \param abs_start    a uint64 count of the start of the range of interest
     * \param abs_end      a uint64 count of the end of the range of interest
     * \param id           Block ID
     */
    void get_tags_in_range(std::vector<tag_t>& v,
                           unsigned int which_input,
                           uint64_t abs_start,
                           uint64_t abs_end,
                           long id);

    /*!
     * \brief Given a [start,end), returns a vector of all tags in the
     * range with a given key.
     *
     * Calls get_tags_in_range(which_input, abs_start, abs_end) to get
     * a vector of tags from the buffers. This function then provides
     * a secondary filter to the tags to extract only tags with the
     * given 'key'.
     *
     * Tags are tuples of:
     *      (item count, source id, key, value)
     *
     * \param v            a vector reference to return tags into
     * \param which_input  an integer of which input stream to pull from
     * \param abs_start    a uint64 count of the start of the range of interest
     * \param abs_end      a uint64 count of the end of the range of interest
     * \param key          a PMT symbol to select only tags of this key
     * \param id           Block ID
     */
    void get_tags_in_range(std::vector<tag_t>& v,
                           unsigned int which_input,
                           uint64_t abs_start,
                           uint64_t abs_end,
                           const pmt::pmt_t& key,
                           long id);

    /*!
     * \brief Set core affinity of block to the cores in the vector
     * mask.
     *
     * \param mask a vector of ints of the core numbers available to
     * this block.
     */
    void set_processor_affinity(const std::vector<int>& mask);

    /*!
     * \brief Unset core affinity.
     */
    void unset_processor_affinity();

    /*!
     * \brief Get the current thread priority
     */
    int thread_priority();

    /*!
     * \brief Set the current thread priority
     *
     * \param priority the new thread priority to set
     */
    int set_thread_priority(int priority);

    /*!
     * Post general_work() cleanup to decrement the active counts for all inputs
     * and outputs.
     */
    void post_work_cleanup()
    {
        // Decrement active counts for all inputs and outputs
        for (int i = 0; i < noutputs(); i++)
            output(i)->decrement_active();
        for (int i = 0; i < ninputs(); i++)
            input(i)->buffer()->decrement_active();
    }

    bool threaded;                  // set if thread is currently running.
    gr::thread::gr_thread_t thread; // portable thread handle

    void start_perf_counters();
    void stop_perf_counters(int noutput_items, int nproduced);
    void reset_perf_counters();

    // Calls to get performance counter items
    float pc_noutput_items();
    float pc_nproduced();
    float pc_input_buffers_full(size_t which);
    std::vector<float> pc_input_buffers_full();
    float pc_output_buffers_full(size_t which);
    std::vector<float> pc_output_buffers_full();
    float pc_work_time();

    float pc_noutput_items_avg();
    float pc_nproduced_avg();
    float pc_input_buffers_full_avg(size_t which);
    std::vector<float> pc_input_buffers_full_avg();
    float pc_output_buffers_full_avg(size_t which);
    std::vector<float> pc_output_buffers_full_avg();
    float pc_work_time_avg();
    float pc_throughput_avg();

    float pc_noutput_items_var();
    float pc_nproduced_var();
    float pc_input_buffers_full_var(size_t which);
    std::vector<float> pc_input_buffers_full_var();
    float pc_output_buffers_full_var(size_t which);
    std::vector<float> pc_output_buffers_full_var();
    float pc_work_time_var();

    float pc_work_time_total();

    tpb_detail d_tpb; // used by thread-per-block scheduler
    int d_produce_or;

    int consumed() const;

    // necessary because stupidly block_executor.cc's "propagate_tags" is a function, not
    // any class member
    gr::logger_ptr d_logger, d_debug_logger;

    // ----------------------------------------------------------------------------

private:
    unsigned int d_ninputs;
    unsigned int d_noutputs;
    std::vector<buffer_reader_sptr> d_input;
    std::vector<buffer_sptr> d_output;
    bool d_done;
    int d_consumed;

    // Performance counters
    float d_ins_noutput_items;
    float d_avg_noutput_items;
    float d_var_noutput_items;
    float d_total_noutput_items;
    gr::high_res_timer_type d_pc_start_time;
    gr::high_res_timer_type d_pc_last_work_time;
    float d_ins_nproduced;
    float d_avg_nproduced;
    float d_var_nproduced;
    std::vector<float> d_ins_input_buffers_full;
    std::vector<float> d_avg_input_buffers_full;
    std::vector<float> d_var_input_buffers_full;
    std::vector<float> d_ins_output_buffers_full;
    std::vector<float> d_avg_output_buffers_full;
    std::vector<float> d_var_output_buffers_full;
    gr::high_res_timer_type d_start_of_work, d_end_of_work;
    float d_ins_work_time;
    float d_avg_work_time;
    float d_var_work_time;
    float d_total_work_time;
    float d_avg_throughput;
    float d_pc_counter;

    block_detail(unsigned int ninputs, unsigned int noutputs);

    friend struct tpb_detail;

    friend GR_RUNTIME_API block_detail_sptr make_block_detail(unsigned int ninputs,
                                                              unsigned int noutputs);
};

GR_RUNTIME_API block_detail_sptr make_block_detail(unsigned int ninputs,
                                                   unsigned int noutputs);

GR_RUNTIME_API long block_detail_ncurrently_allocated();

} /* namespace gr */

#endif /* INCLUDED_GR_RUNTIME_BLOCK_DETAIL_H */
