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

#ifndef TANGO_CLIENT_EVENTCONSUMER_H
#define TANGO_CLIENT_EVENTCONSUMER_H

#include <tango/client/AttrConfEventDataList.h>
#include <tango/client/AttributeInfoEx.h>
#include <tango/client/DataReadyEventDataList.h>
#include <tango/client/DevIntrChangeEventDataList.h>
#include <tango/client/EventConsumerKeepAliveThread.h>
#include <tango/client/EventDataList.h>
#include <tango/client/PipeEventDataList.h>
#include <tango/common/omnithread_wrapper.h>
#include <tango/common/PointerWithLock.h>
#include <tango/common/tango_const.h>
#include <tango/common/zmq_event_marshalling.h>
#include <tango/server/Attribute.h>
#include <tango/server/Except.h>
#include <tango/server/ReadersWritersLock.h>
#include <tango/server/TangoMonitor.h>

#include <COS/CosNotification.hh>
#include <COS/CosNotifyChannelAdmin.hh>
#include <COS/CosNotifyComm.hh>
#include <zmq.hpp>

#include <iostream>
#include <map>
#include <memory>

namespace Tango
{
class ApiUtil;
class DeviceProxy;
class CallBack;
class EventQueue;
class TangoMonitor;
class DeviceData;
class EventDataList;
class AttrConfEventDataList;
class DataReadyEventDataList;
class DevIntrChangeEventDataList;
class PipeEventDataList;
// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor)
class EventConsumerKeepAliveThread;
class Database;
class FwdEventData;

#ifndef _USRDLL
extern "C"
{
#endif
    void leavefunc();
#ifndef _USRDLL
}
#endif

/********************************************************************************
 *                                                                                 *
 *                         EventConsumer class                                        *
 *                                                                                 *
 *******************************************************************************/

class EventConsumer
{
    typedef void (*EventCallbackFunction)(std::string event_name,
                                          std::string event_type,
                                          Tango::DeviceAttribute *attr_value);

  public:
    EventConsumer(Tango::ApiUtil *ptr);

    virtual ~EventConsumer() { }

    void connect_event(DeviceProxy *,
                       const std::string &,
                       EventType,
                       CallBack *,
                       EventQueue *,
                       const std::vector<std::string> &,
                       std::string &,
                       int event_id,
                       EventSubMode event_sub_mode);
    void connect(DeviceProxy *, const std::string &, DeviceData &, const std::string &, bool &);

    void shutdown();
    void shutdown_keep_alive_thread();
    static ChannelType get_event_system_for_event_id(int);
    // Reclaims consumer specific data from the static event channel map
    virtual void cleanup_EventChannel_map() = 0;
    // Clears the static event channel map to an empty state
    static void reset_static_data();
    virtual void get_subscription_command_name(std::string &) = 0;

    int subscribe_event(DeviceProxy *device,
                        const std::string &attribute,
                        EventType event,
                        CallBack *callback,
                        const std::vector<std::string> &filters,
                        EventSubMode event_sub_mode);
    int subscribe_event(DeviceProxy *device,
                        const std::string &attribute,
                        EventType event,
                        int event_queue_size,
                        const std::vector<std::string> &filters,
                        EventSubMode event_sub_mode);
    int subscribe_event(DeviceProxy *device, EventType event, CallBack *callback, EventSubMode event_sub_mode);
    int subscribe_event(DeviceProxy *device, EventType event, int event_queue_size, EventSubMode event_sub_mode);

    void unsubscribe_event(int event_id);
    virtual void get_subscribed_event_ids(DeviceProxy *, std::vector<int> &) = 0;
    virtual void query_event_system(std::ostream &os) = 0;

    // methods to access data in event queues

    void get_events(int event_id, EventDataList &event_list);
    void get_events(int event_id, AttrConfEventDataList &event_list);
    void get_events(int event_id, DataReadyEventDataList &event_list);
    void get_events(int event_id, DevIntrChangeEventDataList &event_list);
    void get_events(int event_id, PipeEventDataList &event_list);
    void get_events(int event_id, CallBack *cb);
    int event_queue_size(int event_id);
    TimeVal get_last_event_date(int event_id);
    bool is_event_queue_empty(int event_id);

    int get_thread_id()
    {
        return thread_id;
    }

    void add_not_connected_event(std::optional<DevFailed>, EventNotConnected &);

    void async_event_subscription(DeviceProxy *device,
                                  const std::string &attribute,
                                  EventType event,
                                  CallBack *callback,
                                  EventQueue *ev_queue,
                                  const std::string &event_name,
                                  int event_id,
                                  EventSubMode event_sub_mode);

    static ReadersWritersLock &get_map_modification_lock()
    {
        return map_modification_lock;
    }

    static KeepAliveThCmd cmd;
    static EventConsumerKeepAliveThread *keep_alive_thread;

  protected:
    int subscribe_event(DeviceProxy *device,
                        const std::string &attribute,
                        EventType event,
                        CallBack *callback,
                        EventQueue *ev_queue,
                        const std::vector<std::string> &filters,
                        EventSubMode event_sub_mode);
    friend class EventConsumerKeepAliveThread;
    void attr_to_device(const AttributeValue *, const AttributeValue_3 *, long, DeviceAttribute *);
    void attr_to_device(const AttributeValue_4 *, DeviceAttribute *);
    void attr_to_device(const ZmqAttributeValue_4 *, DeviceAttribute *);
    void attr_to_device(const ZmqAttributeValue_5 *, DeviceAttribute *);
    void conf_to_info(AttributeConfig_2 &, AttributeInfoEx **);
    void update_alias_map(Database *db, const std::vector<std::string> &vs);
    std::string get_client_attribute_name(const std::string &, const std::vector<std::string> &filters);

    static std::map<std::string, std::string>
        device_channel_map; // key - device_name, value - channel name (full adm name)
    static std::map<std::string, EventChannelStruct>
        channel_map; // key - channel_name (full adm name), value - Event Channel info
    static std::map<std::string, EventCallBackStruct>
        event_callback_map; // key - callback_key, value - Event CallBack info

    // ReaderWriter lock for event_callback_map and event_not_connected
    // not used for async_event_to_be_connected
    static ReadersWritersLock map_modification_lock;

    static std::vector<EventNotConnected> event_not_connected;
    static int subscribe_event_id; // unique event id
    static std::vector<std::string> env_var_fqdn_prefix;
    static std::map<std::string, std::string> alias_map; // key - real host name, value - alias

    /// @{
    /// Vector with not yet connected events from async modes
    /// all readers/writers must honour async_event_lock
    static std::vector<EventNotConnected> async_event_to_be_connected;
    static ReadersWritersLock async_event_lock;
    /// @}

    std::string device_name;
    std::string obj_name_lower;
    int thread_id;

    void add_new_callback(DeviceProxy *, EvCbIte &, CallBack *, EventQueue *, int);
    void get_fire_sync_event(DeviceProxy *,
                             CallBack *,
                             EventQueue *,
                             EventType,
                             std::string &,
                             const std::string &,
                             EventCallBackStruct &,
                             std::string &);

    virtual void connect_event_channel(const std::string &, Database *, bool, DeviceData &) = 0;

    virtual void disconnect_event_channel(TANGO_UNUSED(const std::string &channel_name),
                                          TANGO_UNUSED(const std::string &endpoint),
                                          TANGO_UNUSED(const std::string &endpoint_event))
    {
    }

    virtual 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) = 0;

    virtual void disconnect_event(const std::string &, const std::string &) { }

    virtual void set_channel_type(EventChannelStruct &) = 0;
    virtual void zmq_specific(DeviceData &, std::string &, DeviceProxy *, const std::string &) = 0;

    virtual 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) = 0;
    void get_subscription_info(const std::shared_ptr<Tango::DeviceProxy> &adm_dev,
                               Tango::DeviceProxy *device,
                               std::string obj_name_lower,
                               std::string event_name,
                               Tango::DeviceData &dd,
                               bool &zmq_used);
    std::string get_callback_key(const std::string &device_name,
                                 const std::string &obj_name_lower,
                                 EventType event_type,
                                 const std::string &event_name);
    int get_new_event_id();
    template <typename T>
    void get_events_helper(int event_id, T &list);
    Tango::EventQueue *get_event_queue(int event_id);
};

} // namespace Tango

#endif // TANGO_CLIENT_EVENTCONSUMER_H
