/*
 * SPDX-FileCopyrightText: 2025 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */
#include <tango/client/DelayEvent.h>

#include <tango/client/ApiUtil.h>
#include <tango/client/EventConsumer.h>

#include <zmq.hpp>

namespace Tango
{

//---------------------------------------------------------------------------------------------------------------------
//
// method :
//        DelayEvent::DelayEvent
//
// description :
//        A class to ask the ZMQ main thread to stop receiving external event. This is necessary to prevent a possible
//      deadlock which could happen if an event is received while a user is calling subscribe or unsubscribe event
//
// argument :
//        in :
//            - ec : Event consumer pointer
//
//--------------------------------------------------------------------------------------------------------------------

DelayEvent::DelayEvent(const PointerWithLock<EventConsumer> &ec)
{
    std::string str;
    ec->get_subscription_command_name(str);

    //
    // Do something only for ZMQ event system
    //

    if(str[0] == 'Z')
    {
        auto eve_con = ApiUtil::instance()->get_zmq_event_consumer_derived(ec.operator->());

        //
        // Do something only for ZMQ event system
        //

        zmq::message_t reply;

        try
        {
            zmq::socket_t sender(eve_con->zmq_context, ZMQ_REQ);

            //
            // In case this thread runs before the main ZMQ thread, it is possible to call connect before the main ZMQ
            // thread has binded its socket. In such a case, error code is set to ECONNREFUSED. If this happens, give
            // the main ZMQ thread a chance to run and retry the connect call. I have tried with a yield call but it
            // still failed in some cases (when running the DS with a file as database  for instance). Replace the yield
            // with a 15 mS sleep !!!
            //
            // Since ZMQ 4, itś possible to connect to the remote socket event if it is not yet bound but the remote
            // socket will hang in the its recev call!!!!
            // We still need the sleep call but not in the exception case
            //

            try
            {
                sender.connect(CTRL_SOCK_ENDPOINT);

                if(!eve_con->is_ctrl_sock_bound())
                {
#ifndef _TG_WINDOWS_
                    std::this_thread::sleep_for(std::chrono::milliseconds(15));
#else
                    std::this_thread::sleep_for(std::chrono::milliseconds(20));
#endif
                }
            }
            catch(zmq::error_t &e)
            {
                if(e.num() == ECONNREFUSED)
                {
#ifndef _TG_WINDOWS_
                    std::this_thread::sleep_for(std::chrono::milliseconds(15));
#else
                    std::this_thread::sleep_for(std::chrono::milliseconds(20));
#endif
                    sender.connect(CTRL_SOCK_ENDPOINT);
                }
                else
                {
                    throw;
                }
            }

            //
            // Build message sent to ZMQ main thread. In this case, this is only a command code
            //

            char buffer[10];
            int length = 0;

            buffer[length] = ZMQ_DELAY_EVENT;
            length++;

            eve_con->subscription_monitor.get_monitor();

            //
            // Send command to main ZMQ thread
            //

            zmq::message_t send_data(length);
            ::memcpy(send_data.data(), buffer, length);
            auto result = sender.send(send_data, zmq::send_flags::none);
            TANGO_ASSERT(result);

            result = sender.recv(reply);
            TANGO_ASSERT(result);
        }
        catch(zmq::error_t &e)
        {
            eve_con->subscription_monitor.rel_monitor();

            TangoSys_OMemStream o;

            o << "Failed to delay event!\n";
            o << "Error while communicating with the ZMQ main thread\n";
            o << "ZMQ message: " << e.what() << std::ends;

            TANGO_THROW_EXCEPTION(API_ZmqFailed, o.str());
        }

        //
        // In case of error returned by the main ZMQ thread
        //

        if(reply.size() != 2)
        {
            eve_con->subscription_monitor.rel_monitor();

            char err_mess[512];
            ::memcpy(err_mess, reply.data(), reply.size());
            err_mess[reply.size()] = '\0';

            TangoSys_OMemStream o;

            o << "Failed to delay events!\n";
            o << "Error while asking the ZMQ thread to delay events\n";
            o << "ZMQ message: " << err_mess << std::ends;

            TANGO_THROW_EXCEPTION(API_ZmqFailed, o.str());
        }
    }
}

DelayEvent::DelayEvent(EventConsumer *ec) :
    DelayEvent(ApiUtil::instance()->get_locked_event_consumer(ec))
{
}

DelayEvent::~DelayEvent()
{
    try
    {
        release();
    }
    catch(CORBA::Exception &e)
    {
        Tango::Except::print_exception(e);
    }
    catch(std::exception &e)
    {
        std::cerr << "DelayEvent::~DelayEvent(): std::exception thrown: " << e.what() << std::endl;
    }
    catch(...)
    {
        std::cerr << "DelayEvent::~DelayEvent(): Unknown exception thrown " << std::endl;
    }
}

void DelayEvent::release()
{
    auto ec = ApiUtil::instance()->get_zmq_event_consumer();

    if(ec == nullptr)
    {
        return;
    }

    auto eve_con = ApiUtil::instance()->get_zmq_event_consumer_derived(ec.operator->());

    zmq::message_t reply;

    try
    {
        zmq::socket_t sender(eve_con->zmq_context, ZMQ_REQ);
        sender.connect(CTRL_SOCK_ENDPOINT);

        //
        // Build message sent to ZMQ main thread. In this case, this is only a command code
        //

        char buffer[10];
        int length = 0;

        buffer[length] = ZMQ_RELEASE_EVENT;
        length++;

        //
        // Send command to main ZMQ thread
        //

        zmq::message_t send_data(length);
        ::memcpy(send_data.data(), buffer, length);
        auto result = sender.send(send_data, zmq::send_flags::none);
        TANGO_ASSERT(result);

        result = sender.recv(reply);
        TANGO_ASSERT(result);

        eve_con->subscription_monitor.rel_monitor();
    }
    catch(zmq::error_t &e)
    {
        eve_con->subscription_monitor.rel_monitor();

        TangoSys_OMemStream o;

        o << "Failed to delay event!\n";
        o << "Error while communicating with the ZMQ main thread\n";
        o << "ZMQ message: " << e.what() << std::ends;

        TANGO_THROW_EXCEPTION(API_ZmqFailed, o.str());
    }

    //
    // In case of error returned by the main ZMQ thread
    //

    if(reply.size() != 2)
    {
        char err_mess[512];
        ::memcpy(err_mess, reply.data(), reply.size());
        err_mess[reply.size()] = '\0';

        TangoSys_OMemStream o;

        o << "Failed to release event!\n";
        o << "Error while trying to ask the ZMQ thread to release events\n";
        o << "ZMQ message: " << err_mess << std::ends;

        TANGO_THROW_EXCEPTION(API_ZmqFailed, o.str());
    }
}

} // namespace Tango
