/*
 * SPDX-FileCopyrightText: 2025 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#ifndef TANGO_CLIENT_ZMQEVENTCONSUMER_H
#define TANGO_CLIENT_ZMQEVENTCONSUMER_H

#include <tango/client/EventConsumer.h>
#include <tango/client/EventConsumerKeepAliveThread.h>
#include <tango/common/omnithread_wrapper.h>
#include <tango/common/PointerWithLock.h>
#include <tango/common/zmq_event_marshalling.h>
#include <tango/server/TangoMonitor.h>

#include <zmq.hpp>

namespace Tango
{

class FwdEventData;

/********************************************************************************
 *                                                                                 *
 *                         ZmqEventConsumer class                                  *
 *                                                                                 *
 *******************************************************************************/

// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor)
class ZmqEventConsumer : public EventConsumer, public omni_thread
{
  public:
    ZmqEventConsumer(ApiUtil *ptr);

    void cleanup_EventChannel_map() override;

    void get_subscription_command_name(std::string &cmd) override
    {
        cmd = "ZmqEventSubscriptionChange";
    }

    void get_subscribed_event_ids(DeviceProxy *, std::vector<int> &) override;

    void query_event_system(std::ostream &os) override;
    static void enable_perf_mon(Tango::DevBoolean enabled);

    enum UserDataEventType
    {
        ATT_CONF = 0,
        ATT_READY,
        ATT_VALUE,
        DEV_INTR,
        PIPE
    };

    enum SocketCmd
    {
        SUBSCRIBE = 0,
        UNSUBSCRIBE
    };

  protected:
    void connect_event_channel(const std::string &, Database *, bool, DeviceData &) override;
    void disconnect_event_channel(const std::string &channel_name,
                                  const std::string &endpoint,
                                  const std::string &endpoint_event) override;
    void connect_event_system(const std::string &,
                              const std::string &,
                              const std::string &e,
                              const std::vector<std::string> &,
                              const EvChanIte &,
                              EventCallBackStruct &,
                              DeviceData &,
                              size_t) override;
    void disconnect_event(const std::string &, const std::string &) override;

    void set_channel_type(EventChannelStruct &ecs) override
    {
        ecs.channel_type = ZMQ;
    }

    void zmq_specific(DeviceData &, std::string &, DeviceProxy *, const std::string &) override;

    ReceivedFromAdmin initialize_received_from_admin(const Tango::DevVarLongStringArray *pArray,
                                                     const std::string &local_callback_key,
                                                     const std::string &adm_name,
                                                     bool device_from_env_var) override;

  private:
    zmq::context_t zmq_context;        // ZMQ context
    zmq::socket_t *heartbeat_sub_sock; // heartbeat subscriber socket
    zmq::socket_t *control_sock;       // control socket
    zmq::socket_t *event_sub_sock;     // event subscriber socket

    std::map<std::string, zmq::socket_t *> event_mcast; // multicast socket(s)
    std::vector<std::string> connected_pub;             //
    std::vector<std::string> connected_heartbeat;       //

    AttributeValue_var av;
    AttributeValue_3_var av3;
    ZmqAttributeValue_4 zav4;
    ZmqAttributeValue_5 zav5;
    AttributeConfig_2_var ac2;
    AttributeConfig_3_var ac3;
    AttributeConfig_5_var ac5;
    AttDataReady_var adr;
    DevIntrChange_var dic;
    ZmqDevPipeData zdpd;
    DevErrorList_var del;

    int old_poll_nb;
    TangoMonitor subscription_monitor{"subscription monitor"};
    omni_mutex sock_bound_mutex;
    bool ctrl_socket_bound{false};
    // variable to count the number of ZMQ_DELAY_EVENT requests currently in progress:
    int nb_current_delay_event_requests{0};

    void *run_undetached(void *arg) override;
    void push_heartbeat_event(std::string &);
    void push_zmq_event(std::string &, unsigned char, zmq::message_t &, bool, const DevULong &);
    bool process_ctrl(zmq::message_t &, zmq::pollitem_t *, int &);
    void process_heartbeat(zmq::message_t &, zmq::message_t &, zmq::message_t &);
    void process_event(zmq::message_t &, zmq::message_t &, zmq::message_t &, zmq::message_t &);

    void multi_tango_host(zmq::socket_t *, SocketCmd, const std::string &);

    void print_error_message(const char *mess);

    void set_ctrl_sock_bound()
    {
        sock_bound_mutex.lock();
        ctrl_socket_bound = true;
        sock_bound_mutex.unlock();
    }

    bool is_ctrl_sock_bound()
    {
        bool _b;
        sock_bound_mutex.lock();
        _b = ctrl_socket_bound;
        sock_bound_mutex.unlock();
        return _b;
    }

    void set_socket_hwm(int hwm);
    static void disconnect_socket(zmq::socket_t &, const char *);

    bool check_zmq_endpoint(const std::string &);

    friend class DelayEvent;

    FwdEventData *newFwdEventData(zmq::message_t &event_data,
                                  DeviceProxy *device,
                                  EventReason event_reason,
                                  DevErrorList &errors,
                                  std::string &event_name,
                                  std::string &full_att_name,
                                  long vers,
                                  const DeviceAttribute *dev_attr,
                                  bool no_unmarshalling,
                                  unsigned int cb_nb,
                                  unsigned int cb_ctr,
                                  const CallBack *callback) const;

    ~ZmqEventConsumer() override
    {
        delete event_sub_sock;
        event_sub_sock = nullptr;

        zmq_context.shutdown();
    }
};

} // namespace Tango

#endif
