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

#include <tango/server/DServerClass.h>

#include <tango/internal/server/dservercmds.h>
#include <tango/internal/server/logcmds.h>
#include <tango/internal/server/pollcmds.h>
#include <tango/internal/server/telemetrycmds.h>

#include <tango/client/Database.h>
#include <tango/server/DServer.h>
#include <tango/server/NotifdEventSupplier.h>

#include <algorithm>
#include <memory>

namespace Tango
{

DServerClass *DServerClass::_instance = nullptr;

void DServerClass::cleanup()
{
    delete _instance;
    _instance = nullptr;
}

//+----------------------------------------------------------------------------
//
// method :         DServerClass::DServerClass()
//
// description :     constructor for the DServerClass class
//            The constructor add specific class commands to the
//            command list, create a device of the DServer class
//            retrieve all classes which must be created within the
//            server and finally, creates these classes
//
// argument : in :     - s : The class name
//
//-----------------------------------------------------------------------------

DServerClass::DServerClass(const std::string &s) :
    DeviceClass(s)
{
    TANGO_LOG_DEBUG << "Entering DServerClass::DServerClass() for class " << s << std::endl;
    try
    {
        //
        // Add class command(s) to the command_list
        //

        command_factory();

        //
        // Sort commands
        //

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

        //
        // Create device name from device server name
        //

        std::string dev_name(DSDeviceDomain);
        dev_name.append(1, '/');
        dev_name.append(Tango::Util::instance()->get_ds_exec_name());
        dev_name.append(1, '/');
        dev_name.append(Tango::Util::instance()->get_ds_inst_name());

        Tango::DevVarStringArray dev_list(1);
        dev_list.length(1);
        dev_list[0] = dev_name.c_str();

        //
        // Create the device server device
        //

        device_factory(&dev_list);
    }
    catch(std::bad_alloc &)
    {
        for(unsigned long i = 0; i < command_list.size(); i++)
        {
            delete command_list[i];
        }
        command_list.clear();

        if(!device_list.empty())
        {
            for(unsigned long i = 0; i < device_list.size(); i++)
            {
                delete device_list[i];
            }
            device_list.clear();
        }
        std::cerr << "Can't allocate memory while building the DServerClass object" << std::endl;
        throw;
    }
    TANGO_LOG_DEBUG << "Leaving DServerClass::DServerClass() for class " << s << std::endl;
}

//+----------------------------------------------------------------------------
//
// method :         DServerClass::Instance
//
// description :     Create the object if not already done. Otherwise, just
//            return a pointer to the object
//
//-----------------------------------------------------------------------------

DServerClass *DServerClass::instance()
{
    if(_instance == nullptr)
    {
        std::cerr << "Class DServer is not initialised!" << std::endl;
        TANGO_THROW_EXCEPTION(API_DServerClassNotInitialised, "The DServerClass is not yet initialised, please wait!");
        // exit(-1);
    }
    return _instance;
}

DServerClass *DServerClass::init()
{
    if(_instance == nullptr)
    {
        try
        {
            std::string s("DServer");
            _instance = new DServerClass(s);
        }
        catch(std::bad_alloc &)
        {
            throw;
        }
    }
    return _instance;
}

//+----------------------------------------------------------------------------
//
// method :         DServerClass::command_factory
//
// description :     Create the command object(s) and store them in the
//            command list
//
//-----------------------------------------------------------------------------

void DServerClass::command_factory()
{
    command_list.push_back(new DevRestartCmd("DevRestart", Tango::DEV_STRING, Tango::DEV_VOID, "Device name"));
    command_list.push_back(new DevRestartServerCmd("RestartServer", Tango::DEV_VOID, Tango::DEV_VOID));
    command_list.push_back(
        new DevQueryClassCmd("QueryClass", Tango::DEV_VOID, Tango::DEVVAR_STRINGARRAY, "Device server class(es) list"));
    command_list.push_back(new DevQueryDeviceCmd(
        "QueryDevice", Tango::DEV_VOID, Tango::DEVVAR_STRINGARRAY, "Device server device(s) list"));
    command_list.push_back(new DevQuerySubDeviceCmd(
        "QuerySubDevice", Tango::DEV_VOID, Tango::DEVVAR_STRINGARRAY, "Device server sub device(s) list"));
    command_list.push_back(new DevKillCmd("Kill", Tango::DEV_VOID, Tango::DEV_VOID));
    command_list.push_back(new DevQueryEventSystemCmd(
        "QueryEventSystem", Tango::DEV_VOID, Tango::DEV_STRING, "JSON object with information about the event system"));
    command_list.push_back(
        new DevEnableEventSystemPerfMonCmd("EnableEventSystemPerfMon",
                                           Tango::DEV_BOOLEAN,
                                           Tango::DEV_VOID,
                                           "Enable or disable the collection of performance samples for events"));

    //
    // Now, commands related to polling
    //

    command_list.push_back(
        new PolledDeviceCmd("PolledDevice", Tango::DEV_VOID, Tango::DEVVAR_STRINGARRAY, "Polled device name list"));
    command_list.push_back(new DevPollStatusCmd(
        "DevPollStatus", Tango::DEV_STRING, Tango::DEVVAR_STRINGARRAY, "Device name", "Device polling status"));
    std::string msg("Lg[0]=Upd period.");
    msg = msg + (" Str[0]=Device name");
    msg = msg + (". Str[1]=Object type");
    msg = msg + (". Str[2]=Object name");

    command_list.push_back(new AddObjPollingCmd("AddObjPolling", Tango::DEVVAR_LONGSTRINGARRAY, Tango::DEV_VOID, msg));

    command_list.push_back(
        new UpdObjPollingPeriodCmd("UpdObjPollingPeriod", Tango::DEVVAR_LONGSTRINGARRAY, Tango::DEV_VOID, msg));

    msg = "Str[0]=Device name. Str[1]=Object type. Str[2]=Object name";

    command_list.push_back(new RemObjPollingCmd("RemObjPolling", Tango::DEVVAR_STRINGARRAY, Tango::DEV_VOID, msg));

    command_list.push_back(new StopPollingCmd("StopPolling", Tango::DEV_VOID, Tango::DEV_VOID));

    command_list.push_back(new StartPollingCmd("StartPolling", Tango::DEV_VOID, Tango::DEV_VOID));

    msg = "Str[i]=Device-name. Str[i+1]=Target-type::Target-name";

    command_list.push_back(new AddLoggingTarget("AddLoggingTarget", Tango::DEVVAR_STRINGARRAY, Tango::DEV_VOID, msg));

    command_list.push_back(
        new RemoveLoggingTarget("RemoveLoggingTarget", Tango::DEVVAR_STRINGARRAY, Tango::DEV_VOID, msg));

    command_list.push_back(new GetLoggingTarget("GetLoggingTarget",
                                                Tango::DEV_STRING,
                                                Tango::DEVVAR_STRINGARRAY,
                                                std::string("Device name"),
                                                std::string("Logging target list")));

    command_list.push_back(new SetLoggingLevel("SetLoggingLevel",
                                               Tango::DEVVAR_LONGSTRINGARRAY,
                                               Tango::DEV_VOID,
                                               std::string("Lg[i]=Logging Level. Str[i]=Device name.")));

    command_list.push_back(new GetLoggingLevel("GetLoggingLevel",
                                               Tango::DEVVAR_STRINGARRAY,
                                               Tango::DEVVAR_LONGSTRINGARRAY,
                                               std::string("Device list"),
                                               std::string("Lg[i]=Logging Level. Str[i]=Device name.")));

    command_list.push_back(new StopLogging("StopLogging", Tango::DEV_VOID, Tango::DEV_VOID));

    command_list.push_back(new StartLogging("StartLogging", Tango::DEV_VOID, Tango::DEV_VOID));
    command_list.push_back(new EventSubscriptionChangeCmd("EventSubscriptionChange",
                                                          Tango::DEVVAR_STRINGARRAY,
                                                          Tango::DEV_LONG,
                                                          "Event consumer wants to subscribe to",
                                                          "Tango lib release"));

    command_list.push_back(new ZmqEventSubscriptionChangeCmd());

    command_list.push_back(
        new EventConfirmSubscriptionCmd("EventConfirmSubscription",
                                        Tango::DEVVAR_STRINGARRAY,
                                        Tango::DEV_VOID,
                                        "Str[0] = dev1 name, Str[1] = att1 name, Str[2] = event name, Str[3] = dev2 "
                                        "name, Str[4] = att2 name, Str[5] = event name,..."));

    command_list.push_back(
        new QueryWizardClassPropertyCmd("QueryWizardClassProperty",
                                        Tango::DEV_STRING,
                                        Tango::DEVVAR_STRINGARRAY,
                                        "Class name",
                                        "Class property list (name - description and default value)"));

    command_list.push_back(
        new QueryWizardDevPropertyCmd("QueryWizardDevProperty",
                                      Tango::DEV_STRING,
                                      Tango::DEVVAR_STRINGARRAY,
                                      "Class name",
                                      "Device property list (name - description and default value)"));

    //
    // Locking device commands
    //

    command_list.push_back(new LockDeviceCmd(
        "LockDevice", Tango::DEVVAR_LONGSTRINGARRAY, Tango::DEV_VOID, "Str[0] = Device name. Lg[0] = Lock validity"));

    command_list.push_back(new UnLockDeviceCmd("UnLockDevice",
                                               Tango::DEVVAR_LONGSTRINGARRAY,
                                               Tango::DEV_LONG,
                                               "Str[x] = Device name(s). Lg[0] = Force flag",
                                               "Device global lock counter"));

    command_list.push_back(
        new ReLockDevicesCmd("ReLockDevices", Tango::DEVVAR_STRINGARRAY, Tango::DEV_VOID, "Device(s) name"));

    command_list.push_back(new DevLockStatusCmd(
        "DevLockStatus", Tango::DEV_STRING, Tango::DEVVAR_LONGSTRINGARRAY, "Device name", "Device locking status"));

    if(Util::instance()->use_file_db())
    {
        command_list.push_back(new QueryEventChannelIORCmd(
            "QueryEventChannelIOR", Tango::DEV_VOID, Tango::DEV_STRING, "Device server event channel IOR"));
    }

    //
    // Telemetry device commands
    //
#ifdef TANGO_USE_TELEMETRY
    command_list.push_back(
        new StartTelemetry("StartTelemetry", Tango::DEVVAR_STRINGARRAY, Tango::DEV_VOID, "Device list"));
    command_list.push_back(
        new StopTelemetry("StopTelemetry", Tango::DEVVAR_STRINGARRAY, Tango::DEV_VOID, "Device list"));
    command_list.push_back(new IsTelemetryEnabled("IsTelemetryEnabled",
                                                  Tango::DEVVAR_STRINGARRAY,
                                                  Tango::DEVVAR_LONGSTRINGARRAY,
                                                  "Device list",
                                                  "Long[i]=enable(1)/disabled(0); Str[i]=device name"));
    command_list.push_back(new SetTelemetryTopics("SetTelemetryTopics",
                                                  Tango::DEVVAR_STRINGARRAY,
                                                  Tango::DEV_VOID,
                                                  "Str[i] = Device name, Str[i+1]=# of topis, Str[i+2] ... = topics"));
    command_list.push_back(new GetTelemetryTopics("GetTelemetryTopics",
                                                  Tango::DEVVAR_STRINGARRAY,
                                                  Tango::DEVVAR_STRINGARRAY,
                                                  "Device list",
                                                  "Str[i]=device name, Str[i+1] = # of topics, Str[i+2]... = topics"));
    command_list.push_back(new SetTelemetryTracing("SetTelemetryTracing",
                                                   Tango::DEVVAR_LONGSTRINGARRAY,
                                                   Tango::DEV_VOID,
                                                   "Long[i]=enable(1)/disabled(0); Str[i]=device name"));
    command_list.push_back(new GetTelemetryTracing("GetTelemetryTracing",
                                                   Tango::DEVVAR_STRINGARRAY,
                                                   Tango::DEVVAR_LONGSTRINGARRAY,
                                                   "Device list",
                                                   "Long[i]=enable(1)/disabled(0); Str[i]=device name"));
    command_list.push_back(new SetTelemetryTracingEndpoints(
        "SetTelemetryTracingEndpoints",
        Tango::DEVVAR_STRINGARRAY,
        Tango::DEV_VOID,
        "Str[i]=Device name, Str[i+1]=# of exporter/endpoint pairs, Str[i+2]=exporter type A, Str[i+3]=endpoint A, "
        "Str[i+3]=exporter type B, Str[i+4]=endpoint B..."));
    command_list.push_back(new GetTelemetryTracingEndpoints(
        "GetTelemetryTracingEndpoints",
        Tango::DEVVAR_STRINGARRAY,
        Tango::DEVVAR_STRINGARRAY,
        "Device List",
        "Str[i]=Device Name, Str[i+1] = # of endpoints, Str[i+2]=exporter type, Str[i+3]=endpoint..."));
    command_list.push_back(
        new AddTelemetryTracingEndpoints("AddTelemetryTracingEndpoints",
                                         Tango::DEVVAR_STRINGARRAY,
                                         Tango::DEV_VOID,
                                         "Str[i]=device name, Str[i+1]=exporter type, Str[i+2]=endpoint..."));
    command_list.push_back(
        new RemoveTelemetryTracingEndpoints("RemoveTelemetryTracingEndpoints",
                                            Tango::DEVVAR_STRINGARRAY,
                                            Tango::DEV_VOID,
                                            "Str[i]=device name, Str[i+1]=exporter type, Str[i+2]=endpoint..."));
    command_list.push_back(new SetTelemetryLogging("SetTelemetryLogging",
                                                   Tango::DEVVAR_LONGSTRINGARRAY,
                                                   Tango::DEV_VOID,
                                                   "Long[i]=enable(1)/disabled(0); Str[i]=device name"));
    command_list.push_back(new GetTelemetryLogging("GetTelemetryLogging",
                                                   Tango::DEVVAR_STRINGARRAY,
                                                   Tango::DEVVAR_LONGSTRINGARRAY,
                                                   "Device list",
                                                   "Long[i]=enable(1)/disabled(0); Str[i]=device name"));
    command_list.push_back(new SetTelemetryLoggingEndpoints(
        "SetTelemetryLoggingEndpoints",
        Tango::DEVVAR_STRINGARRAY,
        Tango::DEV_VOID,
        "Str[i]=Device name, Str[i+1]=# of exporter/endpoint pairs, Str[i+2]=exporter type A, Str[i+3]=endpoint A, "
        "Str[i+3]=exporter type B, Str[i+4]=endpoint B..."));
    command_list.push_back(new GetTelemetryLoggingEndpoints(
        "GetTelemetryLoggingEndpoints",
        Tango::DEVVAR_STRINGARRAY,
        Tango::DEVVAR_STRINGARRAY,
        "Device List",
        "Str[i]=Device Name, Str[i+1] = # of endpoints, Str[i+2]=exporter type, Str[i+3]=endpoint..."));
    command_list.push_back(
        new AddTelemetryLoggingEndpoints("AddTelemetryLoggingEndpoints",
                                         Tango::DEVVAR_STRINGARRAY,
                                         Tango::DEV_VOID,
                                         "Str[i]=device name, Str[i+1]=exporter type, Str[i+2]=endpoint..."));
    command_list.push_back(
        new RemoveTelemetryLoggingEndpoints("RemoveTelemetryLoggingEndpoints",
                                            Tango::DEVVAR_STRINGARRAY,
                                            Tango::DEV_VOID,
                                            "Str[i]=device name, Str[i+1]=exporter type, Str[i+2]=endpoint..."));
#endif // TANGO_USE_TELEMETRY
}

//+----------------------------------------------------------------------------
//
// method :         DServerClass::device_factory
//
// description :     Create the device object(s) and store them in the
//            device list
//
// in :            Tango::DevVarStringArray *devlist_ptr :
//            The device name list
//
//-----------------------------------------------------------------------------

void DServerClass::device_factory(const Tango::DevVarStringArray *devlist_ptr)
{
    TANGO_LOG_DEBUG << "Entering DServerClass::device_factory()" << std::endl;
    Tango::Util *tg = Tango::Util::instance();

    for(unsigned long i = 0; i < devlist_ptr->length(); i++)
    {
        TANGO_LOG_DEBUG << "Device name : " << (*devlist_ptr)[i].in() << std::endl;

        //
        // Create device and add it into the device list
        //
        auto dserver = tg->create_dserver(
            this, std::string((*devlist_ptr)[i]), "A device server device !!", Tango::ON, "The device is ON");

        dserver->init_device();

        device_list.push_back(dserver.release());

        //
        // Export device to the outside world
        //

        if(tg->use_db() && !tg->use_file_db())
        {
            export_device(device_list.back());
        }
        else
        {
            export_device(device_list.back(), (*devlist_ptr)[i]);
        }

        //
        // After the export of the admin device, the server is marked as started
        // and the database server connection timeout is set to the classical
        // timeout value (Except for db server itself)
        //

        Database *db = tg->get_database();
        if((db != nullptr) && !tg->use_file_db())
        {
            db->set_timeout_millis(CLNT_TIMEOUT);
        }
    }
    TANGO_LOG_DEBUG << "Leaving DServerClass::device_factory()" << std::endl;
}

} // namespace Tango
