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

#include <tango/internal/server/basiccommand.h>

#include <tango/client/ApiExcept.h>
#include <tango/client/Database.h>
#include <tango/client/DbClass.h>
#include <tango/client/DbDevExportInfo.h>
#include <tango/client/NamedDevFailedList.h>
#include <tango/common/defines.h>
#include <tango/server/BlackBox.h>
#include <tango/server/Device_3Impl.h>
#include <tango/server/DServer.h>
#include <tango/server/DServerSignal.h>
#include <tango/server/EventSupplier.h>
#include <tango/server/LogAdapter.h>
#include <tango/server/Logging.h>
#include <tango/server/MultiClassAttribute.h>
#include <tango/server/MultiClassPipe.h>
#include <tango/server/Pipe.h>

namespace Tango
{

static void lower_cmd_name(std::string &cmd)
{
    std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::tolower);
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::DeviceClass(std::string &s)
//
// description :
//        DeviceClass constructor. Protected method which will be called automatically by the compiler.
//
//-------------------------------------------------------------------------------------------------------------------

DeviceClass::DeviceClass(const std::string &s) :
    name(s),
    ext(new DeviceClassExt),
    only_one("class " + s)

{
    TANGO_LOG_DEBUG << "Entering DeviceClass::DeviceClass for class " << name << std::endl;

    //
    // Create the associated DbClass object
    //

    db_class = new DbClass(name, Tango::Util::instance()->get_database());

    //
    // initialise command_list with State, Status and Init
    //

    try
    {
        command_list.push_back(new DevStatusCmd("Status", Tango::DEV_VOID, Tango::DEV_STRING));
        command_list[0]->set_out_type_desc("Device status");
        command_list.push_back(new DevStateCmd("State", Tango::DEV_VOID, Tango::DEV_STATE));
        command_list[1]->set_out_type_desc("Device state");
        command_list.push_back(new DevInitCmd("Init", Tango::DEV_VOID, Tango::DEV_VOID));
    }
    catch(std::bad_alloc &)
    {
        if(!command_list.empty())
        {
            for(unsigned long i = 0; i < command_list.size(); i++)
            {
                delete command_list[i];
            }
            command_list.clear();
        }
        throw;
    }

    //
    // Retrieve basic class resource
    //

    get_class_system_resource();

    //
    // Create the multi class attribute object
    //

    class_attr = new MultiClassAttribute();

    //
    // Create the multi class pipe object
    //

    class_pipe = new MultiClassPipe();

    //
    // Give a default value for device type
    //

    type = NotSet;

    TANGO_LOG_DEBUG << "Leaving DeviceClass::DeviceClass for class " << name << std::endl;
}

//+-------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::get_class_system_resource(std::string &s)
//
// description :
//        Method to retrieve some basic class resource(s)
//
//-------------------------------------------------------------------------------------------------------------------

void DeviceClass::get_class_system_resource()
{
    //
    // Try to retrieve the class resource doc_url
    //

    Tango::Util *tg = Tango::Util::instance();
    if(tg->use_db())
    {
        Database *db = tg->get_database();
        DbData db_data;

        db_data.emplace_back("doc_url");
        db_data.emplace_back("cvs_tag");
        db_data.emplace_back("cvs_location");
        db_data.emplace_back("AllowedAccessCmd");
        db_data.emplace_back("svn_tag");
        db_data.emplace_back("svn_location");

        try
        {
            db->get_class_property(name, db_data, tg->get_db_cache());
        }
        catch(Tango::DevFailed &)
        {
            TangoSys_OMemStream o;
            o << "Database error while trying to retrieve properties for class " << name.c_str() << std::ends;

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

        if(!db_data[1].is_empty())
        {
            db_data[1] >> cvs_tag;
        }
        if(!db_data[2].is_empty())
        {
            db_data[2] >> cvs_location;
        }

        //
        // Init allowed commands vector (in lowercase letters)
        //

        if(!db_data[3].is_empty())
        {
            db_data[3] >> allowed_cmds;
            for_each(allowed_cmds.begin(), allowed_cmds.end(), lower_cmd_name);
        }

        if(db_data[0].is_empty())
        {
            TANGO_LOG_DEBUG << "doc_url property for class " << name << " is not defined in database" << std::endl;
            try
            {
                db->get_class_property("Default", db_data, tg->get_db_cache());
            }
            catch(Tango::DevFailed &)
            {
                TangoSys_OMemStream o;
                o << "Database error while trying to retrieve properties for class " << name.c_str() << std::ends;

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

            if(db_data[0].is_empty())
            {
                doc_url = DefaultDocUrl;
            }
            else
            {
                db_data[0] >> doc_url;
            }
        }
        else
        {
            db_data[0] >> doc_url;
        }

        if(!db_data[4].is_empty())
        {
            db_data[4] >> svn_tag;
        }
        if(!db_data[5].is_empty())
        {
            db_data[5] >> svn_location;
        }
    }
    else
    {
        doc_url = DefaultDocUrl;
    }
}

//---------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::set_memorized_values()
//
// description :
//        Write the memorized attribute with the value stored in database
//
// argument :
//         in :
//            - all : Flag set to true if memorized values must be applied to all class devices
//            - idx : Index of the device in the device_list vector of the device for which memorized values must
//                    be applied. Used if "all" is set to false
//            - from_init : Flag set to true if the method is called due to a device Init command execution
//
//--------------------------------------------------------------------------------------------------------------------

void DeviceClass::set_memorized_values(bool all, long idx, bool from_init)
{
    TANGO_LOG_DEBUG << "Entering DeviceClass::set_memorized_values() method" << std::endl;

    short sh;
    Tango::DevVarShortArray sh_seq(1);
    sh_seq.length(1);

    Tango::DevLong lg;
    Tango::DevVarLongArray lg_seq(1);
    lg_seq.length(1);

    double db;
    Tango::DevVarDoubleArray db_seq(1);
    db_seq.length(1);

    Tango::DevString tmp_str;
    Tango::DevVarStringArray str_seq(1);
    str_seq.length(1);

    float fl;
    Tango::DevVarFloatArray fl_seq(1);
    fl_seq.length(1);

    Tango::DevBoolean boo;
    Tango::DevVarBooleanArray boo_seq(1);
    boo_seq.length(1);

    Tango::DevUShort ush;
    Tango::DevVarUShortArray ush_seq(1);
    ush_seq.length(1);

    Tango::DevUChar uch;
    Tango::DevVarCharArray uch_seq(1);
    uch_seq.length(1);

    Tango::DevULong ulg;
    Tango::DevVarULongArray ulg_seq(1);
    ulg_seq.length(1);

    Tango::DevLong64 lg64;
    Tango::DevVarLong64Array lg64_seq(1);
    lg64_seq.length(1);

    Tango::DevULong64 ulg64;
    Tango::DevVarULong64Array ulg64_seq(1);
    ulg64_seq.length(1);

    //
    // Set loop start and stop limits
    //

    unsigned long start, stop;
    if(all)
    {
        start = 0;
        stop = device_list.size();
    }
    else
    {
        start = idx;
        stop = idx + 1;
    }

    for(unsigned long i = start; i < stop; i++)
    {
        //
        // This feature is available only for devices implementing IDL release 3 and above
        //

        if(device_list[i]->get_dev_idl_version() < 3)
        {
            continue;
        }

        //
        // Get list of device writable attributes
        //

        AttributeValueList att_val(10);
        std::vector<long> &att_list = device_list[i]->get_device_attr()->get_w_attr_list();

        long nb_wr = 0;
        for(unsigned long j = 0; j < att_list.size(); j++)
        {
            WAttribute &att = device_list[i]->get_device_attr()->get_w_attr_by_ind(att_list[j]);

            if(att.is_memorized())
            {
                std::string &mem_value = att.get_mem_value();
                if(mem_value != MemNotUsed)
                {
                    nb_wr++;
                    att_val.length(nb_wr);

                    //
                    // In order to not send a new time the already memorized value into db, mark it as not memorized
                    // before writing the value
                    //

                    att.set_memorized(false);

                    //
                    // The memorized value gotten from db is a string, we need to convert this string to its real type
                    // before inserting it into an Any
                    //

                    TangoSys_MemStream str;
                    if(!from_init)
                    {
                        str << mem_value << std::ends;
                    }

                    try
                    {
                        switch(att.get_data_type())
                        {
                        case Tango::DEV_SHORT:
                        case Tango::DEV_ENUM:
                            if(!from_init)
                            {
                                if(!(str >> sh))
                                {
                                    throw_mem_value(device_list[i], att);
                                }
                                att.set_write_value(sh);
                            }
                            else
                            {
                                att.get_write_value(sh);
                            }

                            sh_seq[0] = sh;
                            att_val[nb_wr - 1].value <<= sh_seq;
                            break;

                        case Tango::DEV_LONG:
                            if(!from_init)
                            {
                                if(!(str >> lg))
                                {
                                    throw_mem_value(device_list[i], att);
                                }
                                att.set_write_value(lg);
                            }
                            else
                            {
                                att.get_write_value(lg);
                            }

                            lg_seq[0] = lg;
                            att_val[nb_wr - 1].value <<= lg_seq;
                            break;

                        case Tango::DEV_DOUBLE:
                            if(!from_init)
                            {
                                if(!(str >> db))
                                {
                                    throw_mem_value(device_list[i], att);
                                }
                                att.set_write_value(db);
                            }
                            else
                            {
                                att.get_write_value(db);
                            }

                            db_seq[0] = db;
                            att_val[nb_wr - 1].value <<= db_seq;
                            break;

                        case Tango::DEV_STRING:
                            if(!from_init)
                            {
                                att.set_write_value(mem_value);

                                str_seq[0] = Tango::string_dup(mem_value.c_str());
                            }
                            else
                            {
                                att.get_write_value(tmp_str);
                                str_seq[0] = Tango::string_dup(tmp_str);
                            }
                            att_val[nb_wr - 1].value <<= str_seq;
                            break;

                        case Tango::DEV_FLOAT:
                            if(!from_init)
                            {
                                if(!(str >> fl))
                                {
                                    throw_mem_value(device_list[i], att);
                                }
                                att.set_write_value(fl);
                            }
                            else
                            {
                                att.get_write_value(fl);
                            }

                            fl_seq[0] = fl;
                            att_val[nb_wr - 1].value <<= fl_seq;
                            break;

                        case Tango::DEV_BOOLEAN:
                            if(!from_init)
                            {
                                if(!(str >> std::boolalpha >> boo))
                                {
                                    throw_mem_value(device_list[i], att);
                                }
                                att.set_write_value(boo);
                            }
                            else
                            {
                                att.get_write_value(boo);
                            }

                            boo_seq[0] = boo;
                            att_val[nb_wr - 1].value <<= boo_seq;
                            break;

                        case Tango::DEV_USHORT:
                            if(!from_init)
                            {
                                if(!(str >> ush))
                                {
                                    throw_mem_value(device_list[i], att);
                                }
                                att.set_write_value(ush);
                            }
                            else
                            {
                                att.get_write_value(ush);
                            }

                            ush_seq[0] = ush;
                            att_val[nb_wr - 1].value <<= ush_seq;
                            break;

                        case Tango::DEV_UCHAR:
                            if(!from_init)
                            {
                                short tmp_sh;
                                if(!(str >> tmp_sh))
                                {
                                    throw_mem_value(device_list[i], att);
                                }
                                uch = (DevUChar) tmp_sh;
                                att.set_write_value(uch);
                            }
                            else
                            {
                                att.get_write_value(uch);
                            }

                            uch_seq[0] = uch;
                            att_val[nb_wr - 1].value <<= uch_seq;
                            break;

                        case Tango::DEV_ULONG:
                            if(!from_init)
                            {
                                if(!(str >> ulg))
                                {
                                    throw_mem_value(device_list[i], att);
                                }
                                att.set_write_value(ulg);
                            }
                            else
                            {
                                att.get_write_value(ulg);
                            }

                            ulg_seq[0] = ulg;
                            att_val[nb_wr - 1].value <<= ulg_seq;
                            break;

                        case Tango::DEV_LONG64:
                            if(!from_init)
                            {
                                if(!(str >> lg64))
                                {
                                    throw_mem_value(device_list[i], att);
                                }
                                att.set_write_value(lg64);
                            }
                            else
                            {
                                att.get_write_value(lg64);
                            }

                            lg64_seq[0] = lg64;
                            att_val[nb_wr - 1].value <<= lg64_seq;
                            break;

                        case Tango::DEV_ULONG64:
                            if(!from_init)
                            {
                                if(!(str >> ulg64))
                                {
                                    throw_mem_value(device_list[i], att);
                                }
                                att.set_write_value(ulg64);
                            }
                            else
                            {
                                att.get_write_value(ulg64);
                            }

                            ulg64_seq[0] = ulg64;
                            att_val[nb_wr - 1].value <<= ulg64_seq;
                            break;
                        }

                        //
                        // Check the initialisation flag for memorized attributes. If the flag is false, do not add the
                        // element to the att_val vector. This avoids a call to write the memorized value to the
                        // attribute.
                        //

                        if(!att.is_memorized_init())
                        {
                            nb_wr--;
                            att_val.length(nb_wr);
                            // reset memorized flag
                            att.set_memorized(true);
                        }
                        else
                        {
                            //
                            // Init the AttributeValue structure
                            //

                            att_val[nb_wr - 1].name = Tango::string_dup(att.get_name().c_str());
                            att_val[nb_wr - 1].dim_x = 1;
                            att_val[nb_wr - 1].dim_y = 0;
                            att_val[nb_wr - 1].quality = Tango::ATTR_VALID;
                        }
                    }

                    catch(Tango::DevFailed &e)
                    {
                        TANGO_LOG_DEBUG << "Cannot configure setpoint value for memorized attribute " << att.get_name()
                                        << std::endl;
                        Tango::Except::print_exception(e);

                        nb_wr--;
                        att_val.length(nb_wr);
                        // reset memorized flag
                        att.set_memorized(true);
                    }
                }
            }
        }

        if(nb_wr != 0)
        {
            //
            // Write attribute values. Print exception if any.
            //

            try
            {
                TANGO_LOG_DEBUG << "Writing data for " << att_val.length() << " attribute(s) for device "
                                << device_list[i]->get_name() << std::endl;
                (static_cast<Device_3Impl *>(device_list[i]))->write_attributes_3(att_val);
            }
            catch(Tango::DevFailed &e)
            {
                TANGO_LOG_DEBUG << "Cannot write setpoint(s) value for any memorized attribute(s) on device "
                                << device_list[i]->get_name() << std::endl;
                Tango::Except::print_exception(e);
            }
            catch(Tango::MultiDevFailed &e)
            {
                TANGO_LOG_DEBUG << "Cannot write setpoint(s) value for memorized attribute(s) of device "
                                << device_list[i]->get_name() << std::endl;
                for(unsigned long k = 0; k < e.errors.length(); k++)
                {
                    WAttribute &att = device_list[i]->get_device_attr()->get_w_attr_by_name(
                        att_val[e.errors[k].index_in_call].name.in());
                    att.set_mem_exception(e.errors[k].err_list);

                    DEV_WARN_STREAM(device_list[i])
                        << "Writing set_point for attribute " << att.get_name() << " failed" << std::endl;
                    DEV_WARN_STREAM(device_list[i])
                        << "\tException desc = " << e.errors[k].err_list[0].desc.in() << std::endl;
                    DEV_WARN_STREAM(device_list[i])
                        << "\tException reason = " << e.errors[k].err_list[0].reason.in() << std::endl;
                }
                device_list[i]->set_run_att_conf_loop(true);
                Tango::NamedDevFailedList e_list(e,
                                                 device_list[i]->get_name(),
                                                 (const char *) "DeviceClass::set_memorized_values()",
                                                 (const char *) API_AttributeFailed);
                Tango::Except::print_exception(e_list);
            }
            catch(...)
            {
                TANGO_LOG_DEBUG << "Cannot write setpoint(s) value for memorized attribute(s) of device "
                                << device_list[i]->get_name() << std::endl;
                std::cerr << "Received unknown exception while trying to write memorized attribute(s)" << std::endl;

                size_t nb_att = att_val.length();
                for(size_t k = 0; k < nb_att; k++)
                {
                    std::stringstream ss;
                    ss << "Received unknown exception (nor a devfailed, nor a MultiDevFailed !!";

                    Tango::DevErrorList errors;
                    errors.length(1);
                    errors[0].reason = API_WrongEventData;
                    errors[0].origin = Tango::string_dup(TANGO_EXCEPTION_ORIGIN);
                    errors[0].desc = Tango::string_dup(ss.str().c_str());
                    errors[0].severity = ERR;

                    WAttribute &att = device_list[i]->get_device_attr()->get_w_attr_by_name(att_val[k].name.in());
                    att.set_mem_exception(errors);
                    log4tango::Logger *log = device_list[i]->get_logger();
                    if(log->is_warn_enabled())
                    {
                        log->warn_stream() << log4tango::_begin_log << "Writing set_point for attribute "
                                           << att.get_name() << " failed" << std::endl;
                    }
                }
                device_list[i]->set_run_att_conf_loop(true);
            }

            //
            // Reset memorized flags
            //

            for(unsigned long k = 0; k < att_val.length(); k++)
            {
                WAttribute &att = device_list[i]->get_device_attr()->get_w_attr_by_name(att_val[k].name.in());
                att.set_memorized(true);
            }
        }
    }

    TANGO_LOG_DEBUG << "Leaving DeviceClass::set_memorized_values() method" << std::endl;
}

//-------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::throw_mem_value()
//
// description :
//        Throw API_AttrWrongMemValue exception
//
// argument :
//        in :
//            - dev: Pointer to the device
//            - att: The attribute object
//
//--------------------------------------------------------------------------------------------------------------------

void DeviceClass::throw_mem_value(DeviceImpl *dev, const Attribute &att)
{
    TangoSys_OMemStream o;

    o << "Memorized value for attribute ";
    o << att.get_name();
    o << " (device ";
    o << dev->get_name();
    o << ") is in an incorrect format !" << std::ends;

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

//--------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::~DeviceClass(std::string &s)
//
// description :
//        DeviceClass class destructor.
//
//--------------------------------------------------------------------------------------------------------------------

DeviceClass::~DeviceClass()
{
    TANGO_LOG_DEBUG << "Entering DeviceClass destructor for class " << name << std::endl;

    //
    // Destroy the DbClass object
    //

    delete db_class;

    //
    // Destroy the device list
    //

    unsigned long i;
    if(device_list.size() != 0)
    {
        Tango::Util *tg = Tango::Util::instance();
        PortableServer::POA_ptr r_poa = tg->get_poa();

        unsigned long nb_dev = device_list.size();
        for(i = 0; i < nb_dev; i++)
        {
            //
            // Clear vectors used to memorize info used to clean db in case of devices with dyn attr removed during
            // device destruction
            //

            tg->get_polled_dyn_attr_names().clear();
            tg->get_full_polled_att_list().clear();
            tg->get_all_dyn_attr_names().clear();
            tg->get_dyn_att_dev_name().clear();

            //
            // Delete device
            //

            delete_dev(0, tg, r_poa);

            //
            // Clean-up db (dyn attribute and cmd)
            //

            if(tg->get_polled_dyn_attr_names().size() != 0)
            {
                tg->clean_attr_polled_prop();
            }
            if(tg->get_all_dyn_attr_names().size() != 0)
            {
                tg->clean_dyn_attr_prop();
            }
            if(tg->get_polled_dyn_cmd_names().size() != 0)
            {
                tg->clean_cmd_polled_prop();
            }

            auto it = device_list.begin();
            device_list.erase(it);
        }
        device_list.clear();
        CORBA::release(r_poa);
    }

    //
    // Destroy the command list
    //

    for(i = 0; i < command_list.size(); i++)
    {
        delete command_list[i];
    }
    command_list.clear();

    //
    // Destroy the pipe list
    //

    std::map<std::string, std::vector<Pipe *>>::iterator ite;
    for(ite = ext->dev_pipe_list.begin(); ite != ext->dev_pipe_list.end(); ite++)
    {
        for(size_t loop = 0; loop < ite->second.size(); loop++)
        {
            delete(ite->second)[loop];
            (ite->second)[loop] = nullptr;
        }
    }
    ext->dev_pipe_list.clear();

    //
    // Destroy the MultiClassAttribute object
    //

    delete class_attr;

    //
    // Destroy the MultiClassPipe object
    //

    delete class_pipe;

    //
    // Unregister the class from signal handler
    //

    DServerSignal::instance()->unregister_class_signal(this);

    //
    // Delete the class extension object
    //

    TANGO_LOG_DEBUG << "Leaving DeviceClass destructor for class " << name << std::endl;
}

//--------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::delete_dev()
//
// description :
//        Delete a device from the class device list
//
// argument :
//        in :
//            - idx :
//            - tg : Pointer to the Tango Util singleton
//            - r_poa : Pointer to the CORBA POA
//
//-------------------------------------------------------------------------------------------------------------------

void DeviceClass::delete_dev(long idx, Tango::Util *tg, PortableServer::POA_ptr r_poa)
{
    TANGO_LOG_DEBUG << "Entering DeviceClas::delete_dev method for device with index " << idx << std::endl;

    //
    // If the polling thread is alive and if device is polled, ask polling thread to stop polling
    //

    if((!tg->get_polling_threads_info().empty()) && (device_list[idx]->is_polled()))
    {
        device_list[idx]->stop_polling(false);
    }

    //
    // Deactivate the CORBA object
    //

    bool exported_device = device_list[idx]->get_exported_flag();

    if(exported_device)
    {
        r_poa->deactivate_object(device_list[idx]->get_obj_id().in());
    }

    //
    // Wait for CORBA to call the device dtor
    //

    if(device_list[idx] != nullptr && exported_device)
    {
        while(device_list[idx] != nullptr)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }

    TANGO_LOG_DEBUG << "Leaving DeviceClass delete_dev" << std::endl;
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::register_signal
//
// description :
//        Method to register a class on a signal. When the signal is sent to the process, the signal_handler
//        method of this class will be executed
//
// argument :
//         in :
//            - signo : The signal number
//            - delayed : The boolean delayed flag
//
//-------------------------------------------------------------------------------------------------------------------

#ifdef _TG_WINDOWS_
void DeviceClass::register_signal(long signo)
{
    TANGO_LOG_DEBUG << "DeviceClass::register_signal() arrived for signal " << signo << std::endl;

    DServerSignal::instance()->register_class_signal(signo, this);

    TANGO_LOG_DEBUG << "Leaving DeviceClass::register_signal method()" << std::endl;
}
#else
void DeviceClass::register_signal(long signo, bool handler)
{
    TANGO_LOG_DEBUG << "DeviceClass::register_signal() arrived for signal " << signo << std::endl;

    DServerSignal::instance()->register_class_signal(signo, handler, this);

    TANGO_LOG_DEBUG << "Leaving DeviceClass::register_signal method()" << std::endl;
}
#endif

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::unregister_signal
//
// description :
//        Method to unregister a class on a signal.
//
// argument :
//         in :
//            - signo : The signal number
//            - delayed : The boolean delayed flag
//
//-------------------------------------------------------------------------------------------------------------------

void DeviceClass::unregister_signal(long signo)
{
    TANGO_LOG_DEBUG << "DeviceClass::unregister_signal() arrived for signal " << signo << std::endl;

    DServerSignal::instance()->unregister_class_signal(signo, this);

    TANGO_LOG_DEBUG << "Leaving DeviceClass::unregister_signal method()" << std::endl;
}

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::signal_handler
//
// description :
//        This is the signal handler for the class. This method is defined as virtual and therefore, can be redefined
//        by DS programmers in their own classes derived from DeviceClass class
//
// argument :
//         in :
//            - signo : The signal number
//
//------------------------------------------------------------------------------------------------------------------

void DeviceClass::signal_handler(long signo)
{
    TANGO_LOG_DEBUG << "DeviceClass::signal_handler() arrived for signal " << signo << std::endl;

    TANGO_LOG_DEBUG << "Leaving DeviceClass::signal_handler method()" << std::endl;
}

//+----------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::export_device()
//
// description :
//        This method exports a device to the outside world. This is done by sending its CORBA network parameter
//        (mainly the IOR) to the Tango database
//
// argument :
//         in :
//            - dev : The device to be exported
//            - corba_obj_name : The CORBA object name associated with the device. A default value is provided.
//                               This field is mainly use for specific device server like the database device server.
//
//------------------------------------------------------------------------------------------------------------------

void DeviceClass::export_device(DeviceImpl *dev, const char *corba_obj_name)
{
    TANGO_LOG_DEBUG << "DeviceClass::export_device() arrived" << std::endl;

    Device_var d;

    if(Tango::Util::instance()->use_db() && !Tango::Util::instance()->use_file_db())
    {
        //
        // Take the Tango monitor in order to protect the device against external world access
        // until memorized attribute (if any) are set but forget the admin device
        // (which does not have any memorized attribute)
        // But don't do this for dynamic devices which are not created in the DServer::init_device method class loop
        //

        std::string &dev_name = dev->get_name_lower();
        TANGO_LOG_DEBUG << "DeviceClass::export_device::dev_name=" << dev_name << std::endl;
        if((!get_device_factory_done()) && (dev_name.find("dserver") != 0))
        {
            dev->get_dev_monitor().get_monitor();
        }

        //
        // Activate the CORBA object incarnated by the dev C++ object
        // Also call _remove_ref to give POA the full ownership of servant
        //

        d = dev->_this();
        dev->set_d_var(Tango::Device::_duplicate(d));
        dev->_remove_ref();

        //
        // Store the ObjectId (The ObjectId_var type is a typedef of a string_var type)
        //

        PortableServer::ObjectId_var oid;
        try
        {
            PortableServer::POA_ptr r_poa = Util::instance()->get_poa();
            oid = r_poa->reference_to_id(d);
            CORBA::release(r_poa);
        }
        catch(...)
        {
            TangoSys_OMemStream o;
            o << "Cant get CORBA reference Id for device " << dev->get_name() << std::ends;
            TANGO_THROW_EXCEPTION(API_CantGetDevObjectId, o.str());
        }
        dev->set_obj_id(oid);
    }
    else
    {
        //
        // For server started without db usage (Mostly the database server). In this case, it is necessary to create our
        // own CORBA object id and to bind it into the Boot Manager for access through a stringified object reference
        // constructed using the corbaloc style. The API will try to connect to device using lower case letters.
        // Register device in POA with lower case letters
        //

        std::string corba_obj_name_lower(corba_obj_name);
        std::transform(
            corba_obj_name_lower.begin(), corba_obj_name_lower.end(), corba_obj_name_lower.begin(), ::tolower);
        PortableServer::ObjectId_var id = PortableServer::string_to_ObjectId(corba_obj_name_lower.c_str());
        try
        {
            PortableServer::POA_ptr r_poa = Util::instance()->get_poa();
            r_poa->activate_object_with_id(id.in(), dev);
            CORBA::release(r_poa);
        }
        catch(...)
        {
            TangoSys_OMemStream o;
            o << "Can't get CORBA reference Id for device " << dev->get_name() << std::ends;
            TANGO_THROW_EXCEPTION(API_CantGetDevObjectId, o.str());
        }

        d = dev->_this();
        dev->set_obj_id(id);
        dev->set_d_var(Tango::Device::_duplicate(d));
        dev->_remove_ref();
    }

    //
    // Prepare sent parameters and allocate mem for them
    //

    if(Tango::Util::instance()->use_db() && !Tango::Util::instance()->use_file_db())
    {
        Tango::Util *tg = Tango::Util::instance();
        CORBA::ORB_var orb_var = tg->get_orb();

        char *s = orb_var->object_to_string(d);
        std::string ior_string(s);

        Tango::DbDevExportInfo exp;

        exp.name = dev->get_name();
        exp.ior = ior_string;
        exp.host = tg->get_host_name();
        exp.pid = tg->get_pid();
        exp.version = tg->get_version_str();

        //
        // Call db server
        // We are still in the server starting phase. Therefore, the db timeout is still high (13 sec the 07/01/2011)
        // with 3 retries in case of timeout
        //

        try
        {
            tg->get_database()->export_device(exp);
        }
        catch(Tango::CommunicationFailed &)
        {
            std::cerr << "CommunicationFailed while exporting device " << dev->get_name() << std::endl;
            Tango::string_free(s);
            throw;
        }

        Tango::string_free(s);
    }

    //
    // Set the DeviceImpl exported flag to true. Also enable device interface change event
    //

    dev->set_exported_flag(true);
    dev->enable_intr_change_ev();

    TANGO_LOG_DEBUG << "Leaving DeviceClass::export_device method()" << std::endl;
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::command_handler(string &s)
//
// description :
//        Command handler which is called by Device when a command is received. It will check to see if the command is
//        implemented. If so it will test to see if it is allowed in this state. Finally it will execute the
//        command by calling its execute method. If an error occurs it will throw a DevFailed exception.
//
//-------------------------------------------------------------------------------------------------------------------

CORBA::Any *DeviceClass::command_handler(DeviceImpl *device, const std::string &command, const CORBA::Any &in_any)
{
    CORBA::Any *ret = nullptr;
    std::vector<Command *>::iterator i_cmd;

    std::string command_lower(command);

    TANGO_LOG_DEBUG << "Entering DeviceClass::command_handler() method" << std::endl;

    std::transform(command_lower.begin(), command_lower.end(), command_lower.begin(), ::tolower);

    //
    // Search for command object first at class level then at device level (case of dynamic command installed at device
    // level)
    //

    bool found = false;
    for(i_cmd = command_list.begin(); i_cmd < command_list.end(); ++i_cmd)
    {
        if((*i_cmd)->get_lower_name() == command_lower)
        {
            found = true;
            break;
        }
    }

    if(!found)
    {
        std::vector<Command *> &dev_command_list = device->get_local_command_list();
        for(i_cmd = dev_command_list.begin(); i_cmd < dev_command_list.end(); ++i_cmd)
        {
            if((*i_cmd)->get_lower_name() == command_lower)
            {
                found = true;
                break;
            }
        }
    }

    if(found)
    {
        //
        // Call the always executed method
        //

        device->always_executed_hook();

        //
        // Check if command is allowed
        //

        if(!(*i_cmd)->is_allowed(device, in_any))
        {
            TangoSys_OMemStream o;
            o << "Command " << command << " not allowed when the device is in "
              << Tango::DevStateName[device->get_state()] << " state" << std::ends;
            TANGO_THROW_EXCEPTION(API_CommandNotAllowed, o.str());
        }

        //
        // Execute command
        //

        ret = (*i_cmd)->execute(device, in_any);
    }

    if(!found)
    {
        TANGO_LOG_DEBUG << "DeviceClass::command_handler(): command " << command << " not found in class/device command"
                        << std::endl;

        Command *def_cmd = get_default_command();
        if(def_cmd != nullptr)
        {
            //
            // Set name in default command object
            //

            def_cmd->set_name(command);

            //
            // Call the always executed method
            //

            device->always_executed_hook();

            //
            // Check if command is allowed
            //

            if(!def_cmd->is_allowed(device, in_any))
            {
                TangoSys_OMemStream o;
                o << "Command " << command << " not allowed when the device is in "
                  << Tango::DevStateName[device->get_state()] << " state" << std::ends;
                TANGO_THROW_EXCEPTION(API_CommandNotAllowed, o.str());
            }

            //
            // Execute command
            //

            ret = def_cmd->execute(device, in_any);
        }
        else
        {
            //
            // Throw an exception to client
            //

            TangoSys_OMemStream o;

            o << "Command " << command << " not found" << std::ends;
            TANGO_THROW_EXCEPTION(API_CommandNotFound, o.str());
        }
    }

    TANGO_LOG_DEBUG << "Leaving DeviceClass::command_handler() method" << std::endl;
    return ret;
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::add_wiz_dev_prop()
//
// description :
//        Method to add a device property definition for the device  wizard
//
// argument :
//         in :
//            - name : The device property name
//            - desc : The device property description
//            - def : The device property default value
//
//-------------------------------------------------------------------------------------------------------------------

void DeviceClass::add_wiz_dev_prop(const std::string &p_name, const std::string &desc, const std::string &def)
{
    //
    // Name in lowercase letters
    //

    std::string name_low = p_name;
    std::transform(name_low.begin(), name_low.end(), name_low.begin(), ::tolower);

    //
    // Check that this property is not already in the vector
    //

    std::vector<std::string>::iterator ite;
    for(ite = wiz_dev_prop.begin(); ite < wiz_dev_prop.end(); ++ite)
    {
        std::string tmp_name(*ite);
        std::transform(tmp_name.begin(), tmp_name.end(), tmp_name.begin(), ::tolower);
        if(tmp_name == name_low)
        {
            break;
        }
    }

    if(ite != wiz_dev_prop.end())
    {
        TangoSys_OMemStream o;
        o << "Device property " << p_name;
        o << " for class " << name << " is already defined in the wizard" << std::ends;
        TANGO_THROW_EXCEPTION(API_WizardConfError, o.str());
    }

    //
    // Insert data in vector
    //

    wiz_dev_prop.push_back(p_name);
    wiz_dev_prop.push_back(desc);
    wiz_dev_prop.push_back(def);
}

void DeviceClass::add_wiz_dev_prop(const std::string &p_name, const std::string &desc)
{
    std::string def(AlrmValueNotSpec);
    add_wiz_dev_prop(p_name, desc, def);
}

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::add_wiz_class_prop()
//
// description :
//        Method to add a class property definition for the device  wizard
//
// argument :
//         in :
//            - name : The class property name
//            - desc : The class property description
//            - def : The class property default value
//
//--------------------------------------------------------------------------------------------------------------------

void DeviceClass::add_wiz_class_prop(const std::string &p_name, const std::string &desc, const std::string &def)
{
    //
    // Name in lowercase letters
    //

    std::string name_low = p_name;
    std::transform(name_low.begin(), name_low.end(), name_low.begin(), ::tolower);

    //
    // Check that this property is not already in the vector
    //

    std::vector<std::string>::iterator ite;
    for(ite = wiz_class_prop.begin(); ite < wiz_class_prop.end(); ++ite)
    {
        std::string tmp_name(*ite);
        std::transform(tmp_name.begin(), tmp_name.end(), tmp_name.begin(), ::tolower);
        if(tmp_name == name_low)
        {
            break;
        }
    }

    if(ite != wiz_class_prop.end())
    {
        TangoSys_OMemStream o;
        o << "Class property " << p_name;
        o << " for class " << name << " is already defined in the wizard" << std::ends;
        TANGO_THROW_EXCEPTION(API_WizardConfError, o.str());
    }

    //
    // Insert data in vector
    //

    wiz_class_prop.push_back(p_name);
    wiz_class_prop.push_back(desc);
    wiz_class_prop.push_back(def);
}

void DeviceClass::add_wiz_class_prop(const std::string &p_name, const std::string &desc)
{
    std::string def(AlrmValueNotSpec);
    add_wiz_class_prop(p_name, desc, def);
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::device_destroyer
//
// description :
//        Method to remove and delete a device from the running devices belonging to the Tango class
//
// argument :
//         in :
//            - dev_name : The device name
//
//-------------------------------------------------------------------------------------------------------------------

void DeviceClass::device_destroyer(const std::string &dev_name)
{
    //
    // Check that the class know this device
    //

    unsigned long k;
    for(k = 0; k < device_list.size(); k++)
    {
        if(device_list[k]->get_name() == dev_name)
        {
            break;
        }
    }

    if(k == device_list.size())
    {
        TangoSys_OMemStream o;
        o << "Device " << dev_name << " not in Tango class device list!" << std::ends;

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

    //
    // Check if the device is polled. If yes, ask polling thread to stop polling it
    //

    if(device_list[k]->is_polled())
    {
        device_list[k]->stop_polling();
    }

    //
    // Delete the device
    //

    Tango::Util *tg = Tango::Util::instance();

    PortableServer::POA_ptr r_poa = tg->get_poa();
    delete_dev(k, tg, r_poa);
    auto it = device_list.begin();
    it += k;
    device_list.erase(it);
    CORBA::release(r_poa);
}

void DeviceClass::device_destroyer(const char *dev_name)
{
    std::string name_str(dev_name);
    device_destroyer(name_str);
}

//+-------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::is_command_allowed
//
// description :
//        Method to check if a command is allowed even if the device is locked by another client. It follows the
//        definition of the Tango control access system to define what is an allowed command
//
// argument :
//         in :
//            - cmd : The command name
//
//--------------------------------------------------------------------------------------------------------------------

bool DeviceClass::is_command_allowed(const char *cmd)
{
    bool ret = true;

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

    auto pos = find(allowed_cmds.begin(), allowed_cmds.end(), tmp_cmd);
    if(pos == allowed_cmds.end())
    {
        ret = false;
    }

    return ret;
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::get_mcast_event()
//
// description :
//        Get for all class devices and for all attributes multicast event parameters (if any)
//
// argument :
//         in :
//            - dserv : Pointer to the DServer device
//
//------------------------------------------------------------------------------------------------------------------

void DeviceClass::get_mcast_event(DServer *dserv)
{
    TANGO_LOG_DEBUG << "Entering DeviceClass::get_mcast_event() method" << std::endl;
    std::vector<std::string> m_cast;

    for(unsigned int i = 0; i < device_list.size(); ++i)
    {
        std::vector<Attribute *> &att_list = device_list[i]->get_device_attr()->get_attribute_list();
        for(unsigned int j = 0; j < att_list.size(); ++j)
        {
            dserv->mcast_event_for_att(device_list[i]->get_name_lower(), att_list[j]->get_name_lower(), m_cast);
            if(!m_cast.empty())
            {
                att_list[j]->set_mcast_event(m_cast);
            }
        }
    }
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::get_cmd_by_name
//
// description :
//        Get a reference to the Command object
//
// arguemt :
//         in :
//            - cmd_name : The command name
//
//------------------------------------------------------------------------------------------------------------------

Command &DeviceClass::get_cmd_by_name(const std::string &cmd_name)
{
    std::vector<Command *>::iterator pos;

    pos = find_if(command_list.begin(),
                  command_list.end(),
                  [&](Command *cmd) -> bool
                  {
                      if(cmd_name.size() != cmd->get_lower_name().size())
                      {
                          return false;
                      }
                      std::string tmp_name(cmd_name);
                      std::transform(tmp_name.begin(), tmp_name.end(), tmp_name.begin(), ::tolower);
                      return cmd->get_lower_name() == tmp_name;
                  });

    if(pos == command_list.end())
    {
        TANGO_LOG_DEBUG << "DeviceClass::get_cmd_by_name throwing exception" << std::endl;
        TangoSys_OMemStream o;

        o << cmd_name << " command not found" << std::ends;
        TANGO_THROW_EXCEPTION(API_CommandNotFound, o.str());
    }

    return *(*pos);
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::get_pipe_by_name
//
// description :
//        Get a reference to the Pipe object
//
// arguemt :
//         in :
//            - pipe_name : The pipe name
//          - dev_name : The device name (lower case letters)
//
//------------------------------------------------------------------------------------------------------------------

Pipe &DeviceClass::get_pipe_by_name(const std::string &pipe_name, const std::string &dev_name)
{
    auto ite = ext->dev_pipe_list.find(dev_name);
    if(ite == ext->dev_pipe_list.end())
    {
        TANGO_LOG_DEBUG << "DeviceClass::get_pipe_by_name throwing exception" << std::endl;
        TangoSys_OMemStream o;

        o << dev_name << " device not found in pipe map" << std::ends;
        TANGO_THROW_EXCEPTION(API_PipeNotFound, o.str());
    }

    std::vector<Pipe *>::iterator pos;

    pos = find_if(ite->second.begin(),
                  ite->second.end(),
                  [&](Pipe *pi) -> bool
                  {
                      if(pipe_name.size() != pi->get_lower_name().size())
                      {
                          return false;
                      }
                      std::string tmp_name(pipe_name);
                      std::transform(tmp_name.begin(), tmp_name.end(), tmp_name.begin(), ::tolower);
                      return pi->get_lower_name() == tmp_name;
                  });

    if(pos == ite->second.end())
    {
        TANGO_LOG_DEBUG << "DeviceClass::get_pipe_by_name throwing exception" << std::endl;
        TangoSys_OMemStream o;

        o << pipe_name << " pipe not found" << std::ends;
        TANGO_THROW_EXCEPTION(API_PipeNotFound, o.str());
    }

    return *(*pos);
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::remove_command
//
// description :
//        Delete a command from the command list
//
// argument :
//         in :
//            - cmd_name : The command name (in lower case letter)
//
//-----------------------------------------------------------------------------------------------------------------

void DeviceClass::remove_command(const std::string &cmd_name)
{
    std::vector<Command *>::iterator pos;

    pos = find_if(command_list.begin(),
                  command_list.end(),
                  [&](Command *cmd) -> bool
                  {
                      if(cmd_name.size() != cmd->get_lower_name().size())
                      {
                          return false;
                      }
                      return cmd->get_lower_name() == cmd_name;
                  });

    if(pos == command_list.end())
    {
        TANGO_LOG_DEBUG << "DeviceClass::get_cmd_by_name throwing exception" << std::endl;
        TangoSys_OMemStream o;

        o << cmd_name << " command not found" << std::ends;
        TANGO_THROW_EXCEPTION(API_CommandNotFound, o.str());
    }

    command_list.erase(pos);
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::check_att_conf
//
// description :
//        Check attribute configuration (for wrong conf. in db exception) for all devices in the class
//
//-------------------------------------------------------------------------------------------------------------------

void DeviceClass::check_att_conf()
{
    std::vector<DeviceImpl *>::iterator ite;

    for(ite = device_list.begin(); ite != device_list.end(); ++ite)
    {
        (*ite)->check_att_conf();
    }
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::release_devices_mon
//
// description :
//        Release all device(s) class monitor. This is needed in DS startup sequence. Each device monitor is taken
//        before the device is known to CORBA and released after the memorized attributes (if any) are set
//
//------------------------------------------------------------------------------------------------------------------

void DeviceClass::release_devices_mon()
{
    std::vector<DeviceImpl *>::iterator ite;

    //
    // Release monitor for all devices belonging to this class
    //

    for(ite = device_list.begin(); ite != device_list.end(); ++ite)
    {
        (*ite)->get_dev_monitor().rel_monitor();
    }
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::create_device_pipe
//
// description :
//        Create all devices for a device
//
// argument :
//         in :
//            - cl : The device class (we need here the pointer to the real device class not to the base class)
//          - dev : Device ptr
//
//------------------------------------------------------------------------------------------------------------------

void DeviceClass::create_device_pipe(DeviceClass *cl, DeviceImpl *dev)
{
    //
    // Create pipe for device and store them in the pipe map
    //

    cl->pipe_factory();

    if(ext->dev_pipe_list.empty())
    {
        MultiClassPipe *c_pipe = get_class_pipe();
        c_pipe->init_class_pipe(this);
    }

    sort(pipe_list.begin(), pipe_list.end(), [](Pipe *a, Pipe *b) { return a->get_name() < b->get_name(); });

    std::string dev_name = dev->get_name_lower();

    auto ret = ext->dev_pipe_list.insert(make_pair(dev_name, pipe_list));
    if(!ret.second)
    {
        // insertion was not successful, we now own the memory in pipe_list
        for(auto *elem : pipe_list)
        {
            delete elem;
            elem = nullptr;
        }
    }

    pipe_list.clear();
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DeviceClass::get_pipe_list
//
// description :
//        Get a reference to the Pipe list for a device
//
// argument :
//         in :
//          - dev_name : The device name
//
//------------------------------------------------------------------------------------------------------------------

std::vector<Pipe *> &DeviceClass::get_pipe_list(const std::string &dev_name)
{
    std::string local_dev_name(dev_name);
    std::transform(local_dev_name.begin(), local_dev_name.end(), local_dev_name.begin(), ::tolower);

    auto ite = ext->dev_pipe_list.find(local_dev_name);
    if(ite == ext->dev_pipe_list.end())
    {
        TANGO_LOG_DEBUG << "DeviceClass::get_pipe_by_name throwing exception" << std::endl;
        TangoSys_OMemStream o;

        o << dev_name << " device not found in pipe map" << std::ends;
        TANGO_THROW_EXCEPTION(API_PipeNotFound, o.str());
    }

    return ite->second;
}

} // namespace Tango
