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

#include <tango/client/GroupElementFactory.h>

#include <tango/client/ApiUtil.h>
#include <tango/client/Database.h>
#include <tango/client/GroupDeviceElement.h>

namespace Tango
{

//=============================================================================
// class GroupElementFactory
//=============================================================================
GroupElements GroupElementFactory::instanciate(const std::string &name_or_pattern, int timeout_millis)
{
    return create_group_elements(resolve_device_names(name_or_pattern), timeout_millis);
}

GroupElements GroupElementFactory::create_group_elements(const DeviceNames &names, int timeout_millis)
{
    GroupElements group_elements;
    group_elements.reserve(names.size());

    for(auto name = names.begin(); name != names.end(); ++name)
    {
        group_elements.push_back(new GroupDeviceElement(*name, timeout_millis));
    }

    return group_elements;
}

DeviceNames GroupElementFactory::resolve_device_names(const std::string &name_or_pattern)
{
    const bool is_name = name_or_pattern.find('*') == std::string::npos;

    if(is_name)
    {
        return DeviceNames(1, name_or_pattern);
    }
    else
    {
        int db_port = 0;
        std::string db_host;
        std::string name_or_pattern_without_host;
        parse_name(name_or_pattern, db_host, db_port, name_or_pattern_without_host);

        const bool is_local_device = db_host.empty();

        if(is_local_device)
        {
            return resolve_local_device_names(name_or_pattern);
        }
        else
        {
            return resolve_remote_device_names(db_port, db_host, name_or_pattern_without_host);
        }
    }
}

DeviceNames GroupElementFactory::resolve_local_device_names(const std::string &name_or_pattern)
{
    std::string &name_or_pattern_non_const = const_cast<std::string &>(name_or_pattern);
    Database db;
    DbDatum db_data = db.get_device_defined(name_or_pattern_non_const);

    DeviceNames device_names;
    device_names.reserve(db_data.size());
    db_data >> device_names;
    return device_names;
}

DeviceNames GroupElementFactory::resolve_remote_device_names(int db_port,
                                                             std::string &db_host,
                                                             std::string &name_or_pattern_without_host)
{
    ApiUtil &api_util = *ApiUtil::instance();
    const int db_index = api_util.get_db_ind(db_host, db_port);
    Database &db = *(api_util.get_db_vect()[db_index]);
    DbDatum db_data = db.get_device_defined(name_or_pattern_without_host);

    DeviceNames device_names;
    device_names.reserve(db_data.size());
    db_data >> device_names;
    return add_host_and_port_to_device_names(device_names, db_host, db_port);
}

DeviceNames
    GroupElementFactory::add_host_and_port_to_device_names(const DeviceNames &names, const std::string &host, int port)
{
    DeviceNames full_names;
    full_names.reserve(names.size());

    for(auto name = names.begin(); name != names.end(); ++name)
    {
        full_names.push_back(build_full_device_name(host, port, *name));
    }

    return full_names;
}

std::string
    GroupElementFactory::build_full_device_name(const std::string &host, int port, const std::string &device_name)
{
    std::ostringstream buffer;
    buffer << "tango://" << host << ":" << port << "/" << device_name;
    return buffer.str();
}

void GroupElementFactory::parse_name(const std::string &p, std::string &db_host, int &db_port, std::string &dev_pattern)
{
    std::string pattern_name_low(p);
    std::transform(pattern_name_low.begin(), pattern_name_low.end(), pattern_name_low.begin(), ::tolower);

    // Try to find protocol specification in device pattern and analyse it

    std::string name_wo_prot;
    std::string::size_type pos = pattern_name_low.find(PROT_SEP);
    if(pos == std::string::npos)
    {
        if(pattern_name_low.size() > 2)
        {
            if((pattern_name_low[0] == '/') && (pattern_name_low[1] == '/'))
            {
                name_wo_prot = pattern_name_low.substr(2);
            }
            else
            {
                name_wo_prot = pattern_name_low;
            }
        }
        else
        {
            name_wo_prot = pattern_name_low;
        }
    }
    else
    {
        std::string protocol = pattern_name_low.substr(0, pos);

        if(protocol == TANGO_PROTOCOL)
        {
            name_wo_prot = pattern_name_low.substr(pos + 3);
        }
        else
        {
            TangoSys_OMemStream desc;
            desc << protocol;
            desc << " protocol is an unsupported protocol" << std::ends;
            TANGO_THROW_DETAILED_EXCEPTION(ApiWrongNameExcept, API_UnsupportedProtocol, desc.str());
        }
    }

    // Search if host and port are specified

    pos = name_wo_prot.find(PORT_SEP);
    if(pos == std::string::npos)
    {
        TangoSys_OMemStream desc;
        desc << "Wrong device name syntax in " << p << std::ends;

        TANGO_THROW_DETAILED_EXCEPTION(ApiWrongNameExcept, API_WrongDeviceNameSyntax, desc.str());
    }

    std::string bef_sep = name_wo_prot.substr(0, pos);
    std::string::size_type tmp = bef_sep.find(HOST_SEP);
    if(tmp != std::string::npos)
    {
        std::string tmp_host(bef_sep.substr(0, tmp));
        if(tmp_host.find('.') == std::string::npos)
        {
            DeviceProxy::get_fqdn(tmp_host);
        }
        db_host = tmp_host;
        std::string db_port_str = bef_sep.substr(tmp + 1);
        if(db_port_str.size() == 0)
        {
            TangoSys_OMemStream desc;
            desc << "Wrong device name syntax in " << p << std::ends;

            TANGO_THROW_DETAILED_EXCEPTION(ApiWrongNameExcept, API_WrongDeviceNameSyntax, desc.str());
        }
        TangoSys_MemStream s;
        s << db_port_str << std::ends;
        s >> db_port;
        dev_pattern = name_wo_prot.substr(pos + 1);
    }
    else
    {
        dev_pattern = name_wo_prot;
    }
}

} // namespace Tango
