//
// Copyright 2010-2013 Ettus Research LLC
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: GPL-3.0-or-later
//

#ifndef RESPONDER_H
#define RESPONDER_H

#include <uhd/usrp/multi_usrp.hpp>
#include <curses.h>
#include <stdint.h>
#include <ctime>
#include <map>

using namespace std;


class Responder
{
public:
    enum ReturnCodes {
        RETCODE_OK                = 0,
        RETCODE_BAD_ARGS          = -1,
        RETCODE_RUNTIME_ERROR     = -2,
        RETCODE_UNKNOWN_EXCEPTION = -3,
        RETCODE_RECEIVE_TIMEOUT   = -4,
        RETCODE_RECEIVE_FAILED    = -5,
        RETCODE_MANUAL_ABORT      = -6,
        RETCODE_BAD_PACKET        = -7,
        RETCODE_OVERFLOW          = -8
    };

    struct Options
    {
        string device_args;
        double delay;
        double sample_rate;
        double trigger_level;
        float output_scale;
        double response_duration;
        double dc_offset_delay;
        double init_delay;
        double timeout;
        size_t samps_per_buff;
        size_t samps_per_packet;
        double level_calibration_duration;
        std::string test_title;
        std::string stats_filename;
        std::string stats_filename_prefix;
        std::string stats_filename_suffix;
        double delay_min;
        double delay_max;
        double delay_step;
        double pulse_detection_threshold;
        uint64_t test_iterations;
        size_t end_test_after_success_count;
        size_t skip_iterations;
        double simulate_frequency;
        double time_mul;
        size_t flush_count;
        size_t optimize_padding;
        double rt_priority;
        bool ignore_simulation_check;
        bool test_iterations_is_sample_count;
        bool skip_eob;
        bool adjust_simulation_rate;
        bool optimize_simulation_rate;
        bool no_stats_file;
        bool log_file;
        bool batch_mode;
        bool skip_if_results_exist;
        bool skip_send;
        bool combine_eob;
        bool pause;
        bool realtime;
        float invert;
        float output_value;
        bool no_delay;
        bool allow_late_bursts;

        uint64_t level_calibration_count() const
        {
            return (uint64_t)(sample_rate * level_calibration_duration);
        }

        uint64_t response_length() const
        {
            return (uint64_t)(sample_rate * response_duration);
        }

        uint64_t highest_delay_samples(const double delay) const
        {
            return (uint64_t)(delay * (double)sample_rate);
        }

        uint64_t simulate_duration(const double simulate_frequency) const
        {
            if (simulate_frequency > 0.0) {
                return (uint64_t)((double)sample_rate / simulate_frequency);
            }
            return 0;
        }
    };

    typedef struct Stats
    {
        double delay;
        uint64_t detected;
        uint64_t missed;
        uint64_t skipped;
    } STATS;

    typedef std::map<uint64_t, STATS> StatsMap;

    struct DebugInfo
    {
        time_t start_time;
        time_t end_time;
        time_t start_time_test;
        time_t end_time_test;
        time_t first_send_timeout;
    };
    Responder(Options& opt);
    virtual ~Responder();

    // Main entry point after constructor.
    int run();

    int get_return_code()
    {
        return _return_code;
    }

protected:
private:
    // These 2 variables are used for ncurses output.
    WINDOW* _window;
    std::stringstream _ss;
    std::stringstream _ss_cerr;

    // struct which holds all arguments as constants settable from outside the class
    const Options _opt;

    string _stats_filename; // Specify name of statistics file
    string _stats_log_filename; // Specify name for log file.
    double _delay; // may be altered in all modes.
    size_t _samps_per_packet; // This is one of the options of interest. Find out how well
                              // it performs.
    double _delay_step; // may be altered in interactive mode
    double _simulate_frequency; // updated during automatic test iterations

    // additional attributes
    bool _allow_late_bursts; // may be altered in interactive mode
    bool _no_delay; // may be altered in interactive mode

    // dependent variables
    uint64_t _response_length;
    int64_t _init_delay_count;
    int64_t _dc_offset_countdown;
    int64_t _level_calibration_countdown;
    uint64_t _simulate_duration;
    uint64_t _original_simulate_duration;

    // these variables store test conditions
    uint64_t _num_total_samps; // printed on exit
    size_t _overruns; // printed on exit
    StatsMap _mapStats; // store results
    uint64_t _max_success; // < 0 --> write results to file
    int _return_code;

    // Hold USRP, streams and commands
    uhd::usrp::multi_usrp::sptr _usrp;
    uhd::tx_streamer::sptr _tx_stream;
    uhd::rx_streamer::sptr _rx_stream;
    uhd::stream_cmd_t _stream_cmd;

    // Keep track of number of timeouts.
    uint64_t _timeout_burst_count;
    uint64_t _timeout_eob_count;

    // Transmit attributes
    float* _pResponse;

    // Control print parameters.
    int _y_delay_pos;
    int _x_delay_pos; // Remember the cursor position of delay line
    uint64_t _last_overrun_count;

    // Hold debug info during test. Will be included in log file.
    DebugInfo _dbginfo;

    /*
     * Here are the class's member methods.
     */
    // These methods are used for ncurses output
    void create_ncurses_window();
    void FLUSH_SCREEN();
    void FLUSH_SCREEN_NL();

    // Variable calculation helpers
    inline uint64_t get_response_length(double sample_rate, double response_duration)
    {
        return (uint64_t)(sample_rate * response_duration);
    }
    int calculate_dependent_values();

    // make sure existing results are not overwritten accidently
    bool set_stats_filename(string test_id);
    bool check_for_existing_results();

    // Functions that may cause Responder to finish
    void register_stop_signal_handler();
    bool test_finished(size_t success_count);
    int test_step_finished(uint64_t trigger_count,
        uint64_t num_total_samps_test,
        STATS statsCurrent,
        size_t success_count);

    // Check if sent burst could be transmitted.
    bool tx_burst_is_late();

    // Handle receiver errors such as overflows.
    bool handle_rx_errors(uhd::rx_metadata_t::error_code_t err, size_t num_rx_samps);

    // In interactive mode, handle Responder control and output.
    bool handle_interactive_control();
    void print_interactive_msg(std::string msg);

    // calibration important for interactive mode with 2nd USRP connected.
    float calibrate_usrp_for_test_run();

    // Run actual test
    void run_test(float threshold = 0.0f);

    // Detect falling edge
    bool get_new_state(
        uint64_t total_samps, uint64_t simulate_duration, float val, float threshold);
    uint64_t detect_respond_pulse_count(STATS& statsCurrent,
        std::vector<std::complex<float>>& buff,
        uint64_t trigger_count,
        size_t num_rx_samps,
        float threshold,
        uhd::time_spec_t rx_time);

    // Hold test results till they are printed to a file
    void add_stats_to_results(STATS statsCurrent, double delay);

    // Control USRP and necessary streamers
    uhd::usrp::multi_usrp::sptr create_usrp_device();
    void set_usrp_rx_dc_offset(uhd::usrp::multi_usrp::sptr usrp, bool ena);
    void stop_usrp_stream();
    uhd::tx_streamer::sptr create_tx_streamer(uhd::usrp::multi_usrp::sptr usrp);
    uhd::rx_streamer::sptr create_rx_streamer(uhd::usrp::multi_usrp::sptr usrp);

    // Send burst and handle results.
    bool send_tx_burst(uhd::time_spec_t rx_time, size_t n);
    void handle_tx_timeout(int burst, int eob);
    float* alloc_response_buffer_with_data(uint64_t response_length);
    uhd::tx_metadata_t get_tx_metadata(uhd::time_spec_t rx_time, size_t n);

    // Control test parameters
    void update_and_print_parameters(const STATS& statsPrev, const double delay);
    double get_simulate_frequency(
        double delay, uint64_t response_length, uint64_t original_simulate_duration);
    double get_max_possible_frequency(
        uint64_t highest_delay_samples, uint64_t response_length);

    // Helper methods to print status during test.
    void print_init_test_status();
    void print_test_title();
    void print_usrp_status();
    void print_create_usrp_msg();
    void print_tx_stream_status();
    void print_rx_stream_status();
    void print_test_parameters();
    void print_formatted_delay_line(const uint64_t simulate_duration,
        const uint64_t old_simulate_duration,
        const STATS& statsPrev,
        const double delay,
        const double simulate_frequency);
    void print_overrun_msg();
    void print_error_msg(std::string msg);
    void print_timeout_msg();
    void print_final_statistics();
    void print_msg_and_wait(std::string msg);
    void print_msg(std::string msg);

    // Safe results of test to file.
    void write_statistics_to_file(StatsMap mapStats);
    void safe_write_statistics_to_file(
        StatsMap mapStats, uint64_t max_success, int return_code);
    void write_log_file();

    // Write debug info to log file if requested.
    void write_debug_info(ofstream& logs);
    std::string get_gmtime_string(time_t time);
    std::string enum2str(int return_code);
    std::vector<std::map<std::string, std::string>> read_eth_info();
    uhd::device_addr_t get_usrp_info();
    std::map<std::string, std::string> get_hw_info();
    std::string get_ip_subnet_addr(std::string ip);
};

#endif // RESPONDER_H