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

#include <tango/client/Database.h>
#include <tango/client/DeviceAttribute.h>
#include <tango/client/DummyDeviceProxy.h>
#include <tango/client/NotifdEventConsumer.h>
#include <tango/client/ZmqEventConsumer.h>
#include <tango/common/defines.h>
#include <tango/server/ReaderLock.h>
#include <tango/server/Util.h>
#include <tango/server/WriterLock.h>

#include <iomanip>
#include <thread>

#ifndef _TG_WINDOWS_
  #include <ifaddrs.h>
  #include <net/if.h>
  #include <netdb.h>
  #include <netinet/in.h> // FreeBSD
  #include <sys/ioctl.h>
  #include <sys/socket.h>
  #include <sys/types.h>

  #include <csignal>
#else
  #include <process.h>
  #include <ws2tcpip.h>
#endif

#include <tango/internal/event_system_info.h>

#include <tango/common/PointerWithLock.h>

namespace
{

void killproc()
{
    ::exit(-1);
}

void _t_handler(TANGO_UNUSED(int signum))
{
    std::thread t(killproc);
    t.detach();
    std::this_thread::sleep_for(std::chrono::seconds(3));
}

//-----------------------------------------------------------------------------------------------------------------
//
// method :
//        attr_to_device_base()
//
// description :
//
//
//-------------------------------------------------------------------------------------------------------------------

template <typename T>
inline void attr_to_device_base(const T *attr_value, Tango::DeviceAttribute *dev_attr)
{
    CORBA::Long *tmp_lo;
    CORBA::Short *tmp_sh;
    CORBA::Double *tmp_db;
    char **tmp_str;
    CORBA::Float *tmp_fl;
    CORBA::Boolean *tmp_boo;
    CORBA::UShort *tmp_ush;
    CORBA::Octet *tmp_uch;
    CORBA::LongLong *tmp_lolo;
    CORBA::ULong *tmp_ulo;
    CORBA::ULongLong *tmp_ulolo;
    Tango::DevState *tmp_state;
    Tango::DevEncoded *tmp_enc;

    CORBA::ULong max, len;

    dev_attr->name = attr_value->name;
    dev_attr->quality = attr_value->quality;
    dev_attr->data_format = attr_value->data_format;
    dev_attr->time = attr_value->time;
    dev_attr->dim_x = attr_value->r_dim.dim_x;
    dev_attr->dim_y = attr_value->r_dim.dim_y;
    dev_attr->set_w_dim_x(attr_value->w_dim.dim_x);
    dev_attr->set_w_dim_y(attr_value->w_dim.dim_y);
    dev_attr->err_list = new Tango::DevErrorList(attr_value->err_list);

    if(dev_attr->quality != Tango::ATTR_INVALID)
    {
        switch(attr_value->value._d())
        {
        case Tango::ATT_BOOL:
        {
            const Tango::DevVarBooleanArray &tmp_seq = attr_value->value.bool_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_boo = (const_cast<Tango::DevVarBooleanArray &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->BooleanSeq = new Tango::DevVarBooleanArray(max, len, tmp_boo, true);
            }
            else
            {
                tmp_boo = const_cast<CORBA::Boolean *>(tmp_seq.get_buffer());
                dev_attr->BooleanSeq = new Tango::DevVarBooleanArray(max, len, tmp_boo, false);
            }
            dev_attr->data_type = Tango::DEV_BOOLEAN;
        }
        break;

        case Tango::ATT_SHORT:
        {
            const Tango::DevVarShortArray &tmp_seq = attr_value->value.short_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_sh = (const_cast<Tango::DevVarShortArray &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->ShortSeq = new Tango::DevVarShortArray(max, len, tmp_sh, true);
            }
            else
            {
                tmp_sh = const_cast<CORBA::Short *>(tmp_seq.get_buffer());
                dev_attr->ShortSeq = new Tango::DevVarShortArray(max, len, tmp_sh, false);
            }
            dev_attr->data_type = Tango::DEV_SHORT;
        }
        break;

        case Tango::ATT_LONG:
        {
            const Tango::DevVarLongArray &tmp_seq = attr_value->value.long_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_lo = (const_cast<Tango::DevVarLongArray &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->LongSeq = new Tango::DevVarLongArray(max, len, tmp_lo, true);
            }
            else
            {
                tmp_lo = const_cast<CORBA::Long *>(tmp_seq.get_buffer());
                dev_attr->LongSeq = new Tango::DevVarLongArray(max, len, tmp_lo, false);
            }
            dev_attr->data_type = Tango::DEV_LONG;
        }
        break;

        case Tango::ATT_LONG64:
        {
            const Tango::DevVarLong64Array &tmp_seq = attr_value->value.long64_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_lolo = (const_cast<Tango::DevVarLong64Array &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->Long64Seq = new Tango::DevVarLong64Array(max, len, tmp_lolo, true);
            }
            else
            {
                tmp_lolo = const_cast<CORBA::LongLong *>(tmp_seq.get_buffer());
                dev_attr->Long64Seq = new Tango::DevVarLong64Array(max, len, tmp_lolo, false);
            }
            dev_attr->data_type = Tango::DEV_LONG64;
        }
        break;

        case Tango::ATT_FLOAT:
        {
            const Tango::DevVarFloatArray &tmp_seq = attr_value->value.float_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_fl = (const_cast<Tango::DevVarFloatArray &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->FloatSeq = new Tango::DevVarFloatArray(max, len, tmp_fl, true);
            }
            else
            {
                tmp_fl = const_cast<CORBA::Float *>(tmp_seq.get_buffer());
                dev_attr->FloatSeq = new Tango::DevVarFloatArray(max, len, tmp_fl, false);
            }
            dev_attr->data_type = Tango::DEV_FLOAT;
        }
        break;

        case Tango::ATT_DOUBLE:
        {
            const Tango::DevVarDoubleArray &tmp_seq = attr_value->value.double_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_db = (const_cast<Tango::DevVarDoubleArray &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->DoubleSeq = new Tango::DevVarDoubleArray(max, len, tmp_db, true);
            }
            else
            {
                tmp_db = const_cast<CORBA::Double *>(tmp_seq.get_buffer());
                dev_attr->DoubleSeq = new Tango::DevVarDoubleArray(max, len, tmp_db, false);
            }
            dev_attr->data_type = Tango::DEV_DOUBLE;
        }
        break;

        case Tango::ATT_UCHAR:
        {
            const Tango::DevVarCharArray &tmp_seq = attr_value->value.uchar_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_uch = (const_cast<Tango::DevVarCharArray &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->UCharSeq = new Tango::DevVarCharArray(max, len, tmp_uch, true);
            }
            else
            {
                tmp_uch = const_cast<CORBA::Octet *>(tmp_seq.get_buffer());
                dev_attr->UCharSeq = new Tango::DevVarCharArray(max, len, tmp_uch, false);
            }
            dev_attr->data_type = Tango::DEV_UCHAR;
        }
        break;

        case Tango::ATT_USHORT:
        {
            const Tango::DevVarUShortArray &tmp_seq = attr_value->value.ushort_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_ush = (const_cast<Tango::DevVarUShortArray &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->UShortSeq = new Tango::DevVarUShortArray(max, len, tmp_ush, true);
            }
            else
            {
                tmp_ush = const_cast<CORBA::UShort *>(tmp_seq.get_buffer());
                dev_attr->UShortSeq = new Tango::DevVarUShortArray(max, len, tmp_ush, false);
            }
            dev_attr->data_type = Tango::DEV_USHORT;
        }
        break;

        case Tango::ATT_ULONG:
        {
            const Tango::DevVarULongArray &tmp_seq = attr_value->value.ulong_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_ulo = (const_cast<Tango::DevVarULongArray &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->ULongSeq = new Tango::DevVarULongArray(max, len, tmp_ulo, true);
            }
            else
            {
                tmp_ulo = const_cast<CORBA::ULong *>(tmp_seq.get_buffer());
                dev_attr->ULongSeq = new Tango::DevVarULongArray(max, len, tmp_ulo, false);
            }
            dev_attr->data_type = Tango::DEV_ULONG;
        }
        break;

        case Tango::ATT_ULONG64:
        {
            const Tango::DevVarULong64Array &tmp_seq = attr_value->value.ulong64_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_ulolo = (const_cast<Tango::DevVarULong64Array &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->ULong64Seq = new Tango::DevVarULong64Array(max, len, tmp_ulolo, true);
            }
            else
            {
                tmp_ulolo = const_cast<CORBA::ULongLong *>(tmp_seq.get_buffer());
                dev_attr->ULong64Seq = new Tango::DevVarULong64Array(max, len, tmp_ulolo, false);
            }
            dev_attr->data_type = Tango::DEV_ULONG64;
        }
        break;

        case Tango::ATT_STRING:
        {
            const Tango::DevVarStringArray &tmp_seq = attr_value->value.string_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_str = (const_cast<Tango::DevVarStringArray &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->StringSeq = new Tango::DevVarStringArray(max, len, tmp_str, true);
            }
            else
            {
                tmp_str = const_cast<char **>(tmp_seq.get_buffer());
                dev_attr->StringSeq = new Tango::DevVarStringArray(max, len, tmp_str, false);
            }
            dev_attr->data_type = Tango::DEV_STRING;
        }
        break;

        case Tango::ATT_STATE:
        {
            const Tango::DevVarStateArray &tmp_seq = attr_value->value.state_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_state = (const_cast<Tango::DevVarStateArray &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->StateSeq = new Tango::DevVarStateArray(max, len, tmp_state, true);
            }
            else
            {
                tmp_state = const_cast<Tango::DevState *>(tmp_seq.get_buffer());
                dev_attr->StateSeq = new Tango::DevVarStateArray(max, len, tmp_state, false);
            }
            dev_attr->data_type = Tango::DEV_STATE;
        }
        break;

        case Tango::DEVICE_STATE:
        {
            dev_attr->d_state = attr_value->value.dev_state_att();
            dev_attr->d_state_filled = true;
            dev_attr->data_type = Tango::DEV_STATE;
        }
        break;

        case Tango::ATT_ENCODED:
        {
            const Tango::DevVarEncodedArray &tmp_seq = attr_value->value.encoded_att_value();
            max = tmp_seq.maximum();
            len = tmp_seq.length();
            if(tmp_seq.release())
            {
                tmp_enc = (const_cast<Tango::DevVarEncodedArray &>(tmp_seq)).get_buffer((CORBA::Boolean) true);
                dev_attr->EncodedSeq = new Tango::DevVarEncodedArray(max, len, tmp_enc, true);
            }
            else
            {
                tmp_enc = const_cast<Tango::DevEncoded *>(tmp_seq.get_buffer());
                dev_attr->EncodedSeq = new Tango::DevVarEncodedArray(max, len, tmp_enc, false);
            }
            dev_attr->data_type = Tango::DEV_ENCODED;
        }
        break;

        case Tango::ATT_NO_DATA:
        {
            dev_attr->data_type = Tango::DATA_TYPE_UNKNOWN;
        }
        break;

        default:
            dev_attr->data_type = Tango::DATA_TYPE_UNKNOWN;
        }
    }
}
} // anonymous namespace

namespace Tango
{

ApiUtil *ApiUtil::_instance = nullptr;
omni_mutex ApiUtil::inst_mutex;

ApiUtil *ApiUtil::instance()
{
    omni_mutex_lock lo(inst_mutex);

    if(_instance == nullptr)
    {
        _instance = new ApiUtil();
    }
    return _instance;
}

void ApiUtil::cleanup()
{
    if(_instance != nullptr)
    {
        delete _instance;
        _instance = nullptr;
    }
}

bool ApiUtil::is_started()
{
    omni_mutex_lock lo(inst_mutex);

    return _instance != nullptr;
}

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::ApiUtil()
//
// description :
//        Constructor of the ApiUtil class.
//
//------------------------------------------------------------------------------------------------------------------

ApiUtil::ApiUtil() :

    ext(new ApiUtilExt),

    cl_pid(0)

{
    _orb = CORBA::ORB::_nil();

    //
    // Check if it is created from a device server
    //

    in_serv = Util::_constructed;

    //
    // Create the Asynchronous polling request Id generator
    //

    UniqIdent *ui = new UniqIdent();

    //
    // Create the table used to store asynchronous polling requests
    //

    asyn_p_table = new AsynReq(ui);

    //
    // Set the asynchronous callback mode to "ON_CALL"
    //

    auto_cb = PULL_CALLBACK;
    cb_thread_ptr = nullptr;

    //
    // Get the process PID
    //

#ifdef _TG_WINDOWS_
    cl_pid = _getpid();
#else
    cl_pid = getpid();
#endif

    //
    // Check if the user has defined his own connection timeout
    //

    std::string var;
    if(get_env_var("TANGOconnectTimeout", var) == 0)
    {
        int user_to = -1;
        std::istringstream iss(var);
        iss >> user_to;
        if(iss)
        {
            user_connect_timeout = user_to;
        }
    }

    //
    // Check if the user has defined his own subscriber hwm (fpr zmq event tuning)
    //

    var.clear();
    if(get_env_var("TANGO_EVENT_BUFFER_HWM", var) == 0)
    {
        int sub_hwm = -1;
        std::istringstream iss(var);
        iss >> sub_hwm;
        if(iss)
        {
            user_sub_hwm = sub_hwm;
        }
    }

#ifndef _TG_WINDOWS_
    // We are using a static variable to ensure that this is installed only once.
    [[maybe_unused]] static bool fork_handler_installed = (
        pthread_atfork(
            +[](){ // prepare
                ApiUtil::inst_mutex.lock();
            },
            +[](){ // parent
                ApiUtil::inst_mutex.unlock();
            },
            +[](){ // child
                // We are deliberately leaking here as we cannot call the
                // ApiUtil dtor in the child process as in the child process
                // we do not have the other threads that have potentially been
                // spawned by the ApiUtil which we would wait for in the dtor.
                //
                // This is not ideal, as we potentially leak open sockets etc.,
                // however, this allows the child to use Tango after the fork()
                // if the parent was _only_ a Tango client _before_ the fork().
                // This is needed by pytango's MultiDeviceTestContext when
                // using the multiprocess backend (process=True).
                ApiUtil::_instance = nullptr;
                ApiUtil::inst_mutex.unlock();
            }
        ), true);
#endif
}

//+----------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::~ApiUtil()
//
// description :
//        Destructor of the ApiUtil class.
//
//------------------------------------------------------------------------------------------------------------------

ApiUtil::~ApiUtil()
{
    //
    // Release Asyn stuff
    //

    shutdown_cb_thread();
    delete asyn_p_table;
    asyn_p_table = nullptr;

    //
    // Kill any remaining locking threads
    //

    clean_locking_threads();

    //
    // Release event stuff (in case it is necessary)
    //

    bool event_was_used = false;

    if(ext != nullptr)
    {
        if((get_notifd_event_consumer() != nullptr) || (get_zmq_event_consumer() != nullptr))
        {
            event_was_used = true;
            leavefunc();
        }
    }

    //
    // Delete the database object
    //

    for(unsigned int i = 0; i < db_vect.size(); i++)
    {
        if(in_serv && i == 0)
        {
            Util *tg = Util::instance();
            if(tg->use_db())
            {
                // The first database belongs to the Util singleton when we are in a
                // server and the server has a database.
                continue;
            }
        }

        delete db_vect[i];
    }
    db_vect.clear();

    //
    // Properly shutdown the ORB
    //

    if((!in_serv) && (!CORBA::is_nil(_orb)))
    {
        if(!event_was_used)
        {
            try
            {
                _orb->destroy();
            }
            catch(...)
            {
            }
        }
    }
}

//------------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::set_sig_handler()
//
// description :
//        Install a signal handler for SIGINT and SIGTERM but only if nothing is already installed
//
//-------------------------------------------------------------------------------------------------------------------

void ApiUtil::set_sig_handler()
{
#ifndef _TG_WINDOWS_
    if(!in_serv)
    {
        struct sigaction sa, old_action;

        sa.sa_handler = _t_handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART;

        if(sigaction(SIGTERM, nullptr, &old_action) != -1)
        {
            if(old_action.sa_handler == nullptr)
            {
                sigaction(SIGTERM, &sa, nullptr);
            }
        }

        if(sigaction(SIGINT, nullptr, &old_action) != -1)
        {
            if(old_action.sa_handler == nullptr)
            {
                sigaction(SIGINT, &sa, nullptr);
            }
        }
    }
#endif
}

//------------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::create_orb()
//
// description :
//        Create the CORBA orb object
//
//-------------------------------------------------------------------------------------------------------------------

void ApiUtil::create_orb()
{
    int _argc;
    char **_argv;

    //
    // pass dummy arguments to init() because we don't have access to argc and argv
    //

    _argc = 1;
    _argv = (char **) malloc(sizeof(char *));
    _argv[0] = (char *) "dummy";

    //
    // Get user signal handler for SIGPIPE (ORB_init call install a SIG_IGN for SIGPIPE. This could be annoying in case
    // the user uses SIGPIPE)
    //

#ifndef _TG_WINDOWS_
    struct sigaction sa;
    sa.sa_handler = nullptr;

    if(sigaction(SIGPIPE, nullptr, &sa) == -1)
    {
        sa.sa_handler = nullptr;
    }
#endif

    // Init the ORB

    const char *options[][2] = {{"clientCallTimeOutPeriod", CLNT_TIMEOUT_STR},
                                {"verifyObjectExistsAndType", "0"},
                                {"maxGIOPConnectionPerServer", MAX_GIOP_PER_SERVER},
                                {"giopMaxMsgSize", MAX_TRANSFER_SIZE},
                                {"throwTransientOnTimeOut", "1"},
                                {"exceptionIdInAny", "0"},
                                {nullptr, nullptr}};

    _orb = CORBA::ORB_init(_argc, _argv, "omniORB4", options);

    free(_argv);

    //
    // Restore SIGPIPE handler
    //

#ifndef _TG_WINDOWS_
    if(sa.sa_handler != nullptr)
    {
        struct sigaction sb;

        sb = sa;

        if(sigaction(SIGPIPE, &sb, nullptr) == -1)
        {
            std::cerr << "Can re-install user signal handler for SIGPIPE!" << std::endl;
        }
    }
#endif
}

//---------------------------------------------------------------------------------------------------------------------
//
// method :
//         ApiUtil::get_db_ind()
//
// description :
//        Retrieve a Tango database object created from the TANGO_HOST environment variable
//
//--------------------------------------------------------------------------------------------------------------------

int ApiUtil::get_db_ind()
{
    omni_mutex_lock lo(the_mutex);

    for(unsigned int i = 0; i < db_vect.size(); i++)
    {
        if(db_vect[i]->get_from_env_var())
        {
            return i;
        }
    }

    //
    // The database object has not been found, create it
    //

    db_vect.push_back(new Database());

    return (db_vect.size() - 1);
}

int ApiUtil::get_db_ind(const std::string &host, int port)
{
    omni_mutex_lock lo(the_mutex);

    for(unsigned int i = 0; i < db_vect.size(); i++)
    {
        if(db_vect[i]->get_db_port_num() == port)
        {
            if(db_vect[i]->get_db_host() == host)
            {
                return i;
            }
        }
    }

    //
    // The database object has not been found, create it
    //

    db_vect.push_back(new Database(host, port));

    return (db_vect.size() - 1);
}

//-------------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::get_asynch_replies()
//
// description :
//        Try to obtain data returned by a command asynchronously requested. This method does not block if the reply is
//        not yet arrived. Fire callback for replies already arrived
//
//------------------------------------------------------------------------------------------------------------------

void ApiUtil::get_asynch_replies()
{
    //
    // First get all replies from ORB buffers
    //

    try
    {
        while(_orb->poll_next_response())
        {
            CORBA::Request_ptr req;
            _orb->get_next_response(req);

            //
            // Retrieve this request in the cb request map and mark it as "arrived" in both maps
            //

            TgRequest &tg_req = asyn_p_table->get_request(req);

            tg_req.arrived = true;
            asyn_p_table->mark_as_arrived(req);

            //
            // Process request
            //

            process_request(tg_req.dev, tg_req, req);
        }
    }
    catch(CORBA::BAD_INV_ORDER &e)
    {
        if(e.minor() != omni::BAD_INV_ORDER_RequestNotSentYet)
        {
            throw;
        }
    }

    //
    // For all replies already there
    //

    std::multimap<Connection *, TgRequest>::iterator pos;
    std::multimap<Connection *, TgRequest> &req_table = asyn_p_table->get_cb_dev_table();

    for(pos = req_table.begin(); pos != req_table.end(); ++pos)
    {
        if(pos->second.arrived)
        {
            process_request(pos->first, pos->second, pos->second.request);
        }
    }
}

void ApiUtil::process_request(Connection *connection, const TgRequest &tg_req, CORBA::Request_ptr &req)
{
    switch(tg_req.req_type)
    {
    case TgRequest::CMD_INOUT:
        connection->Cb_Cmd_Request(req, tg_req.cb_ptr);
        break;

    case TgRequest::READ_ATTR:
        connection->Cb_ReadAttr_Request(req, tg_req.cb_ptr);
        break;

    case TgRequest::WRITE_ATTR:
    case TgRequest::WRITE_ATTR_SINGLE:
        connection->Cb_WriteAttr_Request(req, tg_req.cb_ptr);
        break;
    }
    connection->dec_asynch_counter(CALL_BACK);
    asyn_p_table->remove_request(connection, req);
}

//------------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::get_asynch_replies()
//
// description :
//        Try to obtain data returned by a command asynchronously requested. This method does not block if the reply is
//        not yet arrived. Fire callback for replies already arrived
//
// arg(s) :
//        in :
//            - call_timeout : The timeout value in mS
//
//-----------------------------------------------------------------------------

void ApiUtil::get_asynch_replies(long call_timeout)
{
    //
    // For all replies already there
    //

    std::multimap<Connection *, TgRequest>::iterator pos;
    std::multimap<Connection *, TgRequest> &req_table = asyn_p_table->get_cb_dev_table();

    for(pos = req_table.begin(); pos != req_table.end(); ++pos)
    {
        if(pos->second.arrived)
        {
            switch(pos->second.req_type)
            {
            case TgRequest::CMD_INOUT:
                pos->first->Cb_Cmd_Request(pos->second.request, pos->second.cb_ptr);
                break;

            case TgRequest::READ_ATTR:
                pos->first->Cb_ReadAttr_Request(pos->second.request, pos->second.cb_ptr);
                break;

            case TgRequest::WRITE_ATTR:
            case TgRequest::WRITE_ATTR_SINGLE:
                pos->first->Cb_WriteAttr_Request(pos->second.request, pos->second.cb_ptr);
                break;
            }
            pos->first->dec_asynch_counter(CALL_BACK);
            asyn_p_table->remove_request(pos->first, pos->second.request);
        }
    }

    //
    // If they are requests already sent but without being replied yet
    //

    if(asyn_p_table->get_cb_request_nb() != 0)
    {
        CORBA::Request_ptr req;

        if(call_timeout != 0)
        {
            //
            // A timeout has been specified. Wait if there are still request without replies but not more than the
            // specified timeout. Leave method if the timeout is not arrived but there is no more request without reply
            //

            long nb = call_timeout / 20;

            while((nb > 0) && (asyn_p_table->get_cb_request_nb() != 0))
            {
                std::this_thread::sleep_for(std::chrono::milliseconds(20));
                nb--;

                try
                {
                    if(_orb->poll_next_response())
                    {
                        _orb->get_next_response(req);

                        //
                        // Retrieve this request in the cb request map and mark it as "arrived" in both maps
                        //

                        TgRequest &tg_req = asyn_p_table->get_request(req);

                        tg_req.arrived = true;
                        asyn_p_table->mark_as_arrived(req);

                        //
                        // Is it a request for our device, process it ?
                        //

                        switch(tg_req.req_type)
                        {
                        case TgRequest::CMD_INOUT:
                            tg_req.dev->Cb_Cmd_Request(req, tg_req.cb_ptr);
                            break;

                        case TgRequest::READ_ATTR:
                            tg_req.dev->Cb_ReadAttr_Request(req, tg_req.cb_ptr);
                            break;

                        case TgRequest::WRITE_ATTR:
                        case TgRequest::WRITE_ATTR_SINGLE:
                            tg_req.dev->Cb_WriteAttr_Request(req, tg_req.cb_ptr);
                            break;
                        }

                        tg_req.dev->dec_asynch_counter(CALL_BACK);
                        asyn_p_table->remove_request(tg_req.dev, req);
                    }
                }
                catch(CORBA::BAD_INV_ORDER &e)
                {
                    if(e.minor() != omni::BAD_INV_ORDER_RequestNotSentYet)
                    {
                        throw;
                    }
                }
            }

            //
            // Throw exception if the timeout has expired but there are still request without replies
            //

            if((nb == 0) && (asyn_p_table->get_cb_request_nb() != 0))
            {
                TangoSys_OMemStream desc;
                desc << "Still some reply(ies) for asynchronous callback call(s) to be received" << std::ends;
                TANGO_THROW_DETAILED_EXCEPTION(ApiAsynNotThereExcept, API_AsynReplyNotArrived, desc.str());
            }
        }
        else
        {
            //
            // If timeout is set to 0, this means wait until all the requests sent to this device has sent their replies
            //

            while(asyn_p_table->get_cb_request_nb() != 0)
            {
                try
                {
                    _orb->get_next_response(req);

                    //
                    // Retrieve this request in the cb request map and mark it as "arrived" in both maps
                    //

                    TgRequest &tg_req = asyn_p_table->get_request(req);

                    tg_req.arrived = true;
                    asyn_p_table->mark_as_arrived(req);

                    //
                    // Process the reply
                    //

                    switch(tg_req.req_type)
                    {
                    case TgRequest::CMD_INOUT:
                        tg_req.dev->Cb_Cmd_Request(req, tg_req.cb_ptr);
                        break;

                    case TgRequest::READ_ATTR:
                        tg_req.dev->Cb_ReadAttr_Request(req, tg_req.cb_ptr);
                        break;

                    case TgRequest::WRITE_ATTR:
                    case TgRequest::WRITE_ATTR_SINGLE:
                        tg_req.dev->Cb_WriteAttr_Request(req, tg_req.cb_ptr);
                        break;
                    }

                    tg_req.dev->dec_asynch_counter(CALL_BACK);
                    asyn_p_table->remove_request(tg_req.dev, req);
                }
                catch(CORBA::BAD_INV_ORDER &e)
                {
                    if(e.minor() != omni::BAD_INV_ORDER_RequestNotSentYet)
                    {
                        throw;
                    }
                }
            }
        }
    }
}

//-------------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::set_asynch_cb_sub_model()
//
// description :
//        Set the callback automatic mode (Fired by dedicated call or automatically fired by a separate thread)
//
// arg(s) :
//        in :
//            - mode : The new callback mode
//
//--------------------------------------------------------------------------------------------------------------------

void ApiUtil::set_asynch_cb_sub_model(cb_sub_model mode)
{
    if(auto_cb == PULL_CALLBACK)
    {
        if(mode == PUSH_CALLBACK)
        {
            shutdown_cb_thread();

            asyn_p_table->start_thread();
            cb_thread_ptr = new CallBackThread(asyn_p_table);
            cb_thread_ptr->start();
            auto_cb = PUSH_CALLBACK;
        }
    }
    else
    {
        if(mode == PULL_CALLBACK)
        {
            //
            // Ask the thread to stop and to exit
            //

            asyn_p_table->request_shutdown();
            auto_cb = PULL_CALLBACK;
        }
    }
}

//-----------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::create_xxx_event_consumer()
//
// description :
//        Create the event consumer. This will automatically start a new thread which is waiting in a CORBA::run()
//        indefintely for events. It will then trigger the events.
//
//----------------------------------------------------------------------------------------------------------------

PointerWithLock<EventConsumer> ApiUtil::create_notifd_event_consumer()
{
    if(!is_notifd_event_consumer_created())
    {
        WriterLock lock{notifd_rw_lock};
        notifd_event_consumer = new NotifdEventConsumer(this);
    }

    return get_notifd_event_consumer();
}

PointerWithLock<EventConsumer> ApiUtil::create_zmq_event_consumer()
{
    if(!is_zmq_event_consumer_created())
    {
        WriterLock lock{zmq_rw_lock};
        zmq_event_consumer = new ZmqEventConsumer(this);
    }

    return get_zmq_event_consumer();
}

PointerWithLock<EventConsumer> ApiUtil::get_notifd_event_consumer()
{
    return PointerWithLock<EventConsumer>{notifd_event_consumer, notifd_rw_lock};
}

PointerWithLock<EventConsumer> ApiUtil::get_zmq_event_consumer()
{
    return PointerWithLock<EventConsumer>{zmq_event_consumer, zmq_rw_lock};
}

bool ApiUtil::is_notifd_event_consumer_created()
{
    return get_notifd_event_consumer() != nullptr;
}

bool ApiUtil::is_zmq_event_consumer_created()
{
    return get_zmq_event_consumer() != nullptr;
}

// The caller is required to hold the read lock on the event consumer behind ptr
PointerWithLock<ZmqEventConsumer> ApiUtil::get_zmq_event_consumer_derived(EventConsumer *ptr)
{
    if(ptr == nullptr)
    {
        TANGO_THROW_EXCEPTION(API_InvalidArgs, "Can't handle nullptr");
    }

    {
        ReaderLock lock{zmq_rw_lock};

        if(zmq_event_consumer != nullptr && dynamic_cast<EventConsumer *>(zmq_event_consumer) == ptr)
        {
            return PointerWithLock<ZmqEventConsumer>(zmq_event_consumer, zmq_rw_lock);
        }
    }

    TANGO_THROW_EXCEPTION(API_InvalidArgs, "Could not find event consumer for ptr");
}

// The caller is required to hold the read lock on the event consumer behind ptr
PointerWithLock<EventConsumer> ApiUtil::get_locked_event_consumer(EventConsumer *ptr)
{
    if(ptr == nullptr)
    {
        TANGO_THROW_EXCEPTION(API_InvalidArgs, "Can't handle nullptr");
    }

    {
        ReaderLock lock{zmq_rw_lock};

        if(zmq_event_consumer != nullptr && dynamic_cast<EventConsumer *>(zmq_event_consumer) == ptr)
        {
            return PointerWithLock<EventConsumer>(zmq_event_consumer, zmq_rw_lock);
        }
    }

    {
        ReaderLock lock{notifd_rw_lock};

        if(notifd_event_consumer != nullptr && dynamic_cast<EventConsumer *>(notifd_event_consumer) == ptr)
        {
            return PointerWithLock<EventConsumer>(notifd_event_consumer, notifd_rw_lock);
        }
    }

    TANGO_THROW_EXCEPTION(API_InvalidArgs, "Could not find event consumer for ptr");
}

void ApiUtil::shutdown_event_consumers()
{
    {
        WriterLock lock{notifd_rw_lock};

        if(notifd_event_consumer != nullptr)
        {
            notifd_event_consumer->shutdown();

            //
            // Shut-down the notifd ORB and wait for the thread to exit
            //

            int *rv;
            notifd_event_consumer->orb_->shutdown(true);
            notifd_event_consumer->join((void **) &rv);
            notifd_event_consumer = nullptr;
        }
    }

    {
        WriterLock lock{zmq_rw_lock};

        if(zmq_event_consumer != nullptr)
        {
            int *rv;
            zmq_event_consumer->shutdown();
            zmq_event_consumer->join((void **) &rv);
            zmq_event_consumer = nullptr;
        }
    }

    EventConsumer::reset_static_data();
}

void ApiUtil::shutdown_cb_thread()
{
    if(cb_thread_ptr != nullptr)
    {
        asyn_p_table->request_shutdown();
        cb_thread_ptr->join(nullptr);
        cb_thread_ptr = nullptr;
    }
}

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::query_event_system()
//
// description :
//        Query debugging information about the event system
//
// returns :
//        The information about the ZMQ event consumer and event supplier in a JSON string
//
//------------------------------------------------------------------------------------------------------------------
std::string ApiUtil::query_event_system()
{
    TANGO_LOG_DEBUG << "ApiUtil::query_event_system()" << std::endl;
    return detail::query_event_system();
}

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::enable_event_system_perf_mon()
//
// description :
//        Enable or disable collection of performance counters for consumer and supplier
//
// argument :
//        in :
//          - enabled : If true, enable sampling otherwise disable sampling
//
//------------------------------------------------------------------------------------------------------------------
void ApiUtil::enable_event_system_perf_mon(Tango::DevBoolean flag)
{
    TANGO_LOG_DEBUG << "ApiUtil::enable_event_system_perf_mon(" << flag << ")" << std::endl;
    detail::enable_event_system_perf_mon(flag);
}

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::clean_locking_threads()
//
// description :
//        Ask all remaining locking threads to exit
//
// args :
//        in :
//            - clean :
//
//------------------------------------------------------------------------------------------------------------------

void ApiUtil::clean_locking_threads(bool clean)
{
    omni_mutex_lock oml(lock_th_map);

    if(!lock_threads.empty())
    {
        std::map<std::string, LockingThread>::iterator pos;
        for(pos = lock_threads.begin(); pos != lock_threads.end(); ++pos)
        {
            if(pos->second.shared->suicide)
            {
                delete pos->second.shared;
                delete pos->second.mon;
            }
            else
            {
                {
                    omni_mutex_lock sync(*(pos->second.mon));

                    pos->second.shared->cmd_pending = true;
                    if(clean)
                    {
                        pos->second.shared->cmd_code = LOCK_UNLOCK_ALL_EXIT;
                    }
                    else
                    {
                        pos->second.shared->cmd_code = LOCK_EXIT;
                    }

                    pos->second.mon->signal();

                    TANGO_LOG_DEBUG << "Cmd sent to locking thread" << std::endl;

                    if(pos->second.shared->cmd_pending)
                    {
                        pos->second.mon->wait(DEFAULT_TIMEOUT);
                    }
                }
                delete pos->second.shared;
                delete pos->second.mon;
            }
        }

        if(!clean)
        {
            lock_threads.clear();
        }
    }
}

//-----------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::attr_to_device()
//
// description :
//
//
//-------------------------------------------------------------------------------------------------------------------

void ApiUtil::attr_to_device(const AttributeValue *attr_value,
                             const AttributeValue_3 *attr_value_3,
                             long vers,
                             DeviceAttribute *dev_attr)
{
    const DevVarLongArray *tmp_seq_lo;
    CORBA::Long *tmp_lo;
    const DevVarShortArray *tmp_seq_sh;
    CORBA::Short *tmp_sh;
    const DevVarDoubleArray *tmp_seq_db;
    CORBA::Double *tmp_db;
    const DevVarStringArray *tmp_seq_str;
    char **tmp_str;
    const DevVarFloatArray *tmp_seq_fl;
    CORBA::Float *tmp_fl;
    const DevVarBooleanArray *tmp_seq_boo;
    CORBA::Boolean *tmp_boo;
    const DevVarUShortArray *tmp_seq_ush;
    CORBA::UShort *tmp_ush;
    const DevVarCharArray *tmp_seq_uch;
    CORBA::Octet *tmp_uch;
    const DevVarLong64Array *tmp_seq_64;
    CORBA::LongLong *tmp_lolo;
    const DevVarULongArray *tmp_seq_ulo;
    CORBA::ULong *tmp_ulo;
    const DevVarULong64Array *tmp_seq_u64;
    CORBA::ULongLong *tmp_ulolo;
    const DevVarStateArray *tmp_seq_state;
    Tango::DevState *tmp_state;

    CORBA::ULong max, len;

    if(vers == 3)
    {
        dev_attr->name = attr_value_3->name;
        dev_attr->quality = attr_value_3->quality;
        dev_attr->time = attr_value_3->time;
        dev_attr->dim_x = attr_value_3->r_dim.dim_x;
        dev_attr->dim_y = attr_value_3->r_dim.dim_y;
        dev_attr->set_w_dim_x(attr_value_3->w_dim.dim_x);
        dev_attr->set_w_dim_y(attr_value_3->w_dim.dim_y);
        dev_attr->err_list = new DevErrorList(attr_value_3->err_list);
    }
    else
    {
        dev_attr->name = attr_value->name;
        dev_attr->quality = attr_value->quality;
        dev_attr->time = attr_value->time;
        dev_attr->dim_x = attr_value->dim_x;
        dev_attr->dim_y = attr_value->dim_y;
    }

    if(dev_attr->quality != Tango::ATTR_INVALID)
    {
        CORBA::TypeCode_var ty;
        if(vers == 3)
        {
            ty = attr_value_3->value.type();
        }
        else
        {
            ty = attr_value->value.type();
        }

        if(ty->kind() == CORBA::tk_enum)
        {
            dev_attr->data_type = Tango::DEV_STATE;
            attr_value_3->value >>= dev_attr->d_state;
            dev_attr->d_state_filled = true;
            return;
        }

        CORBA::TypeCode_var ty_alias = ty->content_type();
        CORBA::TypeCode_var ty_seq = ty_alias->content_type();
        switch(ty_seq->kind())
        {
        case CORBA::tk_long:
            dev_attr->data_type = Tango::DEV_LONG;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_lo;
            }
            else
            {
                attr_value->value >>= tmp_seq_lo;
            }
            max = tmp_seq_lo->maximum();
            len = tmp_seq_lo->length();
            if(tmp_seq_lo->release())
            {
                tmp_lo = (const_cast<DevVarLongArray *>(tmp_seq_lo))->get_buffer((CORBA::Boolean) true);
                dev_attr->LongSeq = new DevVarLongArray(max, len, tmp_lo, true);
            }
            else
            {
                tmp_lo = const_cast<CORBA::Long *>(tmp_seq_lo->get_buffer());
                dev_attr->LongSeq = new DevVarLongArray(max, len, tmp_lo, false);
            }
            break;

        case CORBA::tk_longlong:
            dev_attr->data_type = Tango::DEV_LONG64;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_64;
            }
            else
            {
                attr_value->value >>= tmp_seq_64;
            }
            max = tmp_seq_64->maximum();
            len = tmp_seq_64->length();
            if(tmp_seq_64->release())
            {
                tmp_lolo = (const_cast<DevVarLong64Array *>(tmp_seq_64))->get_buffer((CORBA::Boolean) true);
                dev_attr->Long64Seq = new DevVarLong64Array(max, len, tmp_lolo, true);
            }
            else
            {
                tmp_lolo = const_cast<CORBA::LongLong *>(tmp_seq_64->get_buffer());
                dev_attr->Long64Seq = new DevVarLong64Array(max, len, tmp_lolo, false);
            }
            break;

        case CORBA::tk_short:
            dev_attr->data_type = Tango::DEV_SHORT;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_sh;
            }
            else
            {
                attr_value->value >>= tmp_seq_sh;
            }
            max = tmp_seq_sh->maximum();
            len = tmp_seq_sh->length();
            if(tmp_seq_sh->release())
            {
                tmp_sh = (const_cast<DevVarShortArray *>(tmp_seq_sh))->get_buffer((CORBA::Boolean) true);
                dev_attr->ShortSeq = new DevVarShortArray(max, len, tmp_sh, true);
            }
            else
            {
                tmp_sh = const_cast<CORBA::Short *>(tmp_seq_sh->get_buffer());
                dev_attr->ShortSeq = new DevVarShortArray(max, len, tmp_sh, false);
            }
            break;

        case CORBA::tk_double:
            dev_attr->data_type = Tango::DEV_DOUBLE;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_db;
            }
            else
            {
                attr_value->value >>= tmp_seq_db;
            }
            max = tmp_seq_db->maximum();
            len = tmp_seq_db->length();
            if(tmp_seq_db->release())
            {
                tmp_db = (const_cast<DevVarDoubleArray *>(tmp_seq_db))->get_buffer((CORBA::Boolean) true);
                dev_attr->DoubleSeq = new DevVarDoubleArray(max, len, tmp_db, true);
            }
            else
            {
                tmp_db = const_cast<CORBA::Double *>(tmp_seq_db->get_buffer());
                dev_attr->DoubleSeq = new DevVarDoubleArray(max, len, tmp_db, false);
            }
            break;

        case CORBA::tk_string:
            dev_attr->data_type = Tango::DEV_STRING;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_str;
            }
            else
            {
                attr_value->value >>= tmp_seq_str;
            }
            max = tmp_seq_str->maximum();
            len = tmp_seq_str->length();
            if(tmp_seq_str->release())
            {
                tmp_str = (const_cast<DevVarStringArray *>(tmp_seq_str))->get_buffer((CORBA::Boolean) true);
                dev_attr->StringSeq = new DevVarStringArray(max, len, tmp_str, true);
            }
            else
            {
                tmp_str = const_cast<char **>(tmp_seq_str->get_buffer());
                dev_attr->StringSeq = new DevVarStringArray(max, len, tmp_str, false);
            }
            break;

        case CORBA::tk_float:
            dev_attr->data_type = Tango::DEV_FLOAT;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_fl;
            }
            else
            {
                attr_value->value >>= tmp_seq_fl;
            }
            max = tmp_seq_fl->maximum();
            len = tmp_seq_fl->length();
            if(tmp_seq_fl->release())
            {
                tmp_fl = (const_cast<DevVarFloatArray *>(tmp_seq_fl))->get_buffer((CORBA::Boolean) true);
                dev_attr->FloatSeq = new DevVarFloatArray(max, len, tmp_fl, true);
            }
            else
            {
                tmp_fl = const_cast<CORBA::Float *>(tmp_seq_fl->get_buffer());
                dev_attr->FloatSeq = new DevVarFloatArray(max, len, tmp_fl, false);
            }
            break;

        case CORBA::tk_boolean:
            dev_attr->data_type = Tango::DEV_BOOLEAN;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_boo;
            }
            else
            {
                attr_value->value >>= tmp_seq_boo;
            }
            max = tmp_seq_boo->maximum();
            len = tmp_seq_boo->length();
            if(tmp_seq_boo->release())
            {
                tmp_boo = (const_cast<DevVarBooleanArray *>(tmp_seq_boo))->get_buffer((CORBA::Boolean) true);
                dev_attr->BooleanSeq = new DevVarBooleanArray(max, len, tmp_boo, true);
            }
            else
            {
                tmp_boo = const_cast<CORBA::Boolean *>(tmp_seq_boo->get_buffer());
                dev_attr->BooleanSeq = new DevVarBooleanArray(max, len, tmp_boo, false);
            }
            break;

        case CORBA::tk_ushort:
            dev_attr->data_type = Tango::DEV_USHORT;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_ush;
            }
            else
            {
                attr_value->value >>= tmp_seq_ush;
            }
            max = tmp_seq_ush->maximum();
            len = tmp_seq_ush->length();
            if(tmp_seq_ush->release())
            {
                tmp_ush = (const_cast<DevVarUShortArray *>(tmp_seq_ush))->get_buffer((CORBA::Boolean) true);
                dev_attr->UShortSeq = new DevVarUShortArray(max, len, tmp_ush, true);
            }
            else
            {
                tmp_ush = const_cast<CORBA::UShort *>(tmp_seq_ush->get_buffer());
                dev_attr->UShortSeq = new DevVarUShortArray(max, len, tmp_ush, false);
            }
            break;

        case CORBA::tk_octet:
            dev_attr->data_type = Tango::DEV_UCHAR;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_uch;
            }
            else
            {
                attr_value->value >>= tmp_seq_uch;
            }
            max = tmp_seq_uch->maximum();
            len = tmp_seq_uch->length();
            if(tmp_seq_uch->release())
            {
                tmp_uch = (const_cast<DevVarCharArray *>(tmp_seq_uch))->get_buffer((CORBA::Boolean) true);
                dev_attr->UCharSeq = new DevVarCharArray(max, len, tmp_uch, true);
            }
            else
            {
                tmp_uch = const_cast<CORBA::Octet *>(tmp_seq_uch->get_buffer());
                dev_attr->UCharSeq = new DevVarCharArray(max, len, tmp_uch, false);
            }
            break;

        case CORBA::tk_ulong:
            dev_attr->data_type = Tango::DEV_ULONG;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_ulo;
            }
            else
            {
                attr_value->value >>= tmp_seq_ulo;
            }
            max = tmp_seq_ulo->maximum();
            len = tmp_seq_ulo->length();
            if(tmp_seq_ulo->release())
            {
                tmp_ulo = (const_cast<DevVarULongArray *>(tmp_seq_ulo))->get_buffer((CORBA::Boolean) true);
                dev_attr->ULongSeq = new DevVarULongArray(max, len, tmp_ulo, true);
            }
            else
            {
                tmp_ulo = const_cast<CORBA::ULong *>(tmp_seq_ulo->get_buffer());
                dev_attr->ULongSeq = new DevVarULongArray(max, len, tmp_ulo, false);
            }
            break;

        case CORBA::tk_ulonglong:
            dev_attr->data_type = Tango::DEV_ULONG64;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_u64;
            }
            else
            {
                attr_value->value >>= tmp_seq_u64;
            }
            max = tmp_seq_u64->maximum();
            len = tmp_seq_u64->length();
            if(tmp_seq_u64->release())
            {
                tmp_ulolo = (const_cast<DevVarULong64Array *>(tmp_seq_u64))->get_buffer((CORBA::Boolean) true);
                dev_attr->ULong64Seq = new DevVarULong64Array(max, len, tmp_ulolo, true);
            }
            else
            {
                tmp_ulolo = const_cast<CORBA::ULongLong *>(tmp_seq_u64->get_buffer());
                dev_attr->ULong64Seq = new DevVarULong64Array(max, len, tmp_ulolo, false);
            }
            break;

        case CORBA::tk_enum:
            dev_attr->data_type = Tango::DEV_STATE;
            if(vers == 3)
            {
                attr_value_3->value >>= tmp_seq_state;
            }
            else
            {
                attr_value->value >>= tmp_seq_state;
            }
            max = tmp_seq_state->maximum();
            len = tmp_seq_state->length();
            if(tmp_seq_state->release())
            {
                tmp_state = (const_cast<DevVarStateArray *>(tmp_seq_state))->get_buffer((CORBA::Boolean) true);
                dev_attr->StateSeq = new DevVarStateArray(max, len, tmp_state, true);
            }
            else
            {
                tmp_state = const_cast<Tango::DevState *>(tmp_seq_state->get_buffer());
                dev_attr->StateSeq = new DevVarStateArray(max, len, tmp_state, false);
            }
            break;

        default:
            dev_attr->data_type = Tango::DATA_TYPE_UNKNOWN;
            TangoSys_OMemStream desc;
            desc << (vers == 3 ? "'attr_value_3" : "'attr_value") << "->value' with unexpected sequence kind '"
                 << ty_seq->kind() << "'.";
            TANGO_THROW_EXCEPTION(API_InvalidCorbaAny, desc.str().c_str());
        }
    }
}

void ApiUtil::attr_to_device(const AttributeValue_4 *attr_value_4, TANGO_UNUSED(long vers), DeviceAttribute *dev_attr)
{
    attr_to_device_base(attr_value_4, dev_attr);
}

void ApiUtil::attr_to_device(const AttributeValue_5 *attr_value_5, TANGO_UNUSED(long vers), DeviceAttribute *dev_attr)
{
    attr_to_device_base(attr_value_5, dev_attr);
    dev_attr->data_type = attr_value_5->data_type;
}

//------------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::device_to_attr()
//
// description :
//        initialize one AttributeValue instance from a DeviceAttribute one
//
// arg(s) :
//        in :
//            - dev_attr : The DeviceAttribute instance taken as source
//        out :
//            - att : The AttributeValue used as destination
//
//---------------------------------------------------------------------------------------------------------------

void ApiUtil::device_to_attr(const DeviceAttribute &dev_attr, AttributeValue_4 &att)
{
    att.name = dev_attr.name.c_str();
    att.quality = dev_attr.quality;
    att.time = dev_attr.time;
    att.w_dim.dim_x = dev_attr.dim_x;
    att.w_dim.dim_y = dev_attr.dim_y;
    att.data_format = Tango::FMT_UNKNOWN;

    if(dev_attr.LongSeq.operator->() != nullptr)
    {
        att.value.long_att_value(dev_attr.LongSeq.in());
    }
    else if(dev_attr.ShortSeq.operator->() != nullptr)
    {
        att.value.short_att_value(dev_attr.ShortSeq.in());
    }
    else if(dev_attr.DoubleSeq.operator->() != nullptr)
    {
        att.value.double_att_value(dev_attr.DoubleSeq.in());
    }
    else if(dev_attr.StringSeq.operator->() != nullptr)
    {
        att.value.string_att_value(dev_attr.StringSeq.in());
    }
    else if(dev_attr.FloatSeq.operator->() != nullptr)
    {
        att.value.float_att_value(dev_attr.FloatSeq.in());
    }
    else if(dev_attr.BooleanSeq.operator->() != nullptr)
    {
        att.value.bool_att_value(dev_attr.BooleanSeq.in());
    }
    else if(dev_attr.UShortSeq.operator->() != nullptr)
    {
        att.value.ushort_att_value(dev_attr.UShortSeq.in());
    }
    else if(dev_attr.UCharSeq.operator->() != nullptr)
    {
        att.value.uchar_att_value(dev_attr.UCharSeq.in());
    }
    else if(dev_attr.Long64Seq.operator->() != nullptr)
    {
        att.value.long64_att_value(dev_attr.Long64Seq.in());
    }
    else if(dev_attr.ULongSeq.operator->() != nullptr)
    {
        att.value.ulong_att_value(dev_attr.ULongSeq.in());
    }
    else if(dev_attr.ULong64Seq.operator->() != nullptr)
    {
        att.value.ulong64_att_value(dev_attr.ULong64Seq.in());
    }
    else if(dev_attr.StateSeq.operator->() != nullptr)
    {
        att.value.state_att_value(dev_attr.StateSeq.in());
    }
    else if(dev_attr.EncodedSeq.operator->() != nullptr)
    {
        att.value.encoded_att_value(dev_attr.EncodedSeq.in());
    }
}

void ApiUtil::device_to_attr(const DeviceAttribute &dev_attr, AttributeValue &att, const std::string &d_name)
{
    att.name = dev_attr.name.c_str();
    att.quality = dev_attr.quality;
    att.time = dev_attr.time;
    att.dim_x = dev_attr.dim_x;
    att.dim_y = dev_attr.dim_y;

    if(dev_attr.LongSeq.operator->() != nullptr)
    {
        att.value <<= dev_attr.LongSeq.in();
    }
    else if(dev_attr.ShortSeq.operator->() != nullptr)
    {
        att.value <<= dev_attr.ShortSeq.in();
    }
    else if(dev_attr.DoubleSeq.operator->() != nullptr)
    {
        att.value <<= dev_attr.DoubleSeq.in();
    }
    else if(dev_attr.StringSeq.operator->() != nullptr)
    {
        att.value <<= dev_attr.StringSeq.in();
    }
    else if(dev_attr.FloatSeq.operator->() != nullptr)
    {
        att.value <<= dev_attr.FloatSeq.in();
    }
    else if(dev_attr.BooleanSeq.operator->() != nullptr)
    {
        att.value <<= dev_attr.BooleanSeq.in();
    }
    else if(dev_attr.UShortSeq.operator->() != nullptr)
    {
        att.value <<= dev_attr.UShortSeq.in();
    }
    else if(dev_attr.UCharSeq.operator->() != nullptr)
    {
        att.value <<= dev_attr.UCharSeq.in();
    }
    else if(dev_attr.Long64Seq.operator->() != nullptr)
    {
        att.value <<= dev_attr.Long64Seq.in();
    }
    else if(dev_attr.ULongSeq.operator->() != nullptr)
    {
        att.value <<= dev_attr.ULongSeq.in();
    }
    else if(dev_attr.ULong64Seq.operator->() != nullptr)
    {
        att.value <<= dev_attr.ULong64Seq.in();
    }
    else if(dev_attr.StateSeq.operator->() != nullptr)
    {
        att.value <<= dev_attr.StateSeq.in();
    }
    else if(dev_attr.EncodedSeq.operator->() != nullptr)
    {
        TangoSys_OMemStream desc;
        desc << "Device " << d_name;
        desc << " does not support DevEncoded data type" << std::ends;
        TANGO_THROW_DETAILED_EXCEPTION(ApiNonSuppExcept, API_UnsupportedFeature, desc.str());
    }
}

//------------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::AttributeInfoEx_to_AttributeConfig()
//
// description :
//        Initialize one AttributeConfig instance from a AttributeInfoEx one
//
// arg(s) :
//        in :
//            - aie : The AttributeInfoEx instance taken as source
//        out :
//            - att_conf_5 : The AttributeConfig used as destination
//
//------------------------------------------------------------------------------------------------------------------

void ApiUtil::AttributeInfoEx_to_AttributeConfig(const AttributeInfoEx *aie, AttributeConfig_5 *att_conf_5)
{
    att_conf_5->name = aie->name.c_str();
    att_conf_5->writable = aie->writable;
    att_conf_5->data_format = aie->data_format;
    att_conf_5->data_type = aie->data_type;
    att_conf_5->max_dim_x = aie->max_dim_x;
    att_conf_5->max_dim_y = aie->max_dim_y;
    att_conf_5->description = aie->description.c_str();
    att_conf_5->label = aie->label.c_str();
    att_conf_5->unit = aie->unit.c_str();
    att_conf_5->standard_unit = aie->standard_unit.c_str();
    att_conf_5->display_unit = aie->display_unit.c_str();
    att_conf_5->format = aie->format.c_str();
    att_conf_5->min_value = aie->min_value.c_str();
    att_conf_5->max_value = aie->max_value.c_str();
    att_conf_5->writable_attr_name = aie->writable_attr_name.c_str();
    att_conf_5->level = aie->disp_level;
    att_conf_5->root_attr_name = aie->root_attr_name.c_str();
    switch(aie->memorized)
    {
    case NOT_KNOWN:
    case NONE:
        att_conf_5->memorized = false;
        att_conf_5->mem_init = false;
        break;

    case MEMORIZED:
        att_conf_5->memorized = true;
        att_conf_5->mem_init = false;
        break;

    case MEMORIZED_WRITE_INIT:
        att_conf_5->memorized = true;
        att_conf_5->mem_init = true;
        break;

    default:
        TANGO_ASSERT_ON_DEFAULT(aie->memorized);
    }
    att_conf_5->enum_labels.length(aie->enum_labels.size());
    for(size_t j = 0; j < aie->enum_labels.size(); j++)
    {
        att_conf_5->enum_labels[j] = string_dup(aie->enum_labels[j].c_str());
    }
    att_conf_5->extensions.length(aie->extensions.size());
    for(size_t j = 0; j < aie->extensions.size(); j++)
    {
        att_conf_5->extensions[j] = string_dup(aie->extensions[j].c_str());
    }
    for(size_t j = 0; j < aie->sys_extensions.size(); j++)
    {
        att_conf_5->sys_extensions[j] = string_dup(aie->sys_extensions[j].c_str());
    }

    att_conf_5->att_alarm.min_alarm = aie->alarms.min_alarm.c_str();
    att_conf_5->att_alarm.max_alarm = aie->alarms.max_alarm.c_str();
    att_conf_5->att_alarm.min_warning = aie->alarms.min_warning.c_str();
    att_conf_5->att_alarm.max_warning = aie->alarms.max_warning.c_str();
    att_conf_5->att_alarm.delta_t = aie->alarms.delta_t.c_str();
    att_conf_5->att_alarm.delta_val = aie->alarms.delta_val.c_str();
    for(size_t j = 0; j < aie->alarms.extensions.size(); j++)
    {
        att_conf_5->att_alarm.extensions[j] = string_dup(aie->alarms.extensions[j].c_str());
    }

    att_conf_5->event_prop.ch_event.rel_change = aie->events.ch_event.rel_change.c_str();
    att_conf_5->event_prop.ch_event.abs_change = aie->events.ch_event.abs_change.c_str();
    for(size_t j = 0; j < aie->events.ch_event.extensions.size(); j++)
    {
        att_conf_5->event_prop.ch_event.extensions[j] = string_dup(aie->events.ch_event.extensions[j].c_str());
    }

    att_conf_5->event_prop.per_event.period = aie->events.per_event.period.c_str();
    for(size_t j = 0; j < aie->events.per_event.extensions.size(); j++)
    {
        att_conf_5->event_prop.per_event.extensions[j] = string_dup(aie->events.per_event.extensions[j].c_str());
    }

    att_conf_5->event_prop.arch_event.rel_change = aie->events.arch_event.archive_rel_change.c_str();
    att_conf_5->event_prop.arch_event.abs_change = aie->events.arch_event.archive_abs_change.c_str();
    att_conf_5->event_prop.arch_event.period = aie->events.arch_event.archive_period.c_str();
    for(size_t j = 0; j < aie->events.ch_event.extensions.size(); j++)
    {
        att_conf_5->event_prop.arch_event.extensions[j] = string_dup(aie->events.arch_event.extensions[j].c_str());
    }
}

//-------------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::get_env_var()
//
// description :
//        Get environment variable
//
// arg(s) :
//        in :
//            - env_var_name : The environment variable name
//        out :
//            - env_var : Reference to the string initialised with the env. variable value (if found)
//
// return :
//        0 if the env. variable is found and -1 otherwise
//
//----------------------------------------------------------------------------------------------------------------

int ApiUtil::get_env_var(const char *env_var_name, std::string &env_var)
{
    DummyDeviceProxy d;
    return d.get_env_var(env_var_name, env_var);
}

//---------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::get_ip_from_if()
//
// description :
//        Get host IP address from its network interface
//
// arg(s) :
//        out :
//            - ip_adr_list : Host IP address
//
//----------------------------------------------------------------------------------------------------------------

void ApiUtil::get_ip_from_if(std::vector<std::string> &ip_adr_list)
{
    omni_mutex_lock oml(lock_th_map);

    if(host_ip_adrs.empty())
    {
#ifndef _TG_WINDOWS_
        struct ifaddrs *ifaddr, *ifa;
        int family, s;
        char host[NI_MAXHOST];

        if(getifaddrs(&ifaddr) == -1)
        {
            std::cerr << "ApiUtil::get_ip_from_if: getifaddrs() failed: " << strerror(errno) << std::endl;

            TANGO_THROW_EXCEPTION(API_SystemCallFailed, strerror(errno));
        }

        //
        // Walk through linked list, maintaining head pointer so we can free list later. The ifa_addr field points to a
        // structure containing the interface address. (The sa_family subfield should be consulted to determine the
        // format of the address structure.)
        //

        for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next)
        {
            if(ifa->ifa_addr != nullptr)
            {
                family = ifa->ifa_addr->sa_family;

                //
                // Only get IP V4 addresses
                //

                /*              if(family == AF_INET || family == AF_INET6)
                                {
                                    s = getnameinfo(ifa->ifa_addr,(family == AF_INET) ? sizeof(struct sockaddr_in) :
                   sizeof(struct sockaddr_in6), host, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST);*/

                if(family == AF_INET)
                {
                    s = getnameinfo(
                        ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST);

                    if(s != 0)
                    {
                        std::cerr << "ApiUtil::get_ip_from_if: getnameinfo() failed: " << gai_strerror(s);
                        std::cerr << "ApiUtil::get_ip_from_if: not getting info from remaining ifaddrs";

                        freeifaddrs(ifaddr);

                        TANGO_THROW_EXCEPTION(API_SystemCallFailed, gai_strerror(s));
                    }
                    else
                    {
                        host_ip_adrs.emplace_back(host);
                    }
                }
            }
        }

        freeifaddrs(ifaddr);
#else

        //
        // Get address from interfaces
        //

        int sock = socket(AF_INET, SOCK_STREAM, 0);

        if(sock == INVALID_SOCKET)
        {
            int err = WSAGetLastError();
            TangoSys_OMemStream desc;
            desc << "Unable to create socket! Error = " << err;
            TANGO_THROW_EXCEPTION(API_SystemCallFailed, desc.str().c_str());
        }

        INTERFACE_INFO info[64]; // Assume max 64 interfaces
        DWORD retlen;

        if(WSAIoctl(sock,
                    SIO_GET_INTERFACE_LIST,
                    nullptr,
                    0,
                    (LPVOID) &info,
                    sizeof(info),
                    (LPDWORD) &retlen,
                    nullptr,
                    nullptr) == SOCKET_ERROR)
        {
            closesocket(sock);

            int err = WSAGetLastError();
            std::cerr << "Warning: WSAIoctl failed" << std::endl;
            std::cerr << "Unable to obtain the list of all interface addresses. Error = " << err << std::endl;

            TangoSys_OMemStream desc;
            desc << "Can't retrieve list of all interfaces addresses (WSAIoctl)! Error = " << err << std::ends;

            TANGO_THROW_EXCEPTION(API_SystemCallFailed, desc.str().c_str());
        }
        closesocket(sock);

        //
        // Converts addresses to string. Only for running interfaces
        //

        int numAddresses = retlen / sizeof(INTERFACE_INFO);
        for(int i = 0; i < numAddresses; i++)
        {
            if(info[i].iiFlags & IFF_UP)
            {
                if(info[i].iiAddress.Address.sa_family == AF_INET || info[i].iiAddress.Address.sa_family == AF_INET6)
                {
                    struct sockaddr *addr = (struct sockaddr *) &info[i].iiAddress.AddressIn;
                    char dest[NI_MAXHOST];
                    socklen_t addrlen = sizeof(sockaddr);

                    int result = getnameinfo(addr, addrlen, dest, sizeof(dest), 0, 0, NI_NUMERICHOST);
                    if(result != 0)
                    {
                        std::cerr << "Warning: getnameinfo failed" << std::endl;
                        std::cerr << "Unable to convert IP address to string (getnameinfo)." << std::endl;

                        TANGO_THROW_EXCEPTION(API_SystemCallFailed,
                                              "Can't convert IP address to string (getnameinfo)!");
                    }
                    std::string tmp_str(dest);
                    if(tmp_str != "0.0.0.0" && tmp_str != "0:0:0:0:0:0:0:0" && tmp_str != "::")
                    {
                        host_ip_adrs.push_back(tmp_str);
                    }
                }
            }
        }
#endif
    }

    //
    // Copy host IP addresses into caller vector
    //

    ip_adr_list.clear();
    copy(host_ip_adrs.begin(), host_ip_adrs.end(), back_inserter(ip_adr_list));
}

//--------------------------------------------------------------------------------------------------------------------
//
// method :
//        ApiUtil::print_error_message
//
// description :
//        Print error message on stderr but first print date
//
// argument :
//        in :
//            - mess : The user error message
//
//---------------------------------------------------------------------------------------------------------------------

void ApiUtil::print_error_message(const char *mess)
{
    const std::tm tm = Tango_localtime(Tango::get_current_system_datetime());
    std::cerr << std::put_time(&tm, "%c") << ": " << mess << std::endl;
}

} // namespace Tango
