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

#include <tango/client/DbServerData.h>

#include <tango/client/ApiUtil.h>
#include <tango/client/Database.h>
#include <tango/client/DbDatum.h>
#include <tango/client/DbDevInfo.h>
#include <tango/client/DeviceProxy.h>
#include <tango/common/idl_wrapper.h>
#include <tango/server/Except.h>

#include <sstream>
#include <string>
#include <vector>

using namespace CORBA;

namespace Tango
{

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::DbServerData
//
// description:
//        constructor for the DbServerData class. This class data stored in the database related to a device server
//        process. It allows easy insert/remove of one device server process from one database to another
//
// argument:
//         in :
//            exec_name : The device server process exec name
//            inst_name : The device server process instance name
//
//--------------------------------------------------------------------------------------------------------------------

DbServerData::DbServerData(const std::string &exec_name, const std::string &inst_name)
{
    full_server_name = exec_name + '/' + inst_name;

    //
    //  Check if server defined in database
    //

    std::string adm_name("dserver/");
    adm_name = adm_name + full_server_name;
    Database *db_ptr = nullptr;

    try
    {
        DeviceProxy adm(adm_name);
        ApiUtil *au = ApiUtil::instance();
        int ind = au->get_db_ind(adm.get_db_host(), adm.get_db_port_num());
        db_ptr = au->get_db_vect()[ind];
    }
    catch(Tango::DevFailed &)
    {
        std::stringstream ss;
        ss << "Device server process " << full_server_name << " is not defined in database";
        TANGO_THROW_EXCEPTION("DBServerNotDefinedInDb", ss.str());
    }

    //
    //  Build Server classes
    //

    TangoClass tc("DServer", full_server_name, db_ptr);
    classes.push_back(tc);

    DbDatum svr_class_list = db_ptr->get_server_class_list(full_server_name);
    std::vector<std::string> vs;
    svr_class_list >> vs;

    for(size_t loop = 0; loop < vs.size(); loop++)
    {
        TangoClass tc(vs[loop], full_server_name, db_ptr);
        classes.push_back(tc);
    }
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::put_in_database
//
// description:
//        Store all the data related to the device server process in the database specified by the input arg.
//
// argument:
//         in :
//            tg_host : The tango host for the new database
//
//--------------------------------------------------------------------------------------------------------------------

void DbServerData::put_in_database(const std::string &tg_host)
{
    //
    // Check tango host syntax
    //

    std::string::size_type pos;
    if((pos = tg_host.find(':')) == std::string::npos)
    {
        std::stringstream ss;
        ss << tg_host << " is not a valid synatx for Tango host (host:port)";
        TANGO_THROW_EXCEPTION("DBWrongTangoHostSyntax", ss.str());
    }

    //
    // Create db object
    //

    std::string db_host = tg_host.substr(0, pos);
    std::string db_port_str = tg_host.substr(pos + 1);
    std::stringstream ss;
    ss << db_port_str;
    int db_port;
    ss >> db_port;

    Database *db_ptr = new Database(db_host, db_port);

    //
    // Create server in DB and put associated properties
    //

    create_server(db_ptr);
    put_properties(db_ptr);
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::already_exist
//
// description:
//        Check if any of the device server process device(s) is already defined in the database specified by the
//        tango host given as input arg
//
// argument:
//         in :
//            tg_host : The tango host for the database
//
// return:
//        True in case any of the device is already known. False otherwise
//
//--------------------------------------------------------------------------------------------------------------------

bool DbServerData::already_exist(const std::string &tg_host)
{
    //
    // Check tango host syntax
    //

    if(tg_host.find(':') == std::string::npos)
    {
        std::stringstream ss;
        ss << tg_host << " is not a valid synatx for Tango host (host:port)";
        TANGO_THROW_EXCEPTION("DBWrongTangoHostSyntax", ss.str());
    }

    std::string header("tango://");
    header = header + tg_host + '/';

    //
    // Check if server is defined and immediately returned if it is defined
    //

    try
    {
        std::string adm_name(header);
        adm_name = adm_name + "dserver/" + full_server_name;
        DeviceProxy adm(adm_name);

        return true;
    }
    catch(Tango::DevFailed &)
    {
    }

    //
    //  Check if at least one device already defined
    //

    for(size_t loop = 0; loop < classes.size(); loop++)
    {
        for(size_t ctr = 0; ctr < classes[loop].size(); ctr++)
        {
            std::string dev_name = header + classes[loop][ctr].name;

            try
            {
                DeviceProxy dev(dev_name);
                return true;
            }
            catch(Tango::DevFailed &e)
            {
                std::string desc(e.errors[0].desc.in());
                if(desc.find("not defined in the database") == std::string::npos)
                {
                    std::stringstream ss;
                    ss << "Failed to check " << dev_name << " in Tango host " << tg_host << std::endl;
                    TANGO_RETHROW_EXCEPTION(e, "DBFailedToCheck", ss.str());
                }
            }
        }
    }

    return false;
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::create_server
//
// description:
//        Create the device server process with all its devices in the database given as input arg
//
// argument:
//         in :
//            db_ptr : Ptr to the database object
//
//--------------------------------------------------------------------------------------------------------------------

void DbServerData::create_server(Database *db_ptr)
{
    DbDevInfos db_infs;
    for(size_t loop = 0; loop < classes.size(); loop++)
    {
        for(size_t lo = 0; lo < classes[loop].size(); lo++)
        {
            DbDevInfo ddi;
            ddi.name = classes[loop][lo].name;
            ddi._class = classes[loop].name;
            ddi.server = full_server_name;
            db_infs.push_back(ddi);
        }
    }

    db_ptr->add_server(full_server_name, db_infs);
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::put_properties
//
// description:
//        Store all properties (for classes and devices) in the database specified by the input arg
//
// argument:
//         in :
//            db_ptr : Ptr to the database object
//
//--------------------------------------------------------------------------------------------------------------------

void DbServerData::put_properties(Database *db_ptr)
{
    for(size_t loop = 0; loop < classes.size(); loop++)
    {
        classes[loop].put_properties(db_ptr);
        classes[loop].put_attribute_properties(db_ptr);
        classes[loop].put_pipe_properties(db_ptr);
        for(size_t ctr = 0; ctr < classes[loop].size(); ctr++)
        {
            classes[loop][ctr].put_properties(db_ptr);
            classes[loop][ctr].put_attribute_properties(db_ptr);
            classes[loop][ctr].put_pipe_properties(db_ptr);
        }
    }
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::remove
//
// description:
//        Remove device server process from a database
//
// argument:
//         in :
//            tg_host : The tango host for the new database
//
//--------------------------------------------------------------------------------------------------------------------

void DbServerData::remove(const std::string &tg_host)
{
    //
    // Check tango host syntax
    //

    std::string::size_type pos;
    if((pos = tg_host.find(':')) == std::string::npos)
    {
        std::stringstream ss;
        ss << tg_host << " is not a valid synatx for Tango host (host:port)";
        TANGO_THROW_EXCEPTION("DBWrongTangoHostSyntax", ss.str());
    }

    //
    // Create db object
    //

    std::string db_host = tg_host.substr(0, pos);
    std::string db_port_str = tg_host.substr(pos + 1);
    std::stringstream ss;
    ss << db_port_str;
    int db_port;
    ss >> db_port;

    Database *db_ptr = new Database(db_host, db_port);

    try
    {
        //
        // Delete devices
        //

        for(size_t loop = classes.size(); loop != 0; loop--)
        {
            for(size_t ctr = 0; ctr < classes[loop - 1].size(); ctr++)
            {
                db_ptr->delete_device(classes[loop - 1][ctr].name);
            }
        }

        //
        // Also delete class properties if this is the last class instance in DB
        // Forget DServer class
        //

        for(size_t loop = 1; loop < classes.size(); loop++)
        {
            std::string all("*");
            DbDatum dev_list = db_ptr->get_device_name(all, classes[loop].name);
            if(dev_list.value_string.empty())
            {
                classes[loop].remove_properties(db_ptr);
            }
        }
    }
    catch(Tango::DevFailed &)
    {
        delete db_ptr;
        throw;
    }

    delete db_ptr;
}

void DbServerData::remove()
{
    //
    // Get from one device the tango host
    //

    std::string &db_host = classes[0][0].get_db_host();
    std::string &db_port = classes[0][0].get_db_port();

    std::string tg_host = db_host + ":" + db_port;

    remove(tg_host);
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::TangoDevice::TangoDevice
//
// description:
//        Constructor for the DbServerData inner structure TangoDevice. There is one instance of this struc for
//        every device defined in the device server process
//
// argument:
//         in :
//            na : The device name
//
//--------------------------------------------------------------------------------------------------------------------

DbServerData::TangoDevice::TangoDevice(const std::string &na) :
    DeviceProxy(na),
    name(na)
{
    //
    //  First read device properties
    //

    std::vector<std::string> prop_list;
    get_property_list("*", prop_list);

    if(!prop_list.empty())
    {
        DbData prop_val;
        get_property(prop_list, prop_val);

        for(size_t loop = 0; loop < prop_list.size(); loop++)
        {
            if(!prop_val[loop].is_empty())
            {
                TangoProperty tp(prop_list[loop], prop_val[loop].value_string);
                properties.push_back(tp);
            }
        }
    }

    //
    // Get attribute list from db
    //

    ApiUtil *au = ApiUtil::instance();
    int ind = au->get_db_ind(get_db_host(), get_db_port_num());
    Database *db = au->get_db_vect()[ind];

    std::vector<std::string> att_list;
    db->get_device_attribute_list(name, att_list);

    for(size_t loop = 0; loop < att_list.size(); loop++)
    {
        DbData db_data;
        db_data.emplace_back(att_list[loop]);
        db->get_device_attribute_property(na, db_data);
        for(size_t lo = 1; lo < db_data.size(); lo++)
        {
            if(!db_data[lo].is_empty())
            {
                TangoAttribute ta(att_list[loop]);
                ta.push_back(TangoProperty(db_data[lo].name, db_data[lo].value_string));
                attributes.push_back(ta);
            }
        }
    }

    //
    // Then get pipe list from db
    //

    std::vector<std::string> pipe_list;
    try
    {
        db->get_device_pipe_list(name, pipe_list);

        for(size_t loop = 0; loop < pipe_list.size(); loop++)
        {
            DbData db_data;
            db_data.emplace_back(pipe_list[loop]);
            db->get_device_pipe_property(na, db_data);
            for(size_t lo = 1; lo < db_data.size(); lo++)
            {
                if(!db_data[lo].is_empty())
                {
                    TangoPipe ta(pipe_list[loop]);
                    ta.push_back(TangoProperty(db_data[lo].name, db_data[lo].value_string));
                    pipes.push_back(ta);
                }
            }
        }
    }
    catch(DevFailed &e)
    {
        std::string reason(e.errors[0].reason.in());
        if(reason != API_CommandNotFound)
        {
            throw;
        }
    }
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::TangoDevice::put_properties
//
// description:
//        Store in database all the properties belonging to the device
//
// argument:
//         in :
//            db_ptr : Pointer to the database instance
//
//--------------------------------------------------------------------------------------------------------------------

void DbServerData::TangoDevice::put_properties(Database *db_ptr)
{
    if(!properties.empty())
    {
        DbData db_data;
        for(size_t ctr = 0; ctr < properties.size(); ctr++)
        {
            DbDatum prop(properties[ctr].name);
            prop.value_string = properties[ctr].values;
            db_data.push_back(prop);
        }
        db_ptr->put_device_property(name, db_data);
    }
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::TangoDevice::put_attribute_properties
//
// description:
//        Store in database all the properties belonging to the device attribute(s)
//
// argument:
//         in :
//            db_ptr : Pointer to the database instance
//
//--------------------------------------------------------------------------------------------------------------------

void DbServerData::TangoDevice::put_attribute_properties(Database *db_ptr)
{
    if(!attributes.empty())
    {
        DbData db_data;
        for(size_t loop = 0; loop < attributes.size(); loop++)
        {
            if(!attributes[loop].empty())
            {
                DbDatum db_d(attributes[loop].name);
                db_d << (DevLong) attributes[loop].size();
                db_data.push_back(db_d);
                for(size_t ctr = 0; ctr < attributes[loop].size(); ctr++)
                {
                    DbDatum db(attributes[loop][ctr].name);
                    db.value_string = attributes[loop][ctr].values;
                    db_data.push_back(db);
                }
            }
        }

        db_ptr->put_device_attribute_property(name, db_data);
    }
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::TangoDevice::put_pipe_properties
//
// description:
//        Store in database all the properties belonging to the device pipe(s)
//
// argument:
//         in :
//            db_ptr : Pointer to the database instance
//
//--------------------------------------------------------------------------------------------------------------------

void DbServerData::TangoDevice::put_pipe_properties(Database *db_ptr)
{
    if(!pipes.empty())
    {
        DbData db_data;
        for(size_t loop = 0; loop < pipes.size(); loop++)
        {
            if(!pipes[loop].empty())
            {
                DbDatum db_d(pipes[loop].name);
                db_d << (DevLong) pipes[loop].size();
                db_data.push_back(db_d);
                for(size_t ctr = 0; ctr < pipes[loop].size(); ctr++)
                {
                    DbDatum db(pipes[loop][ctr].name);
                    db.value_string = pipes[loop][ctr].values;
                    db_data.push_back(db);
                }
            }
        }

        db_ptr->put_device_pipe_property(name, db_data);
    }
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::TangoClass::TangoClass
//
// description:
//        Constructor for the DbServerData inner structure TangoClass. There is one instance of this struc for
//        every tango class defined in the device server process
//
// argument:
//         in :
//            - na : The Tango class name
//            - full_ds_name : The device server process name
//            - db : Pointer to the database instance
//
//--------------------------------------------------------------------------------------------------------------------

DbServerData::TangoClass::TangoClass(const std::string &na, const std::string &full_ds_name, Database *db) :
    name(na)
{
    //
    // First, read class properties
    //

    DbDatum cl_prop = db->get_class_property_list(name);
    if(!cl_prop.is_empty())
    {
        std::vector<std::string> vs;
        cl_prop >> vs;

        DbData db_data;
        for(size_t loop = 0; loop < vs.size(); loop++)
        {
            db_data.emplace_back(vs[loop]);
        }
        db->get_class_property(name, db_data);

        for(size_t loop = 0; loop < vs.size(); loop++)
        {
            TangoProperty tp(db_data[loop].name, db_data[loop].value_string);
            properties.push_back(tp);
        }
    }

    //
    //  Get class attribute list
    //

    std::string all("*");
    DbDatum cl_att_list = db->get_class_attribute_list(name, all);
    if(!cl_att_list.is_empty())
    {
        std::vector<std::string> vs;
        cl_att_list >> vs;

        for(size_t loop = 0; loop < vs.size(); loop++)
        {
            DbData db_data;
            db->get_class_attribute_property(vs[loop], db_data);
            for(size_t lo = 0; lo < db_data.size(); lo++)
            {
                TangoAttribute ta(vs[loop]);
                ta.push_back(TangoProperty(db_data[lo].name, db_data[lo].value_string));
                attributes.push_back(ta);
            }
        }
    }

    //
    //  Get class pipe list
    //

    try
    {
        DbDatum cl_pipe_list = db->get_class_pipe_list(name, all);
        if(!cl_pipe_list.is_empty())
        {
            std::vector<std::string> vs;
            cl_pipe_list >> vs;

            for(size_t loop = 0; loop < vs.size(); loop++)
            {
                DbData db_data;
                db->get_class_pipe_property(vs[loop], db_data);
                for(size_t lo = 0; lo < db_data.size(); lo++)
                {
                    TangoPipe ta(vs[loop]);
                    ta.push_back(TangoProperty(db_data[lo].name, db_data[lo].value_string));
                    pipes.push_back(ta);
                }
            }
        }
    }
    catch(DevFailed &e)
    {
        std::string reason(e.errors[0].reason.in());
        if(reason != API_CommandNotFound)
        {
            throw;
        }
    }

    //
    // Get all devices for this class
    //

    DbDatum dev_names = db->get_device_name(const_cast<std::string &>(full_ds_name), name);

    for(size_t loop = 0; loop < dev_names.value_string.size(); loop++)
    {
        TangoDevice td(dev_names.value_string[loop]);
        push_back(td);
    }
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::TangoClass::put_properties
//
// description:
//        Store in database all the class properties
//
// argument:
//         in :
//            - db_ptr : Pointer to the database instance
//
//--------------------------------------------------------------------------------------------------------------------

void DbServerData::TangoClass::put_properties(Database *db_ptr)
{
    if(!properties.empty())
    {
        DbData db_data;
        for(size_t ctr = 0; ctr < properties.size(); ctr++)
        {
            DbDatum prop(properties[ctr].name);
            prop.value_string = properties[ctr].values;
            db_data.push_back(prop);
        }
        db_ptr->put_class_property(name, db_data);
    }
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::TangoClass::put_attribute_properties
//
// description:
//        Store in database all the class attribute properties
//
// argument:
//         in :
//            - db_ptr : Pointer to the database instance
//
//--------------------------------------------------------------------------------------------------------------------

void DbServerData::TangoClass::put_attribute_properties(Database *db_ptr)
{
    if(!attributes.empty())
    {
        DbData db_data;
        for(size_t loop = 0; loop < attributes.size(); loop++)
        {
            if(!attributes[loop].empty())
            {
                DbDatum db_d(attributes[loop].name);
                db_d << (DevLong) attributes[loop].size();
                db_data.push_back(db_d);
                for(size_t ctr = 0; ctr < attributes[loop].size(); ctr++)
                {
                    DbDatum db(attributes[loop][ctr].name);
                    db.value_string = attributes[loop][ctr].values;
                    db_data.push_back(db);
                }
            }
        }

        db_ptr->put_class_attribute_property(name, db_data);
    }
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::TangoClass::put_pipe_properties
//
// description:
//        Store in database all the class pipe properties
//
// argument:
//         in :
//            - db_ptr : Pointer to the database instance
//
//--------------------------------------------------------------------------------------------------------------------

void DbServerData::TangoClass::put_pipe_properties(Database *db_ptr)
{
    if(!pipes.empty())
    {
        DbData db_data;
        for(size_t loop = 0; loop < pipes.size(); loop++)
        {
            if(!pipes[loop].empty())
            {
                DbDatum db_d(pipes[loop].name);
                db_d << (DevLong) pipes[loop].size();
                db_data.push_back(db_d);
                for(size_t ctr = 0; ctr < pipes[loop].size(); ctr++)
                {
                    DbDatum db(pipes[loop][ctr].name);
                    db.value_string = pipes[loop][ctr].values;
                    db_data.push_back(db);
                }
            }
        }

        db_ptr->put_class_pipe_property(name, db_data);
    }
}

//--------------------------------------------------------------------------------------------------------------------
//
// method:
//         DbServerData::TangoClass::remove_properties
//
// description:
//        Remove from the database all the class properties
//
// argument:
//         in :
//            - db_ptr : Pointer to the database instance
//
//--------------------------------------------------------------------------------------------------------------------

void DbServerData::TangoClass::remove_properties(Database *db_ptr)
{
    //
    // Delete class properties if any
    //

    if(!properties.empty())
    {
        DbData props;
        for(size_t loop = 0; loop < properties.size(); loop++)
        {
            props.emplace_back(properties[loop].name);
        }

        db_ptr->delete_class_property(name, props);
    }

    //
    // Delete class attribute properties if any
    //

    if(!attributes.empty())
    {
        for(size_t loop = 0; loop < attributes.size(); loop++)
        {
            if(!attributes[loop].empty())
            {
                DbData db_data;
                db_data.emplace_back(attributes[loop].name);
                for(size_t ctr = 0; ctr < attributes[loop].size(); ctr++)
                {
                    db_data.emplace_back(attributes[loop][ctr].name);
                }
                db_ptr->delete_class_attribute_property(name, db_data);
            }
        }
    }

    //
    // Delete class pipe properties if any
    //

    if(!pipes.empty())
    {
        for(size_t loop = 0; loop < pipes.size(); loop++)
        {
            if(!pipes[loop].empty())
            {
                DbData db_data;
                db_data.emplace_back(pipes[loop].name);
                for(size_t ctr = 0; ctr < pipes[loop].size(); ctr++)
                {
                    db_data.emplace_back(pipes[loop][ctr].name);
                }
                db_ptr->delete_class_pipe_property(name, db_data);
            }
        }
    }
}

} // namespace Tango
