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

#include <tango/client/Group.h>

#include <tango/internal/telemetry/telemetry_kernel_macros.h>

#include <tango/client/AsynchRequest.h>
#include <tango/client/Database.h>
#include <tango/client/GroupElementFactory.h>
#include <tango/client/GroupReply.h>
#include <tango/server/Util.h>

#include <sstream>

//-----------------------------------------------------------------------------
// LOCAL DEBUGGING MACRO
//-----------------------------------------------------------------------------
// #define _LOCAL_DEBUGGING

namespace Tango
{

//=============================================================================
// class Group
//=============================================================================
Group::Group(const std::string &name) :
    GroupElement(name),
    asynch_req_id(0)
{
    //- noop ctor
}

//-----------------------------------------------------------------------------
Group::~Group()
{
    remove_all();
    arp.clear();
}

//-----------------------------------------------------------------------------
Group *Group::get_parent() const
{
    return reinterpret_cast<Group *>(parent);
}

//-----------------------------------------------------------------------------
bool Group::is_root_group() const
{
    return parent == nullptr;
}

//-----------------------------------------------------------------------------
std::vector<std::string> Group::get_device_list(bool fwd)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    std::vector<std::string> dl(0);
    std::vector<std::string> sub_dl(0);
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i())
        {
            dl.push_back((*it)->get_name());
        }
        else if(fwd)
        {
            sub_dl = (static_cast<Group *>(*it))->get_device_list(fwd);
            if(!sub_dl.empty())
            {
                dl.insert(dl.end(), sub_dl.begin(), sub_dl.end());
            }
            sub_dl.clear();
        }
    }
    return dl;
}

//-----------------------------------------------------------------------------
GroupElements Group::get_hiearchy()
{
    GroupElements te(0);
    GroupElements sub_te(0);
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i())
        {
            te.push_back(*it);
        }
        else
        {
            sub_te = (static_cast<Group *>(*it))->get_hiearchy();
            if(!sub_te.empty())
            {
                te.insert(te.end(), sub_te.begin(), sub_te.end());
            }
            sub_te.clear();
        }
    }
    return te;
}

//-----------------------------------------------------------------------------
long Group::get_size(bool fwd)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    return get_size_i(fwd);
}

//-----------------------------------------------------------------------------
long Group::get_size_i(bool fwd)
{
    long size = 0;
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i() || fwd)
        {
            size += (*it)->get_size_i();
        }
    }
    return size;
}

//-----------------------------------------------------------------------------
void Group::add(Group *g, int tmo_ms)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
#ifdef _LOCAL_DEBUGGING
    TANGO_LOG << "Group::add::adding sub-group " << (g ? g->get_name() : "NULL") << std::endl;
#endif
    if(g == nullptr || get_group_i(g->get_name()) == g || g->get_group_i(get_name()) == this)
    {
#ifdef _LOCAL_DEBUGGING
        TANGO_LOG << "Group::add::failed to add group"
                  << (g ? (g->get_name() + " (self ref. or already in group)") : "NULL") << std::endl;
#endif
        if(g != nullptr)
        {
            g->set_timeout_millis(tmo_ms);
        }
        return;
    }
    add_i(g, false);
    g->set_timeout_millis(tmo_ms);
}

//-----------------------------------------------------------------------------
void Group::add(const std::string &p, int tmo_ms)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    GroupElements el = GroupElementFactory::instanciate(p, tmo_ms);
    for(unsigned int e = 0; e < el.size(); e++)
    {
        if((el[e] != nullptr) && (!add_i(el[e])))
        {
            GroupElement *te = find_i(el[e]->get_name());
            try
            {
                te->set_timeout_millis(tmo_ms);
            }
            catch(...)
            {
                // ignore errors
            }
            delete el[e];
        }
    }
}

//-----------------------------------------------------------------------------
void Group::add(const std::vector<std::string> &pl, int tmo_ms)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    for(unsigned int p = 0; p < pl.size(); p++)
    {
        GroupElements el = GroupElementFactory::instanciate(pl[p], tmo_ms);
        for(unsigned int e = 0; e < el.size(); e++)
        {
            if((el[e] != nullptr) && (!add_i(el[e])))
            {
                GroupElement *te = find_i(el[e]->get_name());
                try
                {
                    te->set_timeout_millis(tmo_ms);
                }
                catch(...)
                {
                    // ignore errors
                }
                delete el[e];
            }
        }
    }
}

//-----------------------------------------------------------------------------
bool Group::add_i(GroupElement *e, bool fwd)
{
    if(e == nullptr || e == this)
    {
#ifdef _LOCAL_DEBUGGING
        TANGO_LOG << "Group::add_i::failed to add " << (e ? (e->get_name() + " (null or self ref)") : "NULL")
                  << std::endl;
#endif
        return false;
    }
    GroupElement *te = find_i(e->get_name(), fwd);
    if(te != nullptr && te != this)
    {
#ifdef _LOCAL_DEBUGGING
        TANGO_LOG << "Group::add_i::failed to add " << e->get_name() << " (already in group)" << std::endl;
#endif
        return false;
    }
    elements.push_back(e);
    e->set_parent(this);
    return true;
}

//-----------------------------------------------------------------------------
void Group::remove(const std::string &p, bool fwd)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
#ifdef _LOCAL_DEBUGGING
    TANGO_LOG << "Group::remove::pattern [" << p << "]" << std::endl;
#endif
    remove_i(p, fwd);
}

//-----------------------------------------------------------------------------
void Group::remove(const std::vector<std::string> &pl, bool fwd)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
#ifdef _LOCAL_DEBUGGING
    TANGO_LOG << "Group::remove::pattern list" << std::endl;
#endif
    for(unsigned int p = 0; p < pl.size(); p++)
    {
        remove_i(pl[p], fwd);
    }
}

//-----------------------------------------------------------------------------
void Group::remove_i(const std::string &p, bool fwd)
{
#ifdef _LOCAL_DEBUGGING
    TANGO_LOG << "\t|- Group::remove_i" << std::endl;
#endif
    if(name_equals(p))
    {
#ifdef _LOCAL_DEBUGGING
        TANGO_LOG << "Group::remove_i::failed to remove " << p << " (can't remove self)" << std::endl;
#endif
        return;
    }
    GroupElementsIterator it;
    if(p.find('*', 0) == std::string::npos)
    {
        for(it = elements.begin(); it != elements.end(); ++it)
        {
            if((*it)->name_equals(p))
            {
                delete *it;
                elements.erase(it);
                break;
            }
        }
    }
    else
    {
        std::vector<GroupElement *> remove_list(0);
        for(it = elements.begin(); it != elements.end(); ++it)
        {
            if((*it)->name_matches(p))
            {
                remove_list.push_back(*it);
            }
        }
        for(unsigned int i = 0; i < remove_list.size(); i++)
        {
            it = std::find(elements.begin(), elements.end(), remove_list[i]);
            if(it != elements.end())
            {
#ifdef _LOCAL_DEBUGGING
                TANGO_LOG << "\t|- Group::remove_i::removing " << (*it)->get_name() << std::endl;
#endif // _LOCAL_DEBUGGING
                delete remove_list[i];
                elements.erase(it);
            }
        }
    }
    if(fwd)
    {
        for(it = elements.begin(); it != elements.end(); ++it)
        {
            if((*it)->is_group_i())
            {
                reinterpret_cast<Group *>((*it))->remove(p, fwd);
            }
        }
    }
}

//-----------------------------------------------------------------------------
void Group::remove_all()
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
#ifdef _LOCAL_DEBUGGING
    TANGO_LOG << "Group::remove_all::" << get_name() << std::endl;
#endif
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        delete *it;
    }
    elements.clear();
}

//-----------------------------------------------------------------------------
bool Group::contains(const std::string &n, bool fwd)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    return find_i(n, fwd) != nullptr;
}

//-----------------------------------------------------------------------------
GroupElement *Group::find_i(const std::string &n, bool fwd)
{
    std::string::size_type pos = n.find('*', 0);
    bool is_pattern = pos != std::string::npos;
    if(is_pattern)
    {
        if(name_matches(n))
        {
            return this;
        }
    }
    else
    {
        if(name_equals(n))
        {
            return this;
        }
    }
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if(is_pattern)
        {
            if((*it)->name_matches(n))
            {
                return *it;
            }
        }
        else
        {
            if((*it)->name_equals(n))
            {
                return *it;
            }
        }
    }
    GroupElement *e = nullptr;
    if(fwd)
    {
        it = elements.begin();
        end = elements.end();
        while(it != end && e == nullptr)
        {
            e = (*it)->find_i(n);
            ++it;
        }
    }
    return e;
}

//-----------------------------------------------------------------------------
DeviceProxy *Group::get_device(const std::string &n)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    DeviceProxy *dp = nullptr;
    auto it = elements.begin();
    auto end = elements.end();
    while(it != end && dp == nullptr)
    {
        dp = (*it)->get_device(n);
        ++it;
    }
    return dp;
}

//-----------------------------------------------------------------------------
DeviceProxy *Group::get_device(long idx)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    DeviceProxy *dp = nullptr;
    auto it = elements.begin();
    auto end = elements.end();
    while(it != end && dp == nullptr)
    {
        dp = (*it)->get_device(idx--);
        ++it;
    }
    return dp;
}

//-----------------------------------------------------------------------------
DeviceProxy *Group::operator[](long idx)
{
    return get_device(idx);
}

//-----------------------------------------------------------------------------
Group *Group::get_group(const std::string &n)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    return get_group_i(n);
}

//-----------------------------------------------------------------------------
Group *Group::get_group_i(const std::string &n)
{
    GroupElement *e = find_i(n, true);
    return (e != nullptr && e->is_group_i()) ? reinterpret_cast<Group *>(e) : nullptr;
}

//-----------------------------------------------------------------------------
void Group::enable(const std::string &n, bool fwd)
{
    GroupElement *e = find_i(n, fwd);
    if(e != nullptr)
    {
        e->enable();
    }
}

//-----------------------------------------------------------------------------
void Group::disable(const std::string &n, bool fwd)
{
    GroupElement *e = find_i(n, fwd);
    if(e != nullptr)
    {
        e->disable();
    }
}

//-----------------------------------------------------------------------------
bool Group::ping(bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    bool result = true;
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end && result; ++it)
    {
        if((*it)->is_device_i() || fwd)
        {
            result = (*it)->ping(fwd);
        }
    }
    return result;

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
void Group::set_timeout_millis(int tmo_ms)
{
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        try
        {
            (*it)->set_timeout_millis(tmo_ms);
        }
        catch(...)
        {
            // ignore errors
        }
    }
}

//-----------------------------------------------------------------------------
GroupCmdReplyList Group::command_inout(const std::string &c, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    long id = command_inout_asynch_i(c, false, fwd, -1);
    return command_inout_reply_i(id, 0);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
GroupCmdReplyList Group::command_inout(const std::string &c, const DeviceData &d, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    long id = command_inout_asynch_i(c, d, false, fwd, -1);
    return command_inout_reply_i(id, 0);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
GroupCmdReplyList Group::command_inout(const std::string &c, const std::vector<DeviceData> &d, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    long id = command_inout_asynch_i(c, d, false, fwd, -1);
    return command_inout_reply_i(id, 0);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
long Group::command_inout_asynch(const std::string &c, bool fgt, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    return command_inout_asynch_i(c, fgt, fwd, -1);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
long Group::command_inout_asynch_i(const std::string &c, bool fgt, bool fwd, long id)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    if(id == -1)
    {
        id = next_asynch_request_id();
    }
    GroupElements disconnected;
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i() || fwd)
        {
            if((*it)->is_connected())
            {
                (*it)->command_inout_asynch_i(c, fgt, fwd, id);
            }
            else
            {
                disconnected.push_back(*it);
            }
        }
    }
    for(size_t i = 0; i < disconnected.size(); i++)
    {
        disconnected[i]->command_inout_asynch_i(c, fgt, fwd, id);
    }
    if(!fgt)
    {
        push_async_request(id, fwd);
    }
    return id;
}

//-----------------------------------------------------------------------------
long Group::command_inout_asynch(const std::string &c, const DeviceData &d, bool fgt, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    return command_inout_asynch_i(c, d, fgt, fwd, -1);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
long Group::command_inout_asynch_i(const std::string &c, const DeviceData &d, bool fgt, bool fwd, long id)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    if(id == -1)
    {
        id = next_asynch_request_id();
    }
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i() || fwd)
        {
            (*it)->command_inout_asynch_i(c, d, fgt, fwd, id);
        }
    }
    if(!fgt)
    {
        push_async_request(id, fwd);
    }
    return id;
}

//-----------------------------------------------------------------------------
long Group::command_inout_asynch(const std::string &c, const std::vector<DeviceData> &d, bool fgt, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    return command_inout_asynch_i(c, d, fgt, fwd, -1);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
long Group::command_inout_asynch_i(const std::string &c, const std::vector<DeviceData> &d, bool fgt, bool fwd, long id)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif

    long gsize = get_size_i(fwd);
    if(gsize != static_cast<long>(d.size()))
    {
        TangoSys_OMemStream desc;
        desc << "the size of the input argument list must equal the number of device in the group"
             << " [expected:" << gsize << " - got:" << d.size() << "]" << std::ends;
        TANGO_THROW_DETAILED_EXCEPTION(ApiDataExcept, API_MethodArgument, desc.str().c_str());
    }

    if(id == -1)
    {
        id = next_asynch_request_id();
    }

    for(unsigned int i = 0, j = 0; i < elements.size(); i++)
    {
        if(elements[i]->is_device_i())
        {
            elements[i]->command_inout_asynch_i(c, d[j++], fgt, false, id);
        }
        else if(fwd)
        {
            Tango::Group *g = reinterpret_cast<Tango::Group *>(elements[i]);
            gsize = g->get_size_i(fwd);
            std::vector<Tango::DeviceData> sub_d(d.begin() + j, d.begin() + j + gsize);
            g->command_inout_asynch_i(c, static_cast<const std::vector<DeviceData> &>(sub_d), fgt, fwd, id);
            j += gsize;
        }
    }

    if(!fgt)
    {
        push_async_request(id, fwd);
    }

    return id;
}

//-----------------------------------------------------------------------------
GroupCmdReplyList Group::command_inout_reply(long ari, long tmo)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    return command_inout_reply_i(ari, tmo);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
GroupCmdReplyList Group::command_inout_reply_i(long ari, long tmo)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    auto r = arp.find(ari);
    if(r == arp.end())
    {
        Tango::DevErrorList errors(1);
        errors.length(1);
        errors[0].severity = Tango::ERR;
        errors[0].reason = Tango::string_dup(API_BadAsynPollId);
        errors[0].desc = Tango::string_dup("Invalid asynch. request identifier specified");
        errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
        throw DevFailed(errors);
    }
    GroupCmdReplyList reply;
    GroupCmdReplyList sub_reply;
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i() || r->second)
        {
            sub_reply = (*it)->command_inout_reply_i(ari, tmo);
            if(!sub_reply.empty())
            {
                reply.insert(reply.end(), sub_reply.begin(), sub_reply.end());
            }
            if(sub_reply.has_failed_m)
            {
                reply.has_failed_m = true;
            }
            sub_reply.reset();
        }
    }
    arp.erase(r);
    return reply;
}

//-----------------------------------------------------------------------------
GroupAttrReplyList Group::read_attribute(const std::string &a, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    long id = read_attribute_asynch_i(a, fwd, -1);
    return read_attribute_reply_i(id, 0);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
GroupAttrReplyList Group::read_attributes(const std::vector<std::string> &al, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    long id = read_attributes_asynch_i(al, fwd, -1);
    return read_attributes_reply_i(id, 0);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
long Group::read_attribute_asynch(const std::string &a, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    return read_attribute_asynch_i(a, fwd, -1);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
long Group::read_attribute_asynch_i(const std::string &a, bool fwd, long id)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    if(id == -1)
    {
        id = next_asynch_request_id();
    }
    GroupElements disconnected;
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i() || fwd)
        {
            if((*it)->is_connected())
            {
                id = (*it)->read_attribute_asynch_i(a, fwd, id);
            }
            else
            {
                disconnected.push_back(*it);
            }
        }
    }
    for(size_t i = 0; i < disconnected.size(); i++)
    {
        id = disconnected[i]->read_attribute_asynch_i(a, fwd, id);
    }
    push_async_request(id, fwd);
    return id;
}

//-----------------------------------------------------------------------------
GroupAttrReplyList Group::read_attribute_reply(long ari, long tmo)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    return read_attribute_reply_i(ari, tmo);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
GroupAttrReplyList Group::read_attribute_reply_i(long ari, long tmo)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    auto r = arp.find(ari);
    if(r == arp.end())
    {
        Tango::DevErrorList errors(1);
        errors.length(1);
        errors[0].severity = Tango::ERR;
        errors[0].reason = Tango::string_dup(API_BadAsynPollId);
        errors[0].desc = Tango::string_dup("Invalid asynch. request identifier specified");
        errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
        throw DevFailed(errors);
    }
    GroupAttrReplyList reply;
    GroupAttrReplyList sub_reply;
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i() || r->second)
        {
            sub_reply = (*it)->read_attribute_reply_i(ari, tmo);
            if(!sub_reply.empty())
            {
                reply.insert(reply.end(), sub_reply.begin(), sub_reply.end());
            }
            if(sub_reply.has_failed_m)
            {
                reply.has_failed_m = true;
            }
            sub_reply.reset();
        }
    }
    arp.erase(r);
    return reply;
}

//-----------------------------------------------------------------------------
long Group::read_attributes_asynch(const std::vector<std::string> &al, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    return read_attributes_asynch_i(al, fwd, -1);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
long Group::read_attributes_asynch_i(const std::vector<std::string> &al, bool fwd, long id)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    if(id == -1)
    {
        id = next_asynch_request_id();
    }
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i() || fwd)
        {
            id = (*it)->read_attributes_asynch_i(al, fwd, id);
        }
    }
    push_async_request(id, fwd);
    return id;
}

//-----------------------------------------------------------------------------
GroupAttrReplyList Group::read_attributes_reply(long ari, long tmo)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    return read_attributes_reply_i(ari, tmo);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
GroupAttrReplyList Group::read_attributes_reply_i(long ari, long tmo)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    auto r = arp.find(ari);
    if(r == arp.end())
    {
        Tango::DevErrorList errors(1);
        errors.length(1);
        errors[0].severity = Tango::ERR;
        errors[0].reason = Tango::string_dup(API_BadAsynPollId);
        errors[0].desc = Tango::string_dup("Invalid asynch. request identifier specified");
        errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
        throw DevFailed(errors);
    }
    GroupAttrReplyList reply;
    GroupAttrReplyList sub_reply;
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i() || r->second)
        {
            sub_reply = (*it)->read_attributes_reply_i(ari, tmo);
            if(!sub_reply.empty())
            {
                reply.insert(reply.end(), sub_reply.begin(), sub_reply.end());
            }
            if(sub_reply.has_failed_m)
            {
                reply.has_failed_m = true;
            }
            sub_reply.reset();
        }
    }
    arp.erase(r);
    return reply;
}

//-----------------------------------------------------------------------------
GroupReplyList Group::write_attribute(const DeviceAttribute &d, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    long id = write_attribute_asynch_i(d, fwd, -1);
    return write_attribute_reply_i(id, 0);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
GroupReplyList Group::write_attribute(const std::vector<DeviceAttribute> &d, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    long id = write_attribute_asynch_i(d, fwd, -1);
    return write_attribute_reply_i(id, 0);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
long Group::write_attribute_asynch(const DeviceAttribute &d, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    return write_attribute_asynch_i(d, fwd, -1);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
long Group::write_attribute_asynch_i(const DeviceAttribute &d, bool fwd, long id)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    if(id == -1)
    {
        id = next_asynch_request_id();
    }
    GroupElements disconnected;
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i() || fwd)
        {
            if((*it)->is_connected())
            {
                id = (*it)->write_attribute_asynch_i(d, fwd, id);
            }
            else
            {
                disconnected.push_back(*it);
            }
        }
    }
    for(size_t i = 0; i < disconnected.size(); i++)
    {
        id = disconnected[i]->write_attribute_asynch_i(d, fwd, id);
    }
    push_async_request(id, fwd);
    return id;
}

//-----------------------------------------------------------------------------
long Group::write_attribute_asynch(const std::vector<DeviceAttribute> &d, bool fwd)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    return write_attribute_asynch_i(d, fwd, -1);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
long Group::write_attribute_asynch_i(const std::vector<DeviceAttribute> &d, bool fwd, long id)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    GroupReplyList rl;
    long gsize = get_size_i(fwd);
    if(gsize != static_cast<long>(d.size()))
    {
        TangoSys_OMemStream desc;
        desc << "the size of the input argument list must equal the number of device in the group"
             << " [expected:" << gsize << " - got:" << d.size() << "]" << std::ends;
        TANGO_THROW_DETAILED_EXCEPTION(ApiDataExcept, API_MethodArgument, desc.str().c_str());
    }
    if(id == -1)
    {
        id = next_asynch_request_id();
    }
    for(unsigned int i = 0, j = 0; i < elements.size(); i++)
    {
        if(elements[i]->is_device_i())
        {
            elements[i]->write_attribute_asynch_i(d[j++], false, id);
        }
        else if(fwd)
        {
            Tango::Group *g = reinterpret_cast<Tango::Group *>(elements[i]);
            long gsize = g->get_size_i(fwd);
            std::vector<DeviceAttribute> sub_d(d.begin() + j, d.begin() + j + gsize);
            reinterpret_cast<Tango::Group *>(elements[i])->write_attribute_asynch_i(sub_d, fwd, id);
            j += gsize;
        }
    }
    push_async_request(id, fwd);
    return id;
}

//-----------------------------------------------------------------------------
GroupReplyList Group::write_attribute_reply(long ari, long tmo)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({}));

    return write_attribute_reply_i(ari, tmo);

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
GroupReplyList Group::write_attribute_reply_i(long ari, long tmo)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    auto r = arp.find(ari);
    if(r == arp.end())
    {
        Tango::DevErrorList errors(1);
        errors.length(1);
        errors[0].severity = Tango::ERR;
        errors[0].reason = Tango::string_dup(API_BadAsynPollId);
        errors[0].desc = Tango::string_dup("Invalid asynch. request identifier specified");
        errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
        throw DevFailed(errors);
    }
    GroupReplyList reply;
    GroupReplyList sub_reply;
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        if((*it)->is_device_i() || r->second)
        {
            sub_reply = (*it)->write_attribute_reply_i(ari, tmo);
            if(!sub_reply.empty())
            {
                reply.insert(reply.end(), sub_reply.begin(), sub_reply.end());
            }
            if(sub_reply.has_failed_m)
            {
                reply.has_failed_m = true;
            }
            sub_reply.reset();
        }
    }
    arp.erase(r);
    return reply;
}

//-----------------------------------------------------------------------------
long Group::next_asynch_request_id()
{
    asynch_req_id++;
    asynch_req_id %= (long) 0x0FFF;
    return asynch_req_id;
}

//-----------------------------------------------------------------------------
void Group::dump(int indent_level)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    auto it = elements.begin();
    auto end = elements.end();
    for(int i = 0; i < indent_level; i++)
    {
        TANGO_LOG << "\t";
    }
    TANGO_LOG << "|- GROUP: " << get_fully_qualified_name() << " [" << get_size_i(false) << ":" << get_size_i(true)
              << "]" << std::endl;
    for(; it != end; ++it)
    {
        (*it)->dump(indent_level + 1);
    }
}

//-----------------------------------------------------------------------------
void Group::dump(TangoSys_OMemStream &oms, int indent_level)
{
#ifdef TANGO_GROUP_HAS_THREAD_SAFE_IMPL
    omni_mutex_lock guard(elements_mutex);
#endif
    for(int i = 0; i < indent_level; i++)
    {
        oms << "\t";
    }
    oms << "|- GROUP: " << get_fully_qualified_name() << " [" << get_size_i(false) << ":" << get_size_i(true) << "]"
        << std::endl;
    auto it = elements.begin();
    auto end = elements.end();
    for(; it != end; ++it)
    {
        (*it)->dump(oms, indent_level + 1);
    }
    if(indent_level == 0)
    {
        oms << std::ends;
    }
}

//-----------------------------------------------------------------------------
bool Group::is_device_i()
{
    return false;
}

//-----------------------------------------------------------------------------
bool Group::is_group_i()
{
    return true;
}

//-----------------------------------------------------------------------------
void Group::push_async_request(long rid, bool forwarded)
{
    arp.insert(AsynchRequestDescVal(rid, forwarded));
}

//-----------------------------------------------------------------------------
void Group::pop_async_request(long rid)
{
    auto r = arp.find(rid);
    if(r != arp.end())
    {
        arp.erase(r);
    }
}

bool Group::is_enabled(const std::string &device_name, bool fwd)
{
    GroupElement *ge = this->find_i(device_name, fwd);
    return ge != nullptr ? ge->is_enabled() : false;
}

} // namespace Tango
