#pragma once
#include <map>
#include <queue>
#include <any>
#include "sock_client.h"
#include "../common/protocol_commands.h"

enum class state
{
    disconnected,
    connected,
    logged_in
};

class system_client
{
public:
    system_client(const std::string & unix_file, std::function<void(const std::string&)> log_func = nullptr);
    ~system_client();

    state get_current_state() const;

    response_type send_login_cmd         (const std::string & username, const std::string & pass, access_rights & access);
    response_type send_logout_cmd        ();
    response_type send_ping_cmd          (const std::string & ip, size_t count);
    response_type send_traceroute_cmd    (const std::string & ip);
    response_type send_show_interface_cmd(const interface_value & interface);
    response_type send_copy_cmd          (const fm::side_description & src, const fm::side_description & dst);

    response_type send_set_dem_freq_cmd(uint32_t freq);
    response_type send_get_dem_freq_cmd(uint32_t & freq);

    response_type send_set_gain_param (const gain_value & interface_gain, double gain);
    response_type send_get_gain_param (const gain_value & interface_gain, double & gain);
    response_type send_radio_enable   (const cmd_radio & enable_choice, bool enbl_);
    response_type send_modulator_param(const modulator_value & mod_val, uint32_t value);
    response_type send_get_modulator_param(const modulator_value & mod_val, uint32_t &value);
    response_type send_set_demodulator_param(const demodulator_value & demod_val, uint32_t value);
    response_type send_get_demodulator_param(const getdemodulator_value & demod_val, uint32_t &value);
    response_type send_zynq_cmd(const zynq_value & zynq_val, double & value);
    response_type send_network_settings(const network_value & cmd_netw, const std::string & value);
    response_type send_get_network_settings(const network_value & cmd_netw, std::string & value);
    response_type send_rollof_and_baudrate(double & rollof, double &baudrate);
    response_type send_get_level_dem(const cmd_level_dem & lvl_dem_val, double &value);
    response_type send_get_dma_debug(const cmd_get_dma_debugg_enum & dma_debug, std::string &value);
    response_type send_set_10g_config(const cmd_10g_config & _10g_config, std::string &value);
    response_type send_set_dma_debug(const cmd_dma_debugg & dma_debugg, std::string &value);
    response_type send_set_acm_params(const ACM_parameters_serv &acm_params);
    response_type send_get_acm_params(cmd_get_acm_param &acm_params);

    response_type send_set_dpdi_params(dpdi_parameters &dpdi_params);
    response_type send_get_dpdi_params(dpdi_parameters &dpdi_params);

    response_type send_set_lbq_params(const uint32_t & tick_ms, const uint32_t & bucket_size);

    response_type send_set_qos_params(const std::string &node, const name_classes_qos & class_qos);
    response_type send_get_qos_params(std::string &node, const name_classes_qos & class_qos);


    void set_stdout_callback(std::function<void(const char *, uint32_t)> cb);
    void abort();

private:
    system_client(const system_client&)                  = delete;
    const system_client& operator=(const system_client&) = delete;

    boost::asio::io_context                        io_context;
    client                                  clt;
    std::function<void(const std::string&)> log;
    std::thread                             ctx_thread;
    std::atomic<uint32_t>                   cmd_id;

    std::mutex                                      cb_mtx;
    std::function<void(const char *, uint32_t len)> stdout_cb;

    std::mutex                                    responses_mtx;
    std::map<uint32_t, std::queue<response_type>> responses;
    std::map<uint32_t, std::queue<std::any>>      responses_data;
    std::condition_variable                       cv_resp;

    state current_state;

    std::mutex cmd_in_progress_mtx;

    response_type wait_for_response            (uint32_t id, const cmd_type & type, std::any & data);
    response_type wait_for_progressing_response(uint32_t c_id, const cmd_type & cmd_t);
    void          process_response             (uint32_t id, response_type resp, std::any && data);
    void          data_received                (const std::vector<uint8_t> & data);
    void          send_abort                   (uint32_t id);

    template<typename... Args>
    void send_to_socket(Args&&... args)
    {
        //TO-DO: need case for empty parameter pack?
        std::stringstream s;
        s << std::noskipws;
        {
            cereal::BinaryOutputArchive oarchive(s);
            oarchive(std::forward<Args>(args)...);
        }
        std::vector<uint8_t> data((std::istream_iterator<uint8_t>(s)), std::istream_iterator<uint8_t>());
        clt.write(data);

    }
};
