// NOLINTBEGIN(*)

#include "cxx_common.h"

#undef SUITE_NAME
#define SUITE_NAME McastSimpleTestSuite

// In this test, the client subscribes to two multicast events
// One event comes from a device running in the same host than the client
// The other event comes from a device running in a different host (therefore using PGM to communicate)
// These two events are using different callback objects

class McastSimpleTestSuite : public CxxTest::TestSuite
{
  public:
    class EventCallBack : public Tango::CallBack
    {
      public:
        EventCallBack(McastSimpleTestSuite *ptr) :
            parent(ptr)
        {
        }

        void push_event(Tango::EventData *);

        int cb_executed;
        int cb_err;
        long val;
        long val_size;

      private:
        McastSimpleTestSuite *parent;
    };

  protected:
    DeviceProxy *local_device;
    DeviceProxy *remote_device;
    string att_name;
    int local_eve_id;
    int remote_eve_id;
    EventCallBack *cb_local;
    EventCallBack *cb_remote;

  public:
    SUITE_NAME()
    {
        //
        // Arguments check -------------------------------------------------
        //

        string local_device_name;
        string remote_device_name;

        cb_local = new EventCallBack(this);
        cb_remote = new EventCallBack(this);

        // local parameters, obtained from the command line
        local_device_name = CxxTest::TangoPrinter::get_param_loc("local_device", "local device name");
        remote_device_name = CxxTest::TangoPrinter::get_param_loc("remote_device", "remote device name");

        // always add this line, otherwise arguments will not be parsed correctly
        CxxTest::TangoPrinter::validate_args();

        //
        // Initialization --------------------------------------------------
        //

        att_name = "Event_change_tst";

        local_init(local_device_name, local_device);
        local_init(remote_device_name, remote_device);

        cb_local->cb_executed = 0;
        cb_local->cb_err = 0;

        cb_remote->cb_executed = 0;
        cb_remote->cb_err = 0;
    }

    virtual ~SUITE_NAME()
    {
        local_device->stop_poll_attribute(att_name);
        remote_device->stop_poll_attribute(att_name);

        delete local_device;
        delete remote_device;

        delete cb_local;
        delete cb_remote;
    }

    static SUITE_NAME *createSuite()
    {
        return new SUITE_NAME();
    }

    static void destroySuite(SUITE_NAME *suite)
    {
        delete suite;
    }

    //
    // Tests -------------------------------------------------------
    //

    // Test subscribe_event call

    void test_Subscribe_multicast_event_locally(void)
    {
        // switch on the polling first!

        local_device->poll_attribute(att_name, 1000);
        local_eve_id = local_device->subscribe_event(att_name, Tango::CHANGE_EVENT, cb_local);

        // Check that the attribute is now polled at 1000 mS

        bool po = local_device->is_attribute_polled(att_name);
        TEST_LOG << "attribute polled : " << po << endl;
        TS_ASSERT(po);

        int poll_period = local_device->get_attribute_poll_period(att_name);
        TEST_LOG << "att polling period : " << poll_period << endl;
        TS_ASSERT_EQUALS(poll_period, 1000);
    }

    void test_Subscribe_multicast_event_remotely(void)
    {
        // For the remote device

        remote_device->poll_attribute(att_name, 1000);
        remote_eve_id = remote_device->subscribe_event(att_name, Tango::CHANGE_EVENT, cb_remote);

        // Check that the attribute is now polled at 1000 mS

        bool po = remote_device->is_attribute_polled(att_name);
        TEST_LOG << "attribute polled : " << po << endl;
        TS_ASSERT(po);

        int poll_period = remote_device->get_attribute_poll_period(att_name);
        TEST_LOG << "att polling period : " << poll_period << endl;
        TS_ASSERT_EQUALS(poll_period, 1000);
    }

    // Check that first point has been received

    void test_first_point_received_locally_and_remotely(void)
    {
        TS_ASSERT_EQUALS(cb_local->cb_executed, 1);
        TS_ASSERT_EQUALS(cb_local->val, 30);
        TS_ASSERT_EQUALS(cb_local->val_size, 4);

        TS_ASSERT_EQUALS(cb_remote->cb_executed, 1);
        TS_ASSERT_EQUALS(cb_remote->val, 30);
        TS_ASSERT_EQUALS(cb_remote->val_size, 4);
    }

    void test_Callback_executed_after_a_change_localy_and_remotely(void)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));

        local_device->command_inout("IOIncValue");
        remote_device->command_inout("IOIncValue");

        std::this_thread::sleep_for(std::chrono::seconds(2));

        TEST_LOG << "local cb excuted = " << cb_local->cb_executed << endl;
        TEST_LOG << "remote cb executed = " << cb_remote->cb_executed << endl;

        TS_ASSERT_EQUALS(cb_local->cb_executed, 2);
        TS_ASSERT_EQUALS(cb_local->val, 31);
        TS_ASSERT_EQUALS(cb_local->val_size, 4);

        TS_ASSERT_EQUALS(cb_remote->cb_executed, 2);
        TS_ASSERT_EQUALS(cb_remote->val, 31);
        TS_ASSERT_EQUALS(cb_remote->val_size, 4);
    }

    // unsubscribe to the event

    void test_unsubscribe_event_localy_and_remotely(void)
    {
        local_device->unsubscribe_event(local_eve_id);
        remote_device->unsubscribe_event(remote_eve_id);
    }

    //---------------------------------------------------------------------

    void local_init(string &dev_name, Tango::DeviceProxy *&dev_ptr)
    {
        try
        {
            dev_ptr = new DeviceProxy(dev_name);

            //
            // Test set up (stop polling and clear abs_change and rel_change attribute
            // properties but restart device to take this into account)
            // Set the abs_change to 1
            //

            if(dev_ptr->is_attribute_polled(att_name))
            {
                dev_ptr->stop_poll_attribute(att_name);
            }

            DbAttribute dba(att_name, dev_name);
            DbData dbd;
            DbDatum a(att_name);
            a << (short) 2;
            dbd.push_back(a);
            dbd.push_back(DbDatum("abs_change"));
            dbd.push_back(DbDatum("rel_change"));
            dba.delete_property(dbd);

            dbd.clear();
            a << (short) 1;
            dbd.push_back(a);
            DbDatum ch("abs_change");
            ch << (short) 1;
            dbd.push_back(ch);
            dba.put_property(dbd);

            DeviceProxy adm_dev(dev_ptr->adm_name().c_str());
            DeviceData di;
            di << dev_name;
            adm_dev.command_inout("DevRestart", di);

            delete dev_ptr;

            dev_ptr = new DeviceProxy(dev_name);
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        catch(CORBA::Exception &e)
        {
            Except::print_exception(e);
            exit(-1);
        }
    }
};

void McastSimpleTestSuite::EventCallBack::push_event(Tango::EventData *event_data)
{
    vector<DevLong> value;

    cb_executed++;

    try
    {
        TEST_LOG << "EventCallBack::push_event(): called attribute " << event_data->attr_name << " event "
                 << event_data->event << endl;
        if(!event_data->err)
        {
            *(event_data->attr_value) >> value;
            val = value[2];
            val_size = value.size();
        }
        else
        {
            if(strcmp(event_data->errors[0].reason.in(), "bbb") == 0)
            {
                cb_err++;
            }
        }
    }
    catch(...)
    {
        TEST_LOG << "EventCallBack::push_event(): could not extract data !\n";
    }
}

// NOLINTEND(*)
