/*
 * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
 * v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */
#include <assert.h>
#include <limits.h>

#include "dds/dds.h"
#include "CUnit/Theory.h"
#include "RoundTrip.h"

#include "dds/ddsrt/cdtors.h"
#include "dds/ddsrt/misc.h"
#include "dds/ddsrt/process.h"
#include "dds/ddsrt/threads.h"
#include "dds/ddsrt/time.h"

/**************************************************************************************************
 *
 * Some thread related convenience stuff.
 *
 *************************************************************************************************/

typedef enum thread_state_t {
    STARTING,
    WAITING,
    STOPPED
} thread_state_t;

typedef struct thread_arg_t {
    ddsrt_thread_t tid;
    thread_state_t state;
    dds_entity_t   expected;
} thread_arg_t;

static void         waiting_thread_start(struct thread_arg_t *arg, dds_entity_t expected);
static dds_return_t waiting_thread_expect_exit(struct thread_arg_t *arg);




/**************************************************************************************************
 *
 * Test fixtures
 *
 *************************************************************************************************/

static dds_entity_t participant = 0;
static dds_entity_t topic       = 0;
static dds_entity_t writer      = 0;
static dds_entity_t reader      = 0;
static dds_entity_t waitset     = 0;
static dds_entity_t publisher   = 0;
static dds_entity_t subscriber  = 0;
static dds_entity_t readcond    = 0;


static char*
create_topic_name(const char *prefix, char *name, size_t size)
{
    /* Get semi random g_topic name. */
    ddsrt_pid_t pid = ddsrt_getpid();
    ddsrt_tid_t tid = ddsrt_gettid();
    (void) snprintf(name, size, "%s_pid%"PRIdPID"_tid%"PRIdTID"", prefix, pid, tid);
    return name;
}


static void
ddsc_waitset_basic_init(void)
{
    participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL);
    CU_ASSERT_FATAL(participant > 0);

    waitset = dds_create_waitset(participant);
    CU_ASSERT_FATAL(waitset > 0);
}

static void
ddsc_waitset_basic_fini(void)
{
    /* It shouldn't matter if any of these entities were deleted previously.
     * dds_delete will just return an error, which we don't check. */
    dds_delete(waitset);
    dds_delete(participant);
}

static void
ddsc_waitset_init(void)
{
    uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE;
    char name[100];

    ddsrt_init();

    ddsc_waitset_basic_init();

    publisher = dds_create_publisher(participant, NULL, NULL);
    CU_ASSERT_FATAL(publisher > 0);

    subscriber = dds_create_subscriber(participant, NULL, NULL);
    CU_ASSERT_FATAL(subscriber >  0);

    topic = dds_create_topic(participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_waitset_test", name, sizeof name), NULL, NULL);
    CU_ASSERT_FATAL(topic >  0);

    reader = dds_create_reader(subscriber, topic, NULL, NULL);
    CU_ASSERT_FATAL(reader >  0);

    writer = dds_create_writer(publisher, topic, NULL, NULL);
    CU_ASSERT_FATAL(writer >  0);

    readcond = dds_create_readcondition(reader, mask);
    CU_ASSERT_FATAL(readcond >  0);
}

static void
ddsc_waitset_fini(void)
{
    /* It shouldn't matter if any of these entities were deleted previously.
     * dds_delete will just return an error, which we don't check. */
    dds_delete(readcond);
    dds_delete(writer);
    dds_delete(reader);
    dds_delete(topic);
    dds_delete(publisher);
    dds_delete(subscriber);
    ddsc_waitset_basic_fini();
    ddsrt_fini();
}

static void
ddsc_waitset_attached_init(void)
{
    dds_return_t ret;

    ddsc_waitset_init();

    /* Start with interest in nothing. */
    ret = dds_set_status_mask(participant, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_set_status_mask(writer, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_set_status_mask(reader, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_set_status_mask(topic, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_set_status_mask(publisher, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_set_status_mask(subscriber, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    /* Attach all entities to the waitset. */
    ret = dds_waitset_attach(waitset, participant, participant);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_waitset_attach(waitset, waitset, waitset);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_waitset_attach(waitset, writer, writer);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_waitset_attach(waitset, reader, reader);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_waitset_attach(waitset, topic, topic);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_waitset_attach(waitset, publisher, publisher);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_waitset_attach(waitset, subscriber, subscriber);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

}

static void
ddsc_waitset_attached_fini(void)
{
    /* Detach all entities to the waitset. */
    dds_waitset_detach(waitset, participant);
    dds_waitset_detach(waitset, topic);
    dds_waitset_detach(waitset, publisher);
    dds_waitset_detach(waitset, subscriber);
    dds_waitset_detach(waitset, waitset);
    dds_waitset_detach(waitset, writer);
    dds_waitset_detach(waitset, reader);

    ddsc_waitset_fini();
}

#if 0
#else
/**************************************************************************************************
 *
 * These will check the waitset creation in various ways.
 *
 *************************************************************************************************/
/*************************************************************************************************/
CU_Test(ddsc_waitset_create, second, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_entity_t ws;
    dds_return_t ret;

    /* Basically, ddsc_waitset_basic_init() already tested the creation of a waitset. But
     * just see if we can create a second one. */
    ws = dds_create_waitset(participant);
    CU_ASSERT_FATAL(ws > 0);

    /* Also, we should be able to delete this second one. */
    ret = dds_delete(ws);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    /* And, of course, be able to delete the first one (return code isn't checked in the test fixtures). */
    ret = dds_delete(waitset);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_create, deleted_participant, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_entity_t ws;
    dds_entity_t deleted;
    deleted = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL);
    dds_delete(deleted);
    ws = dds_create_waitset(deleted);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ws), DDS_RETCODE_ALREADY_DELETED);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_create, invalid_params) = {
        CU_DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN),
};
CU_Theory((dds_entity_t par), ddsc_waitset_create, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1;
    dds_entity_t ws;

    ws = dds_create_waitset(par);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ws), dds_err_nr(exp));
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_create, non_participants) = {
        CU_DataPoints(dds_entity_t*, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond),
};
CU_Theory((dds_entity_t *par), ddsc_waitset_create, non_participants, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini)
{
    dds_entity_t ws;
    ws = dds_create_waitset(*par);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ws), DDS_RETCODE_ILLEGAL_OPERATION);
}
/*************************************************************************************************/



/**************************************************************************************************
 *
 * These will check the waitset attach in various invalid ways.
 *   - Valid waitset but try to attach all kinds of invalid entities.
 *   - Try to attach a valid participant to all kinds of invalid entities.
 *   - Try attaching valid entities to valid (non-waitset) entities
 *   - Try attaching a valid entity a second time.
 *
 *************************************************************************************************/
/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_attach, invalid_params) = {
        CU_DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN),
        CU_DataPoints(dds_attach_t,  (dds_attach_t)NULL, (dds_attach_t)&reader, (dds_attach_t)3, (dds_attach_t)0, (dds_attach_t)0, (dds_attach_t)0, (dds_attach_t)0),
};
CU_Theory((dds_entity_t e, dds_attach_t a), ddsc_waitset_attach, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t ret;
    ret = dds_waitset_attach(waitset, e, a);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_attach, invalid_waitsets) = {
        CU_DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN),
        CU_DataPoints(dds_attach_t,  (dds_attach_t)NULL, (dds_attach_t)&reader, (dds_attach_t)3, (dds_attach_t)0, (dds_attach_t)0, (dds_attach_t)0, (dds_attach_t)0),
};
CU_Theory((dds_entity_t ws, dds_attach_t a), ddsc_waitset_attach, invalid_waitsets, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1;
    dds_return_t ret;

    ret = dds_waitset_attach(ws, participant, a);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), dds_err_nr(exp));
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_attach, non_waitsets) = {
        CU_DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader,           &publisher, &subscriber, &readcond),
        CU_DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond),
        CU_DataPoints(dds_attach_t,  (dds_attach_t)NULL, (dds_attach_t)&reader, (dds_attach_t)3, (dds_attach_t)0, (dds_attach_t)0, (dds_attach_t)0, (dds_attach_t)0),
};
CU_Theory((dds_entity_t *ws, dds_entity_t *e, dds_attach_t a), ddsc_waitset_attach, non_waitsets, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini)
{
    dds_return_t ret;
    ret = dds_waitset_attach(*ws, *e, a);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_attach, deleted_waitset, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t ret;
    dds_delete(waitset);
    ret = dds_waitset_attach(waitset, participant, 0);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED);
}
/*************************************************************************************************/



/**************************************************************************************************
 *
 * These will check the waitset attach and detach with valid entities (which should always work).
 *
 *************************************************************************************************/
/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_attach_detach, valid_entities) = {
        CU_DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond),
        CU_DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond),
        CU_DataPoints(dds_attach_t,  (dds_attach_t)NULL, (dds_attach_t)&reader, (dds_attach_t)3, (dds_attach_t)3, (dds_attach_t)3, (dds_attach_t)3, (dds_attach_t)3, (dds_attach_t)3),
};
CU_Theory((dds_entity_t *ws, dds_entity_t *e, dds_attach_t a), ddsc_waitset_attach_detach, valid_entities, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini)
{
    dds_return_t exp;
    dds_return_t ret;

    if (*ws == waitset) {
        /* Attaching to the waitset should work. */
        exp = DDS_RETCODE_OK;
    } else {
        /* Attaching to every other entity should fail. */
        exp = DDS_RETCODE_ILLEGAL_OPERATION;
    }

    /* Try to attach. */
    ret = dds_waitset_attach(*ws, *e, a);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), exp);

    /* Detach when needed. */
    if (ret == DDS_RETCODE_OK) {
        ret = dds_waitset_detach(*ws, *e);
        CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    }
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_attach_detach, second, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t ret;

    ret = dds_waitset_attach(waitset, waitset, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    ret = dds_waitset_attach(waitset, waitset, 0);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET);

    ret = dds_waitset_detach(waitset, waitset);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    ret = dds_waitset_detach(waitset, waitset);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET);
}
/*************************************************************************************************/



/**************************************************************************************************
 *
 * These will check the waitset detach in various ways.
 *   - Valid waitset but try to detach all kinds of invalid entities.
 *   - Try to detach a valid participant from all kinds of invalid entities.
 *   - Try detach valid entities from valid entities
 *          -- detach to entities that are not waitsets.
 *          -- detach to a waitset.
 *
 * Note: everything is expected to fail for varous reasons. The 'happy day' detach is tested on
 *       the fly in ddsc_waitset_attach_detach::valid_entities
 *
 *************************************************************************************************/
/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_detach, invalid_params) = {
        CU_DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN),
};
CU_Theory((dds_entity_t e), ddsc_waitset_detach, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t ret;
    ret = dds_waitset_detach(waitset, e);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_detach, invalid_waitsets) = {
        CU_DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN),
};
CU_Theory((dds_entity_t ws), ddsc_waitset_detach, invalid_waitsets, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1;
    dds_return_t ret;

    ret = dds_waitset_detach(ws, participant);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), dds_err_nr(exp));
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_detach, valid_entities) = {
        CU_DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond),
        CU_DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond),
};
CU_Theory((dds_entity_t *ws, dds_entity_t *e), ddsc_waitset_detach, valid_entities, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini)
{
    dds_return_t exp;
    dds_return_t ret;

    if (*ws == waitset) {
        /* Detaching from an empty waitset should yield 'precondition not met'. */
        exp = DDS_RETCODE_PRECONDITION_NOT_MET;
    } else {
        /* Attaching to every other entity should yield 'illegal operation'. */
        exp = DDS_RETCODE_ILLEGAL_OPERATION;
    }

    ret = dds_waitset_detach(*ws, *e);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), exp);
}
/*************************************************************************************************/




/**************************************************************************************************
 *
 * These will check the waitset attach/detach in various ways. We will use NULL as attach argument
 * because it will be properly used in 'waitset behaviour' tests anyway.
 *
 *************************************************************************************************/
/*************************************************************************************************/
CU_Test(ddsc_waitset_attach_detach, itself, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t ret;

    ret = dds_waitset_attach(waitset, waitset, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    ret = dds_waitset_detach(waitset, waitset);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_attach_detach, participant, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t ret;

    ret = dds_waitset_attach(waitset, participant, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    ret = dds_waitset_detach(waitset, participant);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_attach_detach, reader, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini)
{
    dds_return_t ret;

    ret = dds_waitset_attach(waitset, reader, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    ret = dds_waitset_detach(waitset, reader);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_attach_detach, readcondition, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini)
{
    dds_return_t ret;

    ret = dds_waitset_attach(waitset, readcond, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    ret = dds_waitset_detach(waitset, readcond);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
}
/*************************************************************************************************/




/**************************************************************************************************
 *
 * These will check entities can be deleted while attached to the waitset. We will use NULL as
 * attach argument because it will be properly used in 'waitset behaviour' tests anyway.
 * 1) Test if the waitset can be deleted when attached to itself.
 * 2) Test if the waitset parent can be deleted when attached.
 * 3) Test if an 'ordinary' entity can be deleted when attached.
 * 4) Test if an condition can be deleted when attached.
 *
 *************************************************************************************************/
/*************************************************************************************************/
CU_Test(ddsc_waitset_delete_attached, self, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t ret;

    ret = dds_waitset_attach(waitset, waitset, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    ret = dds_delete(waitset);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_delete_attached, participant, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t ret;

    ret = dds_waitset_attach(waitset, participant, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    ret = dds_delete(participant);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_delete_attached, reader, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini)
{
    dds_return_t ret;

    ret = dds_waitset_attach(waitset, reader, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    ret = dds_delete(reader);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_delete_attached, readcondition, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini)
{
    dds_return_t ret;

    ret = dds_waitset_attach(waitset, readcond, 0);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    ret = dds_delete(readcond);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
}
/*************************************************************************************************/



/**************************************************************************************************
 *
 * These will check the waitset triggering in various invalid ways.
 * The happy days scenario is tested by ddsc_waitset_triggering::on_self.
 *
 *************************************************************************************************/
/*************************************************************************************************/
CU_Test(ddsc_waitset_set_trigger, deleted_waitset, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t ret;
    dds_delete(waitset);
    ret = dds_waitset_set_trigger(waitset, true);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_set_trigger, invalid_params) = {
        CU_DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN),
};
CU_Theory((dds_entity_t ws), ddsc_waitset_set_trigger, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1;
    dds_return_t ret;

    ret = dds_waitset_set_trigger(ws, true);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), dds_err_nr(exp));
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_set_trigger, non_waitsets) = {
        CU_DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &publisher, &subscriber, &readcond),
};
CU_Theory((dds_entity_t *ws), ddsc_waitset_set_trigger, non_waitsets, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini)
{
    dds_return_t ret;
    ret = dds_waitset_set_trigger(*ws, true);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION);
}
/*************************************************************************************************/



/**************************************************************************************************
 *
 * These will check the waitset wait in various invalid ways.
 * The happy days scenario is tested by ddsc_waitset_triggering::*.
 *
 *************************************************************************************************/
/*************************************************************************************************/
CU_Test(ddsc_waitset_wait, deleted_waitset, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini)
{
    dds_attach_t triggered;
    dds_return_t ret;
    dds_delete(waitset);
    ret = dds_waitset_wait(waitset, &triggered, 1, DDS_SECS(1));
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_wait, invalid_waitsets) = {
        CU_DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN),
};
CU_Theory((dds_entity_t ws), ddsc_waitset_wait, invalid_waitsets, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_attach_t triggered;
    dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1;
    dds_return_t ret;

    ret = dds_waitset_wait(ws, &triggered, 1, DDS_SECS(1));
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), dds_err_nr(exp));
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_wait, non_waitsets) = {
        CU_DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &publisher, &subscriber, &readcond),
};
CU_Theory((dds_entity_t *ws), ddsc_waitset_wait, non_waitsets, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini)
{
    dds_attach_t triggered;
    dds_return_t ret;
    ret = dds_waitset_wait(*ws, &triggered, 1, DDS_SECS(1));
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION);
}
/*************************************************************************************************/

/*************************************************************************************************/
static dds_attach_t attachment;
CU_TheoryDataPoints(ddsc_waitset_wait, invalid_params) = {
        CU_DataPoints(dds_attach_t *, &attachment, NULL),
        CU_DataPoints(size_t, 0, 1, 100),
        CU_DataPoints(int, -1, 0, 1),
};
CU_Theory((dds_attach_t *a, size_t size, int msec), ddsc_waitset_wait, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t ret;

    /* Only test when the combination of parameters is actually invalid. */
    CU_ASSERT_FATAL(((a == NULL) && (size != 0)) || ((a != NULL) && (size == 0)) || (msec < 0));

    ret = dds_waitset_wait(waitset, a, size, DDS_MSECS(msec));
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_wait_until, deleted_waitset, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini)
{
    dds_attach_t triggered;
    dds_return_t ret;
    dds_delete(waitset);
    ret = dds_waitset_wait_until(waitset, &triggered, 1, dds_time());
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_wait_until, invalid_waitsets) = {
        CU_DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN),
};
CU_Theory((dds_entity_t ws), ddsc_waitset_wait_until, invalid_waitsets, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_attach_t triggered;
    dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1;
    dds_return_t ret;

    ret = dds_waitset_wait_until(ws, &triggered, 1, dds_time());
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), dds_err_nr(exp));
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_wait_until, non_waitsets) = {
        CU_DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &publisher, &subscriber, &readcond),
};
CU_Theory((dds_entity_t *ws), ddsc_waitset_wait_until, non_waitsets, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini)
{
    dds_attach_t triggered;
    dds_return_t ret;
    ret = dds_waitset_wait_until(*ws, &triggered, 1, dds_time());
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION);
}
/*************************************************************************************************/

/*************************************************************************************************/
static dds_attach_t attachment;
CU_TheoryDataPoints(ddsc_waitset_wait_until, invalid_params) = {
        CU_DataPoints(dds_attach_t *, &attachment, NULL),
        CU_DataPoints(size_t, 0, 1, 100)
};
CU_Theory((dds_attach_t *a, size_t size), ddsc_waitset_wait_until, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini)
{
    dds_return_t ret;

    /* Only test when the combination of parameters is actually invalid. */
    CU_ASSERT_FATAL(((a == NULL) && (size != 0)) || ((a != NULL) && (size == 0)));

    ret = dds_waitset_wait_until(waitset, a, size, dds_time());
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_wait_until, past, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini)
{
    dds_attach_t triggered;
    dds_return_t ret;

    ret = dds_waitset_wait_until(waitset, &triggered, 1, dds_time() - 100000);
    CU_ASSERT_EQUAL_FATAL(ret, 0);
}
/*************************************************************************************************/



/**************************************************************************************************
 *
 * These will check the waitset getting the attached entities.
 *
 *************************************************************************************************/
#define MAX_ENTITIES_CNT    (10)
#define FOUND_PARTICIPANT   (0x0001)
#define FOUND_PUBLISHER     (0x0002)
#define FOUND_SUBSCRIBER    (0x0004)
#define FOUND_WAITSET       (0x0008)
#define FOUND_TOPIC         (0x0010)
#define FOUND_READER        (0x0020)
#define FOUND_WRITER        (0x0040)
#define FOUND_ALL           (0x007F)

static uint32_t NumberOfSetBits(uint32_t i)
{
    /* https://stackoverflow.com/questions/109023/how-to-count-the-number-of-set-bits-in-a-32-bit-integer */
    // Java: use >>> instead of >>
    // C or C++: use uint32_t
    i = i - ((i >> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
    return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_get_entities, array_sizes) = {
        CU_DataPoints(size_t, 0, 1, 7, MAX_ENTITIES_CNT),
};
CU_Theory((size_t size), ddsc_waitset_get_entities, array_sizes, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini)
{
    uint32_t found = 0;
    dds_return_t i;
    dds_return_t ret;
    dds_entity_t entities[MAX_ENTITIES_CNT];

    /* Make sure at least one entity is in the waitsets' internal triggered list. */
    ret = dds_waitset_set_trigger(waitset, true);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    /* Get the actual attached entities. */
    ret = dds_waitset_get_entities(waitset, entities, size);

    /* ddsc_waitset_attached_init() attached 7 entities. */
    CU_ASSERT_EQUAL_FATAL(ret, 7);

    /* Found entities should be only present once. */
    ret = ((dds_return_t)size < ret) ? (dds_return_t)size : ret;
    for (i = 0; i < ret; i++) {
        if (entities[i] == participant) {
            CU_ASSERT_FATAL(!(found & FOUND_PARTICIPANT));
            found |= FOUND_PARTICIPANT;
        } else if (entities[i] == publisher) {
            CU_ASSERT_FATAL(!(found & FOUND_PUBLISHER));
            found |= FOUND_PUBLISHER;
        } else if (entities[i] == subscriber) {
            CU_ASSERT_FATAL(!(found & FOUND_SUBSCRIBER));
            found |= FOUND_SUBSCRIBER;
        } else if (entities[i] == waitset) {
            CU_ASSERT_FATAL(!(found & FOUND_WAITSET));
            found |= FOUND_WAITSET;
        } else if (entities[i] == topic) {
            CU_ASSERT_FATAL(!(found & FOUND_TOPIC));
            found |= FOUND_TOPIC;
        } else if (entities[i] == reader) {
            CU_ASSERT_FATAL(!(found & FOUND_READER));
            found |= FOUND_READER;
        } else if (entities[i] == writer) {
            CU_ASSERT_FATAL(!(found & FOUND_WRITER));
            found |= FOUND_WRITER;
        }
    }

    /* Every found entity should be a known one. */
    CU_ASSERT_EQUAL_FATAL((dds_return_t)NumberOfSetBits(found), ret);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_get_entities, no_array, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini)
{
    dds_return_t ret;
    DDSRT_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */
    ret = dds_waitset_get_entities(waitset, NULL, 1);
    DDSRT_WARNING_MSVC_ON(6387);
    /* ddsc_waitset_attached_init attached 7 entities. */
    CU_ASSERT_EQUAL_FATAL(ret, 7);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_Test(ddsc_waitset_get_entities, deleted_waitset, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini)
{
    dds_return_t ret;
    dds_entity_t entities[MAX_ENTITIES_CNT];
    dds_delete(waitset);
    ret = dds_waitset_get_entities(waitset, entities, MAX_ENTITIES_CNT);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED);
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_get_entities, invalid_params) = {
        CU_DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN),
};
CU_Theory((dds_entity_t ws), ddsc_waitset_get_entities, invalid_params, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini)
{
    dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1;
    dds_entity_t entities[MAX_ENTITIES_CNT];
    dds_return_t ret;

    ret = dds_waitset_get_entities(ws, entities, MAX_ENTITIES_CNT);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), dds_err_nr(exp));
}
/*************************************************************************************************/

/*************************************************************************************************/
CU_TheoryDataPoints(ddsc_waitset_get_entities, non_waitsets) = {
        CU_DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &publisher, &subscriber, &readcond),
};
CU_Theory((dds_entity_t *ws), ddsc_waitset_get_entities, non_waitsets, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini)
{
    dds_entity_t entities[MAX_ENTITIES_CNT];
    dds_return_t ret;
    ret = dds_waitset_get_entities(*ws, entities, MAX_ENTITIES_CNT);
    CU_ASSERT_EQUAL_FATAL(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION);
}
/*************************************************************************************************/





/**************************************************************************************************
 *
 * This will check if waitset will wake up when it is attached to itself and triggered.
 *
 * In short:
 * 1) Attach the waitset to itself
 * 2) A different thread will call dds_waitset_wait. This should block because the waitset
 *    hasn't been triggered yet. We also want it to block to know for sure that it'll wake up.
 * 3) Trigger the waitset. This should unblock the other thread that was waiting on the waitset.
 * 4) A new dds_waitset_wait should return immediately because the trigger value hasn't been
 *    reset (dds_waitset_set_trigger(waitset, false)) after the waitset woke up.
 *
 *************************************************************************************************/
/*************************************************************************************************/
CU_Test(ddsc_waitset_triggering, on_self, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini)
{
    dds_attach_t triggered;
    thread_arg_t arg;
    dds_return_t ret;

    /* The waitset should not have been triggered. */
    ret = dds_triggered(waitset);
    CU_ASSERT_EQUAL_FATAL(ret, 0);

    /* Start a thread that'll wait because no entity attached to the waitset
     * has been triggered (for instance by a status change). */
    waiting_thread_start(&arg, waitset);

    /* Triggering of the waitset should unblock the thread. */
    ret = dds_waitset_set_trigger(waitset, true);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = waiting_thread_expect_exit(&arg);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    /* Now the trigger state should be true. */
    ret = dds_triggered(waitset);
    CU_ASSERT_FATAL(ret > 0);

    /* Waitset shouldn't wait, but immediately return our waitset. */
    ret = dds_waitset_wait(waitset, &triggered, 1, DDS_SECS(1));
    CU_ASSERT_EQUAL_FATAL(ret, 1);
    CU_ASSERT_EQUAL_FATAL(waitset, (dds_entity_t)(intptr_t)triggered);

    /* Reset waitset trigger. */
    ret = dds_waitset_set_trigger(waitset, false);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = dds_triggered(waitset);
    CU_ASSERT_EQUAL_FATAL(ret, 0);
}
/*************************************************************************************************/




/**************************************************************************************************
 *
 * This will check if waitset will wake up when data is written related to an attached reader.
 *
 * In short:
 * 1) Attach the reader to the waitset
 * 2) A different thread will call dds_waitset_wait. This should block because the reader
 *    hasn't got data yet. We also want it to block to know for sure that it'll wake up.
 * 3) Write data. This should unblock the other thread that was waiting on the waitset.
 * 4) A new dds_waitset_wait should return immediately because the status of the reader hasn't
 *    changed after the first trigger (it didn't read the data).
 *
 *************************************************************************************************/
/*************************************************************************************************/
CU_Test(ddsc_waitset_triggering, on_reader, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini)
{
    RoundTripModule_DataType sample;
    dds_attach_t triggered;
    thread_arg_t arg;
    dds_return_t ret;

    memset(&sample, 0, sizeof(RoundTripModule_DataType));

    /* Only interested in data_available for this test. */
    ret = dds_set_status_mask(reader, DDS_DATA_AVAILABLE_STATUS);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    /* The reader should not have been triggered. */
    ret = dds_triggered(reader);
    CU_ASSERT_EQUAL_FATAL(ret, 0);

    /* Start a thread that'll wait because no entity attached to the waitset
     * has been triggered (for instance by a status change). */
    waiting_thread_start(&arg, reader);

    /* Writing data should unblock the thread. */
    ret = dds_write(writer, &sample);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = waiting_thread_expect_exit(&arg);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    /* Now the trigger state should be true. */
    ret = dds_triggered(reader);
    CU_ASSERT_FATAL(ret >  0);

    /* Waitset shouldn't wait, but immediately return our reader. */
    ret = dds_waitset_wait(waitset, &triggered, 1, DDS_SECS(1));
    CU_ASSERT_EQUAL_FATAL(ret, 1);
    CU_ASSERT_EQUAL_FATAL(reader, (dds_entity_t)(intptr_t)triggered);
}
/*************************************************************************************************/




/**************************************************************************************************
 *
 * This will check if waitset will wake up when data is written related to an attached condition.
 *
 * In short:
 * 1) Attach the readcondition to the waitset
 * 2) A different thread will call dds_waitset_wait. This should block because the related reader
 *    hasn't got data yet. We also want it to block to know for sure that it'll wake up.
 * 3) Write data. This should unblock the other thread that was waiting on the readcondition.
 * 4) A new dds_waitset_wait should return immediately because the status of the related reader
 *    (and thus the readcondition) hasn't changed after the first trigger (it didn't read the data).
 *
 *************************************************************************************************/
/*************************************************************************************************/
CU_Test(ddsc_waitset_triggering, on_readcondition, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini)
{
    RoundTripModule_DataType sample;
    dds_attach_t triggered;
    thread_arg_t arg;
    dds_return_t ret;

    memset(&sample, 0, sizeof(RoundTripModule_DataType));

    /* Make sure that we start un-triggered. */
    ret = dds_triggered(readcond);
    CU_ASSERT_EQUAL_FATAL(ret, 0);

    /* Attach condition to the waitset. */
    ret = dds_waitset_attach(waitset, readcond, readcond);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    /* Start a thread that'll wait because no entity attached to the waitset
     * has been triggered (for instance by a status change). */
    waiting_thread_start(&arg, readcond);

    /* Writing data should unblock the thread. */
    ret = dds_write(writer, &sample);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
    ret = waiting_thread_expect_exit(&arg);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);

    /* Now the trigger state should be true. */
    ret = dds_triggered(readcond);
    CU_ASSERT(ret > 0);

    /* Waitset shouldn't wait, but immediately return our reader. */
    ret = dds_waitset_wait(waitset, &triggered, 1, DDS_SECS(1));
    CU_ASSERT_EQUAL_FATAL(ret, 1);
    CU_ASSERT_EQUAL_FATAL(readcond, (dds_entity_t)(intptr_t)triggered);

    /* Detach condition. */
    ret = dds_waitset_detach(waitset, readcond);
    CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK);
}
/*************************************************************************************************/





#endif

/**************************************************************************************************
 *
 * Convenience support functions.
 *
 *************************************************************************************************/

static uint32_t
waiting_thread(void *a)
{
    thread_arg_t *arg = (thread_arg_t*)a;
    dds_attach_t triggered;
    dds_return_t ret;

    arg->state = WAITING;
    /* This should block until the main test released all claims. */
    ret = dds_waitset_wait(waitset, &triggered, 1, DDS_SECS(1000));
    CU_ASSERT_EQUAL_FATAL(ret, 1);
    CU_ASSERT_EQUAL_FATAL(arg->expected, (dds_entity_t)(intptr_t)triggered);
    arg->state = STOPPED;

    return 0;
}

static dds_retcode_t
thread_reached_state(thread_state_t *actual, thread_state_t expected, int32_t msec)
{
    /* Convenience function. */
    dds_time_t msec10 = DDS_MSECS(10);
    while ((msec > 0) && (*actual != expected)) {
        dds_sleepfor(msec10);
        msec -= 10;
    }
    return (*actual == expected) ? DDS_RETCODE_OK : DDS_RETCODE_TIMEOUT;
}

static void
waiting_thread_start(struct thread_arg_t *arg, dds_entity_t expected)
{
    ddsrt_thread_t thread_id;
    ddsrt_threadattr_t thread_attr;
    dds_retcode_t rc;

    assert(arg);

    /* Create an other thread that will blocking wait on the waitset. */
    arg->expected = expected;
    arg->state   = STARTING;
    ddsrt_threadattr_init(&thread_attr);
    rc = ddsrt_thread_create(&thread_id, "waiting_thread", &thread_attr, waiting_thread, arg);
    CU_ASSERT_EQUAL_FATAL(rc, DDS_RETCODE_OK);

    /* The thread should reach 'waiting' state. */
    rc = thread_reached_state(&(arg->state), WAITING, 1000);
    CU_ASSERT_EQUAL_FATAL(rc, DDS_RETCODE_OK);

    /* But thread should block and thus NOT reach 'stopped' state. */
    rc = thread_reached_state(&(arg->state), STOPPED, 100);
    CU_ASSERT_EQUAL_FATAL(rc, DDS_RETCODE_TIMEOUT);

    arg->tid = thread_id;
}

static dds_return_t
waiting_thread_expect_exit(struct thread_arg_t *arg)
{
    dds_retcode_t rc;
    assert(arg);
    rc = thread_reached_state(&(arg->state), STOPPED, 5000);
    if (rc == DDS_RETCODE_OK) {
        ddsrt_thread_join(arg->tid, NULL);
        return DDS_RETCODE_OK;
    }
    return DDS_RETCODE_TIMEOUT;
}

