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

#include <tango/client/GroupDeviceElement.h>

#include <algorithm>

namespace Tango
{

//=============================================================================
// class GroupDeviceElement
//=============================================================================
GroupDeviceElement::GroupDeviceElement(const std::string &name) :
    GroupElement(name),
    dp(nullptr)
{
    try
    {
        connect();
    }
    catch(...)
    {
        //- ignore error
    }
}

//-----------------------------------------------------------------------------
GroupDeviceElement::GroupDeviceElement(const std::string &name, int tmo_ms) :
    GroupElement(name),
    dp(nullptr)
{
    try
    {
        connect();
        set_timeout_millis(tmo_ms);
    }
    catch(...)
    {
        //- ignore error
    }
}

//-----------------------------------------------------------------------------
GroupDeviceElement::~GroupDeviceElement()
{
    disconnect();
    arp.clear();
}

//-----------------------------------------------------------------------------
DeviceProxy *GroupDeviceElement::get_device(const std::string &_n)
{
    try
    {
        std::string n(_n);
        std::transform(n.begin(), n.end(), n.begin(), ::tolower);
        if(name_equals(n))
        {
            return dev_proxy();
        }
    }
    catch(...)
    {
        //- ignore exception then return null
    }
    return nullptr;
}

//-----------------------------------------------------------------------------
DeviceProxy *GroupDeviceElement::get_device(long idx)
{
    return ((--idx) == 0) ? dev_proxy() : nullptr;
}

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

//-----------------------------------------------------------------------------
DeviceProxy *GroupDeviceElement::connect()
{
    disconnect();
    try
    {
        dp = new DeviceProxy(const_cast<std::string &>(get_name()));
        dp->set_transparency_reconnection(true);
    }
    catch(...)
    {
        disconnect();
        throw;
    }
    return dp;
}

//-----------------------------------------------------------------------------
void GroupDeviceElement::disconnect()
{
    //-TODO: how to handle pending asynch calls ?
    if(dp != nullptr)
    {
        delete dp;
        dp = nullptr;
    }
}

//-----------------------------------------------------------------------------
bool GroupDeviceElement::ping(bool)
{
    bool result = true;
    try
    {
        dev_proxy()->ping();
    }
    catch(...)
    {
        result = false;
    }
    return result;
}

//-----------------------------------------------------------------------------
long GroupDeviceElement::command_inout_asynch_i(const std::string &c, bool fgt, TANGO_UNUSED(bool fwd), long id)
{
    if(!is_enabled())
    {
        if(!fgt)
        {
            arp.insert(AsynchRequestRepValue(id, AsynchRequest(id, c, false)));
        }
    }
    else
    {
        try
        {
            long ari = dev_proxy()->command_inout_asynch(const_cast<std::string &>(c), fgt);
            if(!fgt)
            {
                arp.insert(AsynchRequestRepValue(id, AsynchRequest(ari, c)));
            }
        }
        catch(const Tango::DevFailed &df)
        {
            if(!fgt)
            {
                arp.insert(AsynchRequestRepValue(id, AsynchRequest(-1, c, df)));
            }
        }
        catch(...)
        {
            if(!fgt)
            {
                //- create a pseudo devfailed
                Tango::DevErrorList errors(1);
                errors.length(1);
                errors[0].severity = Tango::ERR;
                errors[0].desc = Tango::string_dup("unknown error");
                errors[0].reason = Tango::string_dup("unknown exception caught");
                errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
                DevFailed df(errors);
                arp.insert(AsynchRequestRepValue(id, AsynchRequest(-1, c, df)));
            }
        }
    }
    return id;
}

//-----------------------------------------------------------------------------
long GroupDeviceElement::command_inout_asynch_i(
    const std::string &c, const DeviceData &d, bool fgt, TANGO_UNUSED(bool fwd), long id)
{
    if(!is_enabled())
    {
        if(!fgt)
        {
            arp.insert(AsynchRequestRepValue(id, AsynchRequest(id, c, false)));
        }
    }
    else
    {
        try
        {
            long ari =
                dev_proxy()->command_inout_asynch(const_cast<std::string &>(c), const_cast<DeviceData &>(d), fgt);
            if(!fgt)
            {
                arp.insert(AsynchRequestRepValue(id, AsynchRequest(ari, c)));
            }
        }
        catch(const Tango::DevFailed &df)
        {
            if(!fgt)
            {
                arp.insert(AsynchRequestRepValue(id, AsynchRequest(-1, c, df)));
            }
        }
        catch(...)
        {
            if(!fgt)
            {
                //- create a pseudo devfailed
                Tango::DevErrorList errors(1);
                errors.length(1);
                errors[0].severity = Tango::ERR;
                errors[0].desc = Tango::string_dup("unknown error");
                errors[0].reason = Tango::string_dup("unknown exception caught");
                errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
                DevFailed df(errors);
                arp.insert(AsynchRequestRepValue(id, AsynchRequest(-1, c, df)));
            }
        }
    }
    return id;
}

//-----------------------------------------------------------------------------
GroupCmdReplyList GroupDeviceElement::command_inout_reply_i(long id, long tmo)
{
    GroupCmdReplyList rl;

    //- search actual asynch request id in this' repository
    auto it = arp.find(id);
    if(it == arp.end())
    {
        //- error description
        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);
        DevFailed df(errors);
        //- populate the returned vector
        rl.push_back(GroupCmdReply(get_name(), "unknown", df));
        return rl;
    }

    //- no reply if this group element is diabled
    if(!is_enabled())
    {
        rl.push_back(GroupCmdReply(get_name(), it->second.obj_names[0], false));
        //- remove request from repository
        arp.erase(it);
        return rl;
    }

    //- if got error during asynch call then previously stored exception is the reply
    if(it->second.rq_id == -1)
    {
        rl.push_back(GroupCmdReply(get_name(), it->second.obj_names[0], it->second.rq_ex));
        //- remove request from repository
        arp.erase(it);
        return rl;
    }

    //- get reply from device
    try
    {
        DeviceData dd = dev_proxy()->command_inout_reply(it->second.rq_id, tmo);
        rl.push_back(GroupCmdReply(get_name(), it->second.obj_names[0], dd));
    }
    catch(const Tango::DevFailed &df)
    {
        rl.push_back(GroupCmdReply(get_name(), it->second.obj_names[0], df));
    }
    catch(...)
    {
        //- create a pseudo devfailed
        Tango::DevErrorList errors(1);
        errors.length(1);
        errors[0].severity = Tango::ERR;
        errors[0].desc = Tango::string_dup("unknown error");
        errors[0].reason = Tango::string_dup("unknown exception caught");
        errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
        DevFailed df(errors);
        rl.push_back(GroupCmdReply(get_name(), it->second.obj_names[0], df));
    }

    //- remove request from repository
    arp.erase(it);

    return rl;
}

//-----------------------------------------------------------------------------
long GroupDeviceElement::read_attribute_asynch_i(const std::string &a, TANGO_UNUSED(bool fwd), long id)
{
    if(!is_enabled())
    {
        arp.insert(AsynchRequestRepValue(id, AsynchRequest(id, a, false)));
    }
    else
    {
        try
        {
            long ari = dev_proxy()->read_attribute_asynch(const_cast<std::string &>(a));
            arp.insert(AsynchRequestRepValue(id, AsynchRequest(ari, a)));
        }
        catch(const Tango::DevFailed &df)
        {
            arp.insert(AsynchRequestRepValue(id, AsynchRequest(-1, a, df)));
        }
        catch(...)
        {
            //- create a pseudo devfailed
            Tango::DevErrorList errors(1);
            errors.length(1);
            errors[0].severity = Tango::ERR;
            errors[0].desc = Tango::string_dup("unknown error");
            errors[0].reason = Tango::string_dup("unknown exception caught");
            errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
            DevFailed df(errors);
            arp.insert(AsynchRequestRepValue(id, AsynchRequest(-1, a, df)));
        }
    }
    return id;
}

//-----------------------------------------------------------------------------
GroupAttrReplyList GroupDeviceElement::read_attribute_reply_i(long id, long tmo)
{
    GroupAttrReplyList rl;

    //- search actual asynch request id in this' repository
    auto it = arp.find(id);
    if(it == arp.end())
    {
        //- error description
        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);
        DevFailed df(errors);
        //- populate the returned vector
        rl.push_back(GroupAttrReply(get_name(), "unknown", df));
        return rl;
    }

    //- no reply if this group element is diabled
    if(!is_enabled())
    {
        rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[0], false));
        //- remove request from repository
        arp.erase(it);
        return rl;
    }

    //- if got error during asynch call then previously stored exception is the reply
    if(it->second.rq_id == -1)
    {
        rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[0], it->second.rq_ex));
        //- remove request from repository
        arp.erase(it);
        return rl;
    }

    //- get reply from device
    try
    {
        DeviceAttribute *da = dev_proxy()->read_attribute_reply(it->second.rq_id, tmo);
        if(da == nullptr)
        {
            Tango::DevErrorList errors(1);
            errors.length(1);
            errors[0].severity = Tango::ERR;
            errors[0].desc = Tango::string_dup("internal error");
            errors[0].reason = Tango::string_dup("Tango::DeviceProxy::read_attribute_reply returned NULL");
            errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
            DevFailed df(errors);
            rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[0], df));
        }
        else
        {
            if(da->has_failed())
            {
                DevFailed df(da->get_err_stack());
                rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[0], df));
            }
            else
            {
                rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[0], *da));
            }
            delete da;
        }
    }
    catch(const Tango::DevFailed &df)
    {
        rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[0], df));
    }
    catch(...)
    {
        //- create a pseudo devfailed
        Tango::DevErrorList errors(1);
        errors.length(1);
        errors[0].severity = Tango::ERR;
        errors[0].desc = Tango::string_dup("unknown error");
        errors[0].reason = Tango::string_dup("unknown exception caught");
        errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
        DevFailed df(errors);
        rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[0], df));
    }

    //- remove request from repository
    arp.erase(it);

    return rl;
}

//-----------------------------------------------------------------------------
long GroupDeviceElement::read_attributes_asynch_i(const std::vector<std::string> &al, TANGO_UNUSED(bool fwd), long id)
{
    if(!is_enabled())
    {
        arp.insert(AsynchRequestRepValue(id, AsynchRequest(id, al, false)));
    }
    else
    {
        try
        {
            long ari = dev_proxy()->read_attributes_asynch(const_cast<std::vector<std::string> &>(al));
            arp.insert(AsynchRequestRepValue(id, AsynchRequest(ari, al)));
        }
        catch(const Tango::DevFailed &df)
        {
            arp.insert(AsynchRequestRepValue(id, AsynchRequest(-1, al, df)));
        }
        catch(...)
        {
            //- create a pseudo devfailed
            Tango::DevErrorList errors(1);
            errors.length(1);
            errors[0].severity = Tango::ERR;
            errors[0].desc = Tango::string_dup("unknown error");
            errors[0].reason = Tango::string_dup("unknown exception caught");
            errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
            DevFailed df(errors);
            arp.insert(AsynchRequestRepValue(id, AsynchRequest(-1, al, df)));
        }
    }
    return id;
}

//-----------------------------------------------------------------------------
GroupAttrReplyList GroupDeviceElement::read_attributes_reply_i(long id, long tmo)
{
    unsigned int a = 0;
    GroupAttrReplyList rl;

    //- search actual asynch request id in this' repository
    auto it = arp.find(id);
    if(it == arp.end())
    {
        //- error description
        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);
        DevFailed df(errors);
        //- populate the returned vector
        rl.push_back(GroupAttrReply(get_name(), "unknown", df));
        return rl;
    }

    //- no reply if this group element is diabled
    if(!is_enabled())
    {
        for(a = 0; a < it->second.obj_names.size(); a++)
        {
            rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[a], false));
        }
        //- remove request from repository
        arp.erase(it);
        return rl;
    }

    //- if got error during asynch call then previously stored exception is the reply
    if(it->second.rq_id == -1)
    {
        for(a = 0; a < it->second.obj_names.size(); a++)
        {
            rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[a], it->second.rq_ex));
        }
        //- remove request from repository
        arp.erase(it);
        return rl;
    }

    //- get reply from device
    try
    {
        std::vector<DeviceAttribute> *dal = dev_proxy()->read_attributes_reply(it->second.rq_id, tmo);
        if(dal == nullptr)
        {
            Tango::DevErrorList errors(1);
            errors.length(1);
            errors[0].severity = Tango::ERR;
            errors[0].desc = Tango::string_dup("internal error");
            errors[0].reason = Tango::string_dup("Tango::DeviceProxy::read_attribute_reply returned NULL");
            errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
            DevFailed df(errors);
            for(a = 0; a < it->second.obj_names.size(); a++)
            {
                rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[a], df));
            }
        }
        else
        {
            for(a = 0; a < it->second.obj_names.size(); a++)
            {
                if((*dal)[a].has_failed())
                {
                    DevFailed df((*dal)[a].get_err_stack());
                    rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[a], df));
                }
                else
                {
                    rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[a], (*dal)[a]));
                }
            }
            delete dal;
        }
    }
    catch(const Tango::DevFailed &df)
    {
        for(a = 0; a < it->second.obj_names.size(); a++)
        {
            rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[a], df));
        }
    }
    catch(...)
    {
        //- create a pseudo devfailed
        Tango::DevErrorList errors(1);
        errors.length(1);
        errors[0].severity = Tango::ERR;
        errors[0].desc = Tango::string_dup("unknown error");
        errors[0].reason = Tango::string_dup("unknown exception caught");
        errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
        DevFailed df(errors);
        for(a = 0; a < it->second.obj_names.size(); a++)
        {
            rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[a], df));
        }
    }

    //- remove request from repository
    arp.erase(it);

    return rl;
}

//-----------------------------------------------------------------------------
long GroupDeviceElement::write_attribute_asynch_i(const DeviceAttribute &d, TANGO_UNUSED(bool fwd), long id)
{
    if(!is_enabled())
    {
        arp.insert(AsynchRequestRepValue(id, AsynchRequest(id, const_cast<DeviceAttribute &>(d).get_name(), false)));
    }
    else
    {
        try
        {
            long ari = dev_proxy()->write_attribute_asynch(const_cast<DeviceAttribute &>(d));
            arp.insert(AsynchRequestRepValue(id, AsynchRequest(ari, const_cast<DeviceAttribute &>(d).get_name())));
        }
        catch(const Tango::DevFailed &df)
        {
            arp.insert(AsynchRequestRepValue(id, AsynchRequest(-1, const_cast<DeviceAttribute &>(d).get_name(), df)));
        }
        catch(...)
        {
            //- create a pseudo devfailed
            Tango::DevErrorList errors(1);
            errors.length(1);
            errors[0].severity = Tango::ERR;
            errors[0].desc = Tango::string_dup("unknown error");
            errors[0].reason = Tango::string_dup("unknown exception caught");
            errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
            DevFailed df(errors);
            arp.insert(AsynchRequestRepValue(id, AsynchRequest(-1, const_cast<DeviceAttribute &>(d).get_name(), df)));
        }
    }
    return id;
}

//-----------------------------------------------------------------------------
GroupReplyList GroupDeviceElement::write_attribute_reply_i(long id, long tmo)
{
    GroupReplyList rl;
    //- search actual asynch request id in this' repository
    auto it = arp.find(id);
    if(it == arp.end())
    {
        //- error description
        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);
        DevFailed df(errors);
        //- populate the returned vector
        rl.push_back(GroupReply(get_name(), "unknown", df));
        return rl;
    }

    //- no reply if this group element is diabled
    if(!is_enabled())
    {
        rl.push_back(GroupAttrReply(get_name(), it->second.obj_names[0], false));
        //- remove request from repository
        arp.erase(it);
        return rl;
    }

    //- if got error during asynch call then previously stored exception is the reply
    if(it->second.rq_id == -1)
    {
        rl.push_back(GroupReply(get_name(), it->second.obj_names[0], it->second.rq_ex));
        //- remove request from repository
        arp.erase(it);
        return rl;
    }

    //- get reply from device
    try
    {
        dev_proxy()->write_attribute_reply(it->second.rq_id, tmo);
        rl.push_back(GroupReply(get_name(), it->second.obj_names[0]));
    }
    catch(const Tango::DevFailed &df)
    {
        rl.push_back(GroupReply(get_name(), it->second.obj_names[0], df));
    }
    catch(...)
    {
        //- create a pseudo devfailed
        Tango::DevErrorList errors(1);
        errors.length(1);
        errors[0].severity = Tango::ERR;
        errors[0].desc = Tango::string_dup("unknown error");
        errors[0].reason = Tango::string_dup("unknown exception caught");
        errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
        DevFailed df(errors);
        rl.push_back(GroupReply(get_name(), it->second.obj_names[0], df));
    }
    //- remove request from repository
    arp.erase(it);
    return rl;
}

//-----------------------------------------------------------------------------
void GroupDeviceElement::dump(TANGO_UNUSED(int indent_level))
{
#ifdef _LOCAL_DEBUGGING
    for(int i = 0; i < indent_level; i++)
    {
        TANGO_LOG << "\t";
    }
    TANGO_LOG << "|- DEVICE: " << get_name() << std::endl;
#endif
}

//-----------------------------------------------------------------------------
void GroupDeviceElement::dump(TangoSys_OMemStream &oms, int indent_level)
{
    for(int i = 0; i < indent_level; i++)
    {
        oms << "\t";
    }
    oms << "|- DEVICE: " << get_name() << std::endl;
}

//-----------------------------------------------------------------------------
bool GroupDeviceElement::is_connected()
{
    return dev_proxy()->is_connected();
}

//-----------------------------------------------------------------------------
bool GroupDeviceElement::is_device_i()
{
    return true;
}

//-----------------------------------------------------------------------------
bool GroupDeviceElement::is_group_i()
{
    return false;
}

//-----------------------------------------------------------------------------
long GroupDeviceElement::get_size_i(TANGO_UNUSED(bool fwd))
{
    return 1;
}

//-----------------------------------------------------------------------------
// the following function could throw an exception, see DeviceProxy::set_timeout_millis()
void GroupDeviceElement::set_timeout_millis(int tmo)
{
    if(tmo >= 0)
    {
        dev_proxy()->set_timeout_millis(tmo);
    }
}

} // namespace Tango
