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

#include <tango/server/Device_4Impl.h>

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

#include <tango/server/Command.h>
#include <tango/server/EventSupplier.h>

namespace Tango
{

//+-------------------------------------------------------------------------
//
// method :         Device_4Impl::Device_4Impl
//
// description :     constructors for the device_impl class from the
//            class object pointer, the device name,
//            the description field, the state and the status.
//            Device_4Impl inherits from DeviceImpl. These constructors
//            simply call the correct DeviceImpl class
//            constructor
//
//--------------------------------------------------------------------------

Device_4Impl::Device_4Impl(DeviceClass *device_class, const std::string &dev_name) :
    Device_3Impl(device_class, dev_name),
    ext_4(nullptr)
{
    idl_version = 4;
}

Device_4Impl::Device_4Impl(DeviceClass *device_class, const std::string &dev_name, const std::string &desc) :
    Device_3Impl(device_class, dev_name, desc),
    ext_4(nullptr)
{
    idl_version = 4;
}

Device_4Impl::Device_4Impl(DeviceClass *device_class,
                           const std::string &dev_name,
                           const std::string &desc,
                           Tango::DevState dev_state,
                           const std::string &dev_status) :
    Device_3Impl(device_class, dev_name, desc, dev_state, dev_status),
    ext_4(nullptr)
{
    idl_version = 4;
}

Device_4Impl::Device_4Impl(DeviceClass *device_class,
                           const char *dev_name,
                           const char *desc,
                           Tango::DevState dev_state,
                           const char *dev_status) :
    Device_3Impl(device_class, dev_name, desc, dev_state, dev_status),
    ext_4(nullptr)
{
    idl_version = 4;
}

//+-------------------------------------------------------------------------
//
// method :         Device_4Impl::read_attribute_history_4
//
// description :     CORBA operation to read attribute value history from
//            the polling buffer.
//
// argument: in :    - name : attribute name
//            - n : history depth (in record number)
//
// This method returns a pointer to a DevAttrHistory_4 structure
//
//--------------------------------------------------------------------------

Tango::DevAttrHistory_4 *Device_4Impl::read_attribute_history_4(const char *name, CORBA::Long n)
{
    TangoMonitor &mon = get_poll_monitor();
    AutoTangoMonitor sync(&mon);

    TANGO_LOG_DEBUG << "Device_4Impl::read_attribute_history_4 arrived, requested history depth = " << n << std::endl;

    //
    // Record operation request in black box
    //

    blackbox_ptr->insert_op(Op_Read_Attr_history_4);

    Tango::DevAttrHistory_4 *back = nullptr;
    std::vector<PollObj *> &poll_list = get_poll_obj_list();
    long nb_poll = poll_list.size();

    //
    // Check that the device supports this attribute. This method returns an
    // exception in case of unsupported attribute
    //

    Attribute &att = dev_attr->get_attr_by_name(name);

    std::string attr_str(name);
    std::transform(attr_str.begin(), attr_str.end(), attr_str.begin(), ::tolower);

    //
    // Check that the wanted attribute is polled
    //

    long j;
    PollObj *polled_attr = nullptr;
    for(j = 0; j < nb_poll; j++)
    {
        if((poll_list[j]->get_type() == Tango::POLL_ATTR) && (poll_list[j]->get_name() == attr_str))
        {
            polled_attr = poll_list[j];
            break;
        }
    }
    if(polled_attr == nullptr)
    {
        TangoSys_OMemStream o;
        o << "Attribute " << attr_str << " not polled" << std::ends;
        TANGO_THROW_EXCEPTION(API_AttrNotPolled, o.str());
    }

    //
    // Check that some data is available in cache
    //

    if(polled_attr->is_ring_empty())
    {
        TangoSys_OMemStream o;
        o << "No data available in cache for attribute " << attr_str << std::ends;
        TANGO_THROW_EXCEPTION(API_NoDataYet, o.str());
    }

    //
    // Set the number of returned records
    //

    long in_buf = polled_attr->get_elt_nb_in_buffer();
    n = std::min<long>(n, in_buf);

    //
    // Allocate memory for the returned value
    //

    try
    {
        back = new Tango::DevAttrHistory_4;
        back->dates.length(n);
    }
    catch(std::bad_alloc &)
    {
        TANGO_THROW_EXCEPTION(API_MemoryAllocation, "Can't allocate memory in server");
    }

    //
    // Init attribute name in the returned structure
    //

    back->name = Tango::string_dup(name);

    //
    // Get attribute value history
    // Trick: To identify the state used as an attribute from a classical attribute with type
    // DEV_STATE, use DEV_VOID for state as data type.
    //

    if(att.get_name_lower() == "state")
    {
        polled_attr->get_attr_history(n, back, Tango::DEV_VOID, att.get_data_format());
    }
    else
    {
        polled_attr->get_attr_history(n, back, att.get_data_type(), att.get_data_format());
    }

    TANGO_LOG_DEBUG << "Leaving Device_4Impl::read_attribute_history_4 method" << std::endl;
    return back;
}

//+-------------------------------------------------------------------------
//
// method :         Device_2Impl::command_inout_history_4
//
// description :     CORBA operation to read command result history from
//            the polling buffer.
//
// argument: in :    - command : command name
//            - n : history depth (in record number)
//
// This method returns a pointer to a DevCmdHistoryList with one
// DevCmdHistory structure for each command record
//
//--------------------------------------------------------------------------

Tango::DevCmdHistory_4 *Device_4Impl::command_inout_history_4(const char *command, CORBA::Long n)
{
    TangoMonitor &mon = get_poll_monitor();
    AutoTangoMonitor sync(&mon);

    TANGO_LOG_DEBUG << "Device_4Impl::command_inout_history_4 arrived" << std::endl;
    Tango::DevCmdHistory_4 *back = nullptr;

    std::string cmd_str(command);

    //
    // Record operation request in black box
    //

    blackbox_ptr->insert_op(Op_Command_inout_history_4);

    //
    // Check that device supports this command. Also checks if the device
    // implements IDL >=3 when state or status history is requested
    //

    std::transform(cmd_str.begin(), cmd_str.end(), cmd_str.begin(), ::tolower);
    check_command_exists(cmd_str);

    bool status_cmd = false;
    bool state_cmd = false;

    long vers = get_dev_idl_version();

    if(vers >= 3)
    {
        if(cmd_str == "state")
        {
            state_cmd = true;
        }
        else if(cmd_str == "status")
        {
            status_cmd = true;
        }
    }

    //
    // Check that the command is polled
    //

    PollObj *polled_cmd = nullptr;
    std::vector<PollObj *> &poll_list = get_poll_obj_list();
    unsigned long i;
    for(i = 0; i < poll_list.size(); i++)
    {
        if(poll_list[i]->get_name() == cmd_str)
        {
            if((state_cmd) || (status_cmd))
            {
                if(poll_list[i]->get_type() == Tango::POLL_ATTR)
                {
                    polled_cmd = poll_list[i];
                }
            }
            else
            {
                if(poll_list[i]->get_type() == Tango::POLL_CMD)
                {
                    polled_cmd = poll_list[i];
                }
            }
        }
    }

    if(polled_cmd == nullptr)
    {
        TangoSys_OMemStream o;
        o << "Command " << cmd_str << " not polled" << std::ends;
        TANGO_THROW_EXCEPTION(API_CmdNotPolled, o.str());
    }

    //
    // Check that some data is available in cache
    //

    if(polled_cmd->is_ring_empty())
    {
        TangoSys_OMemStream o;
        o << "No data available in cache for command " << cmd_str << std::ends;
        TANGO_THROW_EXCEPTION(API_NoDataYet, o.str());
    }

    //
    // Set the number of returned records
    //

    long in_buf = polled_cmd->get_elt_nb_in_buffer();
    n = std::min<long>(n, in_buf);

    //
    // Allocate memory for the returned value
    //

    try
    {
        back = new Tango::DevCmdHistory_4;
        back->dates.length(n);
    }
    catch(std::bad_alloc &)
    {
        TANGO_THROW_EXCEPTION(API_MemoryAllocation, "Can't allocate memory in server");
    }

    //
    // Get command result history
    // Warning : Starting with Tango V5 (IDL 3), state and status are polled
    // as attributes but could also be retrieved as commands. In this case,
    // retrieved the history as attributes and transfer this as command
    //

    if((state_cmd) || (status_cmd))
    {
        Tango::DevAttrHistory_4 *back_attr = nullptr;
        try
        {
            back_attr = new Tango::DevAttrHistory_4;
            back_attr->dates.length(n);
        }
        catch(std::bad_alloc &)
        {
            TANGO_THROW_EXCEPTION(API_MemoryAllocation, "Can't allocate memory in server");
        }

        if(status_cmd)
        {
            polled_cmd->get_attr_history(n, back_attr, Tango::DEV_STRING, SCALAR);

            back->dates = back_attr->dates;
            back->errors = back_attr->errors;
            back->errors_array = back_attr->errors_array;
            back->dims = back_attr->r_dims;
            back->dims_array = back_attr->r_dims_array;
            back->cmd_type = Tango::DEV_STRING;
            back->value = back_attr->value;
        }
        else
        {
            //
            // Trick: To identify the state used as an attribute from a classical attribute with type
            // DEV_STATE, use DEV_VOID for state as data type.
            //

            polled_cmd->get_attr_history(n, back_attr, Tango::DEV_VOID, SCALAR);

            back->dates = back_attr->dates;
            back->errors = back_attr->errors;
            back->errors_array = back_attr->errors_array;
            back->dims = back_attr->r_dims;
            back->dims_array = back_attr->r_dims_array;
            back->cmd_type = Tango::DEV_STATE;
            back->value = back_attr->value;
        }
        delete back_attr;
    }
    else
    {
        //
        // Get command output data type and fill returned structure
        //

        std::string cmd(command);
        std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::tolower);

        Tango::CmdArgType cmd_type;
        std::vector<Tango::Command *> &cmd_list = device_class->get_command_list();
        for(unsigned int loop = 0; loop < cmd_list.size(); loop++)
        {
            std::string cmd_name(cmd_list[loop]->get_name());
            std::transform(cmd_name.begin(), cmd_name.end(), cmd_name.begin(), ::tolower);
            if(cmd_name == cmd)
            {
                cmd_type = cmd_list[loop]->get_out_type();
                break;
            }
        }
        polled_cmd->get_cmd_history(n, back, cmd_type);
    }

    TANGO_LOG_DEBUG << "Leaving Device_4Impl::command_inout_history_4 method" << std::endl;
    return back;
}

//+-------------------------------------------------------------------------
//
// method :         Device_4Impl::command_inout_4
//
// description :     Method called for each command_inout operation executed
//            from any client on a Tango device version 4.
//
//--------------------------------------------------------------------------

CORBA::Any *Device_4Impl::command_inout_4(const char *in_cmd,
                                          const CORBA::Any &in_data,
                                          Tango::DevSource source,
                                          const Tango::ClntIdent &cl_id)
{
    // Reference comment for multiple locations:
    // HERE, WE ARE REPLYING TO A CLIENT  RPC - WE CAN HARDLY FIND A MORE "UPSTREAM"
    // POINT IN THE CALL STACK TO SETUP THE TELEMETRY CONTEXT - WE CONSEQUENTLY HAVE
    // TO CONSIDER THIS LOCATION AS THE ENTRY POINT OF THE CORRESPONDING TANGO RPC
    TANGO_TELEMETRY_KERNEL_TRACE_BEGIN(({{"tango.operation.argument", in_cmd}}));

    TANGO_LOG_DEBUG << "Device_4Impl::command_inout_4 arrived, source = " << source << ", command = " << in_cmd
                    << std::endl;

    //
    // Record operation request in black box
    //

    blackbox_ptr->insert_cmd_cl_ident(in_cmd, cl_id, idl_version, source);

    //
    // Do not check lock validity if State or Status is requested using command
    //

    bool state_status_cmd = false;
    if((TG_strcasecmp(in_cmd, "state") == 0) || (TG_strcasecmp(in_cmd, "status") == 0))
    {
        state_status_cmd = true;
    }

    //
    // Check if the device is locked and if it is valid
    // If the lock is not valid any more, clear it
    //

    if(!state_status_cmd)
    {
        check_lock("command_inout4", in_cmd);
    }

    //
    // Call the Device_2Impl command_inout
    //

    store_in_bb = false;
    return (command_inout_2(in_cmd, in_data, source));

    TANGO_TELEMETRY_TRACE_END();
}

//+-------------------------------------------------------------------------
//
// method :         Device_4Impl::read_attributes_4
//
// description :     Method called for each read_attributes operation executed
//            from any client on a Tango device version 4.
//
//--------------------------------------------------------------------------

Tango::AttributeValueList_4 *Device_4Impl::read_attributes_4(const Tango::DevVarStringArray &names,
                                                             Tango::DevSource source,
                                                             const Tango::ClntIdent &cl_id)
{
    // see comment in Device_4Impl::command_inout_4
    TANGO_TELEMETRY_KERNEL_TRACE_BEGIN(({}));

    TANGO_LOG_DEBUG << "Device_4Impl::read_attributes_4 arrived for dev " << get_name() << ", att[0] = " << names[0]
                    << std::endl;

    //
    // Record operation request in black box
    //

    if(store_in_bb)
    {
        blackbox_ptr->insert_attr(names, cl_id, idl_version, source);
    }
    store_in_bb = true;

    //
    // Build a sequence with the names of the attribute to be read.
    // This is necessary in case of the "AllAttr" shortcut is used
    // If all attributes are wanted, build this list
    //

    unsigned long nb_names = names.length();
    unsigned long nb_dev_attr = dev_attr->get_attr_nb();
    Tango::DevVarStringArray real_names(nb_names);
    unsigned long i;

    if(nb_names == 1)
    {
        std::string att_name(names[0]);
        if(att_name == AllAttr)
        {
            real_names.length(nb_dev_attr);
            for(i = 0; i < nb_dev_attr; i++)
            {
                real_names[i] = dev_attr->get_attr_by_ind(i).get_name().c_str();
            }
        }
        else
        {
            real_names = names;
        }
    }
    else
    {
        real_names = names;
    }
    nb_names = real_names.length();

    //
    // Allocate memory for the AttributeValue structures
    //

    AttributeIdlData aid;

    try
    {
        auto *l_back = new Tango::AttributeValue_4[nb_names];
        aid.data_4 = new Tango::AttributeValueList_4(nb_names, nb_names, l_back, true);

        for(unsigned long loop = 0; loop < nb_names; loop++)
        {
            (*aid.data_4)[loop].value.union_no_data(true);
        }
    }
    catch(std::bad_alloc &)
    {
        TANGO_THROW_EXCEPTION(API_MemoryAllocation, "Can't allocate memory in server");
    }

    //
    // Store source parameter (used in case of forwarded attribute(s))
    //

    set_call_source(source);

    //
    // If the source parameter specifies device, call the read_attributes method
    // which does not throw exception except for major fault (cant allocate
    // memory,....)
    //

    std::vector<long> idx_in_back;

    if(source == Tango::DEV)
    {
        try
        {
            AutoTangoMonitor sync(this);
            read_attributes_no_except(real_names, aid, false, idx_in_back);
        }
        catch(...)
        {
            delete aid.data_4;
            throw;
        }
    }
    else if(source == Tango::CACHE)
    {
        //
        // If the device has some forwarded attribute, do not try to get data from the local cache but get it
        // from the root device
        // Create two arrays with local attribute names and fwd attribute names
        //

        bool fwd_att_in_call = false;
        Tango::DevVarStringArray local_names(nb_names);
        Tango::DevVarStringArray fwd_names(nb_names);

        if(with_fwd_att)
        {
            for(size_t loop = 0; loop < nb_names; loop++)
            {
                Attribute &att = dev_attr->get_attr_by_name(real_names[loop]);

                if(att.is_fwd_att())
                {
                    size_t nb_fwd = 0;
                    fwd_att_in_call = true;
                    nb_fwd++;
                    fwd_names.length(nb_fwd);
                    fwd_names[nb_fwd - 1] = real_names[loop];
                    idx_in_back.push_back(loop);
                }
                else
                {
                    size_t nb_local = 0;
                    nb_local++;
                    local_names.length(nb_local);
                    local_names[nb_local - 1] = real_names[loop];
                }
            }
        }

        if(!fwd_att_in_call)
        {
            //
            // No fwd attributes in call, get values from local cache
            //

            try
            {
                TangoMonitor &mon = get_poll_monitor();
                AutoTangoMonitor sync(&mon);
                read_attributes_from_cache(real_names, aid);
            }
            catch(...)
            {
                delete aid.data_4;
                throw;
            }
        }
        else
        {
            //
            // Some fwd attributes in call: First get data for local attributes then get data for forwarded attributes
            // from root devices
            //

            try
            {
                TangoMonitor &mon = get_poll_monitor();
                {
                    AutoTangoMonitor sync(&mon);
                    read_attributes_from_cache(local_names, aid);
                }

                {
                    AutoTangoMonitor sync(this);
                    read_attributes_no_except(fwd_names, aid, true, idx_in_back);
                    idx_in_back.clear();
                }
            }
            catch(...)
            {
                delete aid.data_4;
                throw;
            }
        }
    }
    else
    {
        //
        // It must be now CACHE_DEVICE (no other choice), first try to get
        // values from cache
        //

        try
        {
            TangoMonitor &mon = get_poll_monitor();
            AutoTangoMonitor sync(&mon);
            read_attributes_from_cache(real_names, aid);
        }
        catch(...)
        {
            delete aid.data_4;
            throw;
        }

        //
        // Now, build the list of attributes which it was not possible
        // to get their value from cache
        //

        Tango::DevVarStringArray names_from_device(nb_names);
        long nb_attr = 0;

        for(i = 0; i < nb_names; i++)
        {
            long nb_err = (*aid.data_4)[i].err_list.length();
            if(nb_err != 0)
            {
                nb_err--;
                if((strcmp((*aid.data_4)[i].err_list[nb_err].reason, API_AttrNotPolled) == 0) ||
                   (strcmp((*aid.data_4)[i].err_list[nb_err].reason, API_NoDataYet) == 0) ||
                   (strcmp((*aid.data_4)[i].err_list[nb_err].reason, API_NotUpdatedAnyMore) == 0) ||
                   (strcmp((*aid.data_4)[i].err_list[nb_err].origin, "DServer::add_obj_polling") == 0))
                {
                    nb_attr++;
                    names_from_device.length(nb_attr);
                    names_from_device[nb_attr - 1] = real_names[i];
                    idx_in_back.push_back(i);

                    (*aid.data_4)[i].err_list.length(0);
                }
            }
        }

        if(nb_attr != 0)
        {
            //
            // Try to get their values from device
            //

            try
            {
                AutoTangoMonitor sync(this);
                read_attributes_no_except(names_from_device, aid, true, idx_in_back);
            }
            catch(...)
            {
                delete aid.data_4;
                throw;
            }
        }
    }

    return aid.data_4;

    TANGO_TELEMETRY_TRACE_END();
}

//+-------------------------------------------------------------------------
//
// method :         Device_4Impl::write_attributes_4
//
// description :     CORBA operation to write attribute(s) value
//
// argument: in :    - values: The new attribute(s) value to be set.
//
//--------------------------------------------------------------------------

void Device_4Impl::write_attributes_4(const Tango::AttributeValueList_4 &values, const Tango::ClntIdent &cl_id)
{
    AutoTangoMonitor sync(this, true);

    // see comment in Device_4Impl::command_inout_4
    TANGO_TELEMETRY_KERNEL_TRACE_BEGIN(({}));

    TANGO_LOG_DEBUG << "Device_4Impl::write_attributes_4 arrived" << std::endl;

    //
    // Record operation request in black box
    //

    if(store_in_bb)
    {
        blackbox_ptr->insert_attr(values, cl_id, idl_version);
    }
    store_in_bb = true;

    //
    // Check if the device is locked and by who
    //

    check_lock("write_attributes_4");

    //
    // Call the Device_3Impl write_attributes
    //

    write_attributes_34(nullptr, &values);

    TANGO_TELEMETRY_TRACE_END();
}

//+-------------------------------------------------------------------------
//
// method :        Device_4Impl::set_attribute_config_4
//
// description :    CORBA operation to set attribute configuration locally
//            and in the Tango database
//
// argument: in :    - new_conf: The new attribute(s) configuration. One
//                    AttributeConfig structure is needed for each
//                    attribute to update
//
//--------------------------------------------------------------------------

void Device_4Impl::set_attribute_config_4(const Tango::AttributeConfigList_3 &new_conf, const Tango::ClntIdent &cl_id)
{
    AutoTangoMonitor sync(this, true);

    // see comment in Device_4Impl::command_inout_4
    TANGO_TELEMETRY_KERNEL_TRACE_BEGIN(({}));

    TANGO_LOG_DEBUG << "Device_4Impl::set_attribute_config_4 arrived" << std::endl;

    //
    // The attribute conf. is protected by two monitors. One protects access between
    // get and set attribute conf. The second one protects access between set and
    // usage. This is the classical device monitor
    //

    TangoMonitor &mon1 = get_att_conf_monitor();
    AutoTangoMonitor sync1(&mon1);

    //
    // Record operation request in black box
    //

    blackbox_ptr->insert_op(Op_Set_Attr_Config_4, cl_id);

    //
    // Check if the device is locked and by who
    //

    check_lock("set_attribute_config_4");

    //
    // Call the Device_3Impl set_attribute_config
    //

    store_in_bb = false;
    set_attribute_config_3_local(new_conf, false, idl_version);

    TANGO_TELEMETRY_TRACE_END();
}

//+-------------------------------------------------------------------------
//
// method :         Device_4Impl::write_read_attributes_4
//
// description :     CORBA operation to write then read attribute(s) value
//                    Nowadays, it is possible to write then read attribute for
//                    only 1 attribute at a time even if the IDL is written for
//                    several attribute
//
// argument: in :    - values: The new attributes value to be set.
//
//--------------------------------------------------------------------------

Tango::AttributeValueList_4 *Device_4Impl::write_read_attributes_4(const Tango::AttributeValueList_4 &values,
                                                                   const Tango::ClntIdent &cl_id)
{
    AutoTangoMonitor sync(this, true);

    // see comment in Device_4Impl::command_inout_4
    TANGO_TELEMETRY_KERNEL_TRACE_BEGIN(({}));

    TANGO_LOG_DEBUG << "Device_4Impl::write_read_attributes_4 arrived" << std::endl;

    //
    // Record operation request in black box
    //

    Tango::DevVarStringArray dvsa;
    dvsa.length(1);
    dvsa[0] = Tango::string_dup(values[0].name);

    blackbox_ptr->insert_wr_attr(values, dvsa, cl_id, idl_version);

    //
    // Check if the device is locked and by who
    //

    check_lock("write_read_attributes_4");

    //
    // Check the attribute write type (only READ_WRITE or READ_WITH_WRITE allowed)
    //

    Tango::Attribute &att = dev_attr->get_attr_by_name(values[0].name);
    Tango::AttrWriteType awt = att.get_writable();
    if((awt == Tango::READ) || (awt == Tango::WRITE))
    {
        TangoSys_OMemStream o;
        o << "Attribute " << values[0].name << " is not a READ_WRITE or READ_WITH_WRITE attribute" << std::ends;

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

    Tango::AttributeValueList_4 *read_val_ptr;

    //
    // First, write the attribute
    //

    store_in_bb = false;
    write_attributes_4(values, cl_id);

    //
    // Now, read the attribute
    //

    Tango::DevVarStringArray att_name(1);
    att_name.length(1);
    att_name[0] = Tango::string_dup(values[0].name);
    Tango::ClntIdent dummy_cl_id;
    Tango::CppClntIdent cci = 0;
    dummy_cl_id.cpp_clnt(cci);

    store_in_bb = false;
    read_val_ptr = read_attributes_4(att_name, Tango::DEV, dummy_cl_id);

    return read_val_ptr;

    TANGO_TELEMETRY_TRACE_END();
}

} // namespace Tango
