// NOLINTBEGIN(*)

#include <tango/tango.h>

using namespace Tango;

#include "logging.h"

#define CLASS_NAME "DevTest"

void print_changes(const std::string &desc, const std::string &server, DbData &db_data)
{
    TEST_LOG << desc << " -> " << server << " : " << std::endl;

    DbData::iterator it = db_data.begin();
    while(it != db_data.end())
    {
        TEST_LOG << "   " << it->name;
        for(size_t i = 0; i < it->value_string.size(); i++)
        {
            TEST_LOG << ((i == 0) ? (" = ") : (" , ")) << it->value_string[i];
        }
        //            TEST_LOG << "   " << ((i == 0) ? ((str << it->value_string[i], (str >> nb_prop && str.eof()) ? " :
        //            " : (" = " + it->value_string[i]))) : (" , " + it->value_string[i]));
        TEST_LOG << std::endl;
        ++it;
    }
    TEST_LOG << std::endl;
}

/*
 * This is a utility that configures properties of test device servers provided as
 * command line parameters. Attribute values, their ranges and other properties are
 * set to defaults used in the test suite.
 */
int main(int argc, char **argv)
{
    if(argc != 11)
    {
        TEST_LOG << "usage: " << argv[0]
                 << " dserver dserver2 fwd_dserver device1 device2 device3 device1_alias "
                    "attribute_alias fwd_device device20"
                 << std::endl;
        exit(-1);
    }

    std::string dserver_name = argv[1];
    std::string dserver2_name = argv[2];
    std::string fwd_dserver_name = argv[3];
    std::string device1_name = argv[4];
    std::string device2_name = argv[5];
    std::string device3_name = argv[6];
    std::string device1_alias = argv[7];
    std::string attribute_alias = argv[8];
    std::string fwd_dev_name = argv[9];
    std::string device20_name = argv[10];

    Database *db = new Database();
    DbData db_data;

    long num_prop;
    std::string str;
    std::vector<std::string> str_vec;
    DevLong lg;
    DeviceData din;
    DbDevInfos db_dev_infos;
    DbDevInfo db_dev_info_1, db_dev_info_2, db_dev_info_3;

    // Define device server
    str = dserver_name;
    db_dev_info_1.name = device1_name;
    db_dev_info_1._class = CLASS_NAME;
    db_dev_info_2.name = device2_name;
    db_dev_info_2._class = CLASS_NAME;
    db_dev_info_3.name = device3_name;
    db_dev_info_3._class = CLASS_NAME;
    db_dev_infos.push_back(db_dev_info_1);
    db_dev_infos.push_back(db_dev_info_2);
    db_dev_infos.push_back(db_dev_info_3);

    try
    {
        db->add_server(str, db_dev_infos);
        for(auto info : db_dev_infos)
        {
            TEST_LOG << "Added test server : " << str << " -> " << info.name << ", class : " << info._class
                     << std::endl;
        }
        TEST_LOG << std::endl;
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot create test server" << std::endl;
    }

    db_dev_infos.clear();

    str = dserver2_name;
    DbDevInfo device20Info;
    device20Info.name = device20_name;
    device20Info._class = CLASS_NAME;

    db_dev_infos.push_back(device20Info);

    try
    {
        db->add_server(str, db_dev_infos);
        for(auto info : db_dev_infos)
        {
            TEST_LOG << "Added test server : " << str << " -> " << info.name << ", class : " << info._class
                     << std::endl;
        }
        TEST_LOG << std::endl;
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot create test server" << std::endl;
    }

    // Define device server
    str = fwd_dserver_name;
    DbDevInfo fwdTestInfo;
    fwdTestInfo.name = fwd_dev_name;
    fwdTestInfo._class = "FwdTest";
    db_dev_infos.push_back(fwdTestInfo);
    try
    {
        db->add_server(str, db_dev_infos);
        for(auto info : db_dev_infos)
        {
            TEST_LOG << "Added test server : " << str << " -> " << info.name << ", class : " << info._class
                     << std::endl;
        }
        TEST_LOG << std::endl;
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot create test server" << std::endl;
    }

    db_dev_infos.clear();
    //
    // DsCache/test pseudo server (creation & properties)
    //

    str = "DsCache/test";
    db_dev_info_1.name = "test/cache1/1";
    db_dev_info_1._class = "CacheTest1";
    db_dev_info_2.name = "test/cache1/2";
    db_dev_info_2._class = "CacheTest1";
    db_dev_info_3.name = "test/cache2/1";
    db_dev_info_3._class = "CacheTest2";
    db_dev_infos.push_back(db_dev_info_1);
    db_dev_infos.push_back(db_dev_info_2);
    db_dev_infos.push_back(db_dev_info_3);

    try
    {
        db->add_server(str, db_dev_infos);
        for(size_t i = 0; i < db_dev_infos.size(); i++)
        {
            TEST_LOG << "Added pseudo server : " << str << " -> " << db_dev_infos[i].name
                     << ", class : " << db_dev_infos[i]._class << std::endl;
        }
        TEST_LOG << std::endl;
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot create DsCache/test pseudo server" << std::endl;
    }

    db_data.clear();
    DbDatum tst_property_1("tst_property");
    lg = 15;
    tst_property_1 << lg;
    db_data.push_back(tst_property_1);

    try
    {
        db->put_device_property(db_dev_info_1.name, db_data);
        print_changes("Device properties", db_dev_info_1.name.c_str(), db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set test/cache1/1 properties" << std::endl;
    }

    db_data.clear();
    DbDatum tst_property_2("tst_property");
    lg = 25;
    tst_property_2 << lg;
    db_data.push_back(tst_property_2);

    try
    {
        db->put_device_property(db_dev_info_2.name, db_data);
        print_changes("Device properties", db_dev_info_2.name.c_str(), db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set test/cache1/2 properties" << std::endl;
    }

    //
    // dserver properties
    //

    db_data.clear();
    DbDatum logging_level("logging_level");
    std::string logging_str = "WARNING";
    logging_level << logging_str;
    db_data.push_back(logging_level);

    try
    {
        db->put_device_property(dserver_name, db_data);
        print_changes("Dserver properties", dserver_name.c_str(), db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set dserver properties" << std::endl;
    }

    //
    // Properties common to all devices
    //

    // class properties

    db_data.clear();
    DbDatum allowed_access_cmd("AllowedAccessCmd");
    str = "IOFloat";
    allowed_access_cmd << str;
    db_data.push_back(allowed_access_cmd);

    try
    {
        db->put_class_property(CLASS_NAME, db_data);
        print_changes("Class properties", CLASS_NAME, db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set class properties" << std::endl;
    }

    // attribute properties defined on class level

    db_data.clear();
    DbDatum class_string_spec_attr("String_spec_attr"), class_string_spec_attr_label("label"),
        class_string_spec_attr_unit("unit"), class_string_spec_attr_format("format");

    num_prop = 3;
    class_string_spec_attr << num_prop;
    db_data.push_back(class_string_spec_attr);
    str = "Class label";
    class_string_spec_attr_label << str;
    db_data.push_back(class_string_spec_attr_label);
    str = "Class unit";
    class_string_spec_attr_unit << str;
    db_data.push_back(class_string_spec_attr_unit);
    str = "Class format";
    class_string_spec_attr_format << str;
    db_data.push_back(class_string_spec_attr_format);

    DbDatum class_added_short_attr("Added_short_attr"), class_added_short_attr_label("label");

    num_prop = 1;
    class_added_short_attr << num_prop;
    db_data.push_back(class_added_short_attr);
    str = "From db (class)";
    class_added_short_attr_label << str;
    db_data.push_back(class_added_short_attr_label);

    DbDatum class_def_class_attr("DefClassAttr"), class_def_class_attr_description("description"),
        class_def_class_attr_min_value("min_value"), class_def_class_attr_event_period("event_period"),
        class_def_class_attr_rel_change("rel_change"), class_def_class_attr_delta_val("delta_val"),
        class_def_class_attr_delta_t("delta_t");

    num_prop = 6;
    class_def_class_attr << num_prop;
    db_data.push_back(class_def_class_attr);
    str = "Class desc";
    class_def_class_attr_description << str;
    db_data.push_back(class_def_class_attr_description);
    str = "20";
    class_def_class_attr_min_value << str;
    db_data.push_back(class_def_class_attr_min_value);
    str = "500";
    class_def_class_attr_event_period << str;
    db_data.push_back(class_def_class_attr_event_period);
    str = "33,44";
    class_def_class_attr_rel_change << str;
    db_data.push_back(class_def_class_attr_rel_change);
    str = "5";
    class_def_class_attr_delta_val << str;
    db_data.push_back(class_def_class_attr_delta_val);
    str = "2";
    class_def_class_attr_delta_t << str;
    db_data.push_back(class_def_class_attr_delta_t);

    DbDatum class_def_class_user_attr("DefClassUserAttr"), class_def_class_user_attr_description("description"),
        class_def_class_user_attr_min_value("min_value"), class_def_class_user_attr_event_period("event_period"),
        class_def_class_user_attr_rel_change("rel_change"), class_def_class_user_attr_delta_val("delta_val"),
        class_def_class_user_attr_delta_t("delta_t");

    num_prop = 6;
    class_def_class_user_attr << num_prop;
    db_data.push_back(class_def_class_user_attr);
    str = "Class description";
    class_def_class_user_attr_description << str;
    db_data.push_back(class_def_class_user_attr_description);
    str = "20";
    class_def_class_user_attr_min_value << str;
    db_data.push_back(class_def_class_user_attr_min_value);
    str = "500";
    class_def_class_user_attr_event_period << str;
    db_data.push_back(class_def_class_user_attr_event_period);
    str = "33,44";
    class_def_class_user_attr_rel_change << str;
    db_data.push_back(class_def_class_user_attr_rel_change);
    str = "5";
    class_def_class_user_attr_delta_val << str;
    db_data.push_back(class_def_class_user_attr_delta_val);
    str = "2";
    class_def_class_user_attr_delta_t << str;
    db_data.push_back(class_def_class_user_attr_delta_t);

    try
    {
        db->put_class_attribute_property(CLASS_NAME, db_data);
        print_changes("Class level attribute properties", CLASS_NAME, db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set attribute properties defined on class level" << std::endl;
    }

    // attribute properties defined on device level

    db_data.clear();
    DbDatum long_attr("Long_attr"), long_attr_min_alarm("min_alarm"), long_attr_max_alarm("max_alarm");

    num_prop = 2;
    long_attr << num_prop;
    db_data.push_back(long_attr);
    lg = 1000;
    long_attr_min_alarm << lg;
    db_data.push_back(long_attr_min_alarm);
    lg = 1500;
    long_attr_max_alarm << lg;
    db_data.push_back(long_attr_max_alarm);

    for(int i = 2; i < argc; i++)
    {
        try
        {
            db->put_device_attribute_property(argv[i], db_data);
            print_changes("Common device properties", argv[i], db_data);
        }
        catch(...)
        {
            TEST_LOG << "Exception: cannot set common properties for the device: " << argv[i] << std::endl;
        }
    }

    //
    // Device specific properties
    //

    // device1

    db_data.clear();
    DbDatum added_short_attr("Added_short_attr"), added_short_attr_format("format");

    num_prop = 1;

    added_short_attr << num_prop;
    db_data.push_back(added_short_attr);
    str = "From db (device)";
    added_short_attr_format << str;
    db_data.push_back(added_short_attr_format);

    try
    {
        db->put_device_attribute_property(device1_name, db_data);
        print_changes("Device specific attribute properties", device1_name.c_str(), db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set specific attribute properties for the device: " << device1_name << std::endl;
    }

    db_data.clear();
    DbDatum min_poll_period("min_poll_period");
    lg = 200;
    min_poll_period << lg;
    db_data.push_back(min_poll_period);

    DbDatum cmd_min_poll_period("cmd_min_poll_period");
    str_vec.push_back("IOExcept");
    str_vec.push_back("500");
    cmd_min_poll_period << str_vec;
    str_vec.clear();
    db_data.push_back(cmd_min_poll_period);

    DbDatum tst_property("tst_property");
    lg = 25;
    tst_property << lg;
    db_data.push_back(tst_property);

    try
    {
        db->put_device_property(device1_name, db_data);
        print_changes("Device specific properties", device1_name.c_str(), db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set specific properties for the device: " << device1_name << std::endl;
    }

    try
    {
        db->put_device_alias(device1_name, device1_alias);
        TEST_LOG << "Device alias -> " << device1_name << " :\n   " << device1_alias << "\n" << std::endl;
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set device alias for the device: " << device1_name << std::endl;
    }

    try
    {
        str = device1_name + "/" + "Short_attr";
        db->put_attribute_alias(str, attribute_alias);
        TEST_LOG << "Attribute alias -> " << str << " :\n   " << attribute_alias << "\n" << std::endl;
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set attribute alias for the attribute: " << device1_name + "/" + "Short_attr"
                 << std::endl;
    }

    // device2

    db_data.clear();
    DbDatum string_spec_attr("String_spec_attr"), string_spec_attr_label("label"), string_spec_attr_unit("unit"),
        string_spec_attr_format("format");

    num_prop = 3;

    string_spec_attr << num_prop;
    db_data.push_back(string_spec_attr);
    str = "Device label";
    string_spec_attr_label << str;
    db_data.push_back(string_spec_attr_label);
    str = "Device unit";
    string_spec_attr_unit << str;
    db_data.push_back(string_spec_attr_unit);
    str = "%s";
    string_spec_attr_format << str;
    db_data.push_back(string_spec_attr_format);

    try
    {
        db->put_device_attribute_property(device2_name, db_data);
        print_changes("Device specific properties", device2_name.c_str(), db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set specific properties for the device: " << device2_name << std::endl;
    }

    db_data.clear();

    Tango::DbDatum fwd_att1("fwd_short_rw");
    Tango::DbDatum root_att1(RootAttrPropName);
    fwd_att1 << (short) 1;
    std::string r_name = device1_name + "/short_attr_rw";
    ;
    root_att1 << r_name;
    db_data.push_back(fwd_att1);
    db_data.push_back(root_att1);

    Tango::DbDatum fwd_att2("fwd_spec_double");
    Tango::DbDatum root_att2(RootAttrPropName);
    fwd_att2 << (short) 1;
    r_name = device1_name + "/double_spec_attr";
    ;
    root_att2 << r_name;
    db_data.push_back(fwd_att2);
    db_data.push_back(root_att2);

    Tango::DbDatum fwd_att3("fwd_string_w");
    Tango::DbDatum root_att3(RootAttrPropName);
    fwd_att3 << (short) 1;
    r_name = device1_name + "/string_attr_w2";
    ;
    root_att3 << r_name;
    db_data.push_back(fwd_att3);
    db_data.push_back(root_att3);

    Tango::DbDatum fwd_att4("fwd_ima_string_rw");
    Tango::DbDatum root_att4(RootAttrPropName);
    fwd_att4 << (short) 1;
    r_name = device2_name + "/string_ima_attr_rw";
    ;
    root_att4 << r_name;
    db_data.push_back(fwd_att4);
    db_data.push_back(root_att4);

    Tango::DbDatum fwd_att5("fwd_state");
    Tango::DbDatum root_att5(RootAttrPropName);
    fwd_att5 << (short) 1;
    r_name = device2_name + "/state";
    ;
    root_att5 << r_name;
    db_data.push_back(fwd_att5);
    db_data.push_back(root_att5);

    Tango::DbDatum fwd_att6("fwd_string_rw");
    Tango::DbDatum root_att6(RootAttrPropName);
    fwd_att6 << (short) 1;
    r_name = device1_name + "/string_attr_rw";
    root_att6 << r_name;
    db_data.push_back(fwd_att6);
    db_data.push_back(root_att6);

    Tango::DbDatum fwd_att7("fwd_ima_string_r");
    Tango::DbDatum root_att7(RootAttrPropName);
    fwd_att7 << (short) 1;
    // State is temporarily entered, the value changed in the test
    r_name = device1_name + "/state";
    root_att7 << r_name;
    db_data.push_back(fwd_att7);
    db_data.push_back(root_att7);

    try
    {
        db->put_device_attribute_property(fwd_dev_name, db_data);
        print_changes("Device specific attribute properties", fwd_dev_name.c_str(), db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set specific attribute properties for the device: " << fwd_dev_name << std::endl;
    }

    db_data.clear();

    // TODO rewrite using functional style

    // pipe class level configuration
    DbDatum pipeConf4("PipeConf4");
    pipeConf4 << (short) 2;
    DbDatum pipeConf4DbClassLabel("label");
    pipeConf4DbClassLabel << "DB_class_def_label";
    DbDatum pipeConf4DbClassDescription("description");
    pipeConf4DbClassDescription << "Bidon";

    DbDatum pipeConf5("PipeConf5");
    pipeConf5 << (short) 1;
    DbDatum pipeConf5DbClassLabel("label");
    pipeConf5DbClassLabel << "ClassDefinedLabel";

    DbDatum pipeConf6("PipeConf6");
    pipeConf6 << (short) 1;
    DbDatum pipeConf6DbClassDesc("description");
    pipeConf6DbClassDesc << "ClassDefinedDesc";

    DbDatum pipeConf7("PipeConf7");
    pipeConf7 << (short) 1;
    DbDatum pipeConf7DbClassDesc("description");
    pipeConf7DbClassDesc << "AnotherClassDefinedDesc";

    db_data.push_back(pipeConf4);
    db_data.push_back(pipeConf4DbClassLabel);
    db_data.push_back(pipeConf4DbClassDescription);
    db_data.push_back(pipeConf5);
    db_data.push_back(pipeConf5DbClassLabel);
    db_data.push_back(pipeConf6);
    db_data.push_back(pipeConf6DbClassDesc);
    db_data.push_back(pipeConf7);
    db_data.push_back(pipeConf7DbClassDesc);

    try
    {
        db->put_class_pipe_property(CLASS_NAME, db_data);
        print_changes("Set pipe properties at class level", CLASS_NAME, db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set pipe properties at class level: " << CLASS_NAME << std::endl;
    }

    // pipe class level configuration
    DbDatum pipeConf3("PipeConf3");
    pipeConf3 << (short) 1;
    DbDatum pipeConf3DbClassLabel("label");
    pipeConf3DbClassLabel << "OverWrittenPipeLabel";

    DbDatum pipeConf4_dev("PipeConf4");
    pipeConf4_dev << (short) 1;
    DbDatum pipeConf4_devDbClassDesc("description");
    pipeConf4_devDbClassDesc << "DB_device_def_desc";

    db_data.push_back(pipeConf3);
    db_data.push_back(pipeConf3DbClassLabel);
    db_data.push_back(pipeConf4_dev);
    db_data.push_back(pipeConf4_devDbClassDesc);

    try
    {
        db->put_device_pipe_property(device1_name, db_data);
        print_changes("Set pipe properties at device level", device1_name.c_str(), db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set specific attribute properties for the device: " << fwd_dev_name << std::endl;
    }

    DbDatum short_attr_rw("short_attr_rw");
    short_attr_rw << (short) 2;
    DbDatum short_attr_rw_unit("unit");
    str = "Kg";
    short_attr_rw_unit << str;
    DbDatum short_attr_rw_std_unit("standard_unit");
    str = "1.0";
    short_attr_rw_std_unit << str;

    db_data.push_back(short_attr_rw);
    db_data.push_back(short_attr_rw_unit);
    db_data.push_back(short_attr_rw_std_unit);

    try
    {
        db->put_device_attribute_property(device1_name, db_data);
        print_changes("Device specific attribute properties", device1_name.c_str(), db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set specific attribute properties for the device: " << device1_name << std::endl;
    }

    // Define polling for DevTest/test2
    DbDatum polled_attr("polled_attr");
    std::vector<std::string> attrs;
    attrs.push_back("event_change_tst");
    attrs.push_back("3000");
    polled_attr << attrs;

    db_data.push_back(polled_attr);
    try
    {
        db->put_device_property(device20_name, db_data);
        print_changes("Device specific attribute properties", device20_name.c_str(), db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set specific attribute properties for the device: " << device20_name
                 << std::endl;
    }

    DbDatum eventProperties("event_change_tst");
    eventProperties << (short) 2;
    DbDatum eventPropertiesAbsCh("abs_change");
    eventPropertiesAbsCh << 1.0;
    DbDatum eventPropertiesRelCh("rel_change");
    eventPropertiesRelCh << 1.0;

    db_data.push_back(eventProperties);
    db_data.push_back(eventPropertiesAbsCh);
    db_data.push_back(eventPropertiesRelCh);

    try
    {
        db->put_device_attribute_property(device20_name, db_data);
        print_changes("Device specific attribute properties", device20_name.c_str(), db_data);
    }
    catch(...)
    {
        TEST_LOG << "Exception: cannot set specific attribute properties for the device: " << device20_name
                 << std::endl;
    }

    delete db;

    return 0;
}

// NOLINTEND(*)
