#include <pthread.h>
#include <stdlib.h>
#include <time.h>

#include "../toxcore/tox.h"

#include "check_compat.h"
#include "../testing/misc_tools.h"

#define TCP_RELAY_PORT 33448
/* The Travis-CI container responds poorly to ::1 as a localhost address
 * You're encouraged to -D FORCE_TESTS_IPV6 on a local test  */
#ifdef FORCE_TESTS_IPV6
#define TOX_LOCALHOST "::1"
#else
#define TOX_LOCALHOST "127.0.0.1"
#endif

typedef struct Loop_Test {
    volatile int start_count;
    volatile int stop_count;
    pthread_mutex_t mutex;
    Tox *tox;
} Loop_Test;

static void tox_loop_cb_start(Tox *tox, void *data)
{
    Loop_Test *userdata = (Loop_Test *)data;
    pthread_mutex_lock(&userdata->mutex);
    fprintf(stderr, "br1:     %p (%d)\n", (volatile void *)&userdata->start_count, userdata->start_count);
    fputs("br2\n", stderr);
    ++userdata->start_count;
}

static void tox_loop_cb_stop(Tox *tox, void *data)
{
    Loop_Test *userdata = (Loop_Test *)data;
    ++userdata->stop_count;
    pthread_mutex_unlock(&userdata->mutex);
}

static void *tox_loop_worker(void *data)
{
    Loop_Test *userdata = (Loop_Test *)data;
    Tox_Err_Loop err;
    tox_loop(userdata->tox, userdata, &err);
    ck_assert_msg(err == TOX_ERR_LOOP_OK, "tox_loop error: %d", err);
    return nullptr;
}

static void test_tox_loop(void)
{
    pthread_t worker, worker_tcp;
    struct Tox_Options *opts = tox_options_new(nullptr);
    Loop_Test userdata;
    uint8_t dpk[TOX_PUBLIC_KEY_SIZE];
    int retval;

    userdata.start_count = 0;
    userdata.stop_count = 0;
    pthread_mutex_init(&userdata.mutex, nullptr);

    tox_options_set_tcp_port(opts, TCP_RELAY_PORT);
    userdata.tox = tox_new(opts, nullptr);
    tox_callback_loop_begin(userdata.tox, tox_loop_cb_start);
    tox_callback_loop_end(userdata.tox, tox_loop_cb_stop);
    pthread_create(&worker, nullptr, tox_loop_worker, &userdata);

    fprintf(stderr, "br0: udp %p\n", (volatile void *)&userdata.start_count);
    tox_self_get_dht_id(userdata.tox, dpk);

    tox_options_default(opts);
    Loop_Test userdata_tcp;
    userdata_tcp.start_count = 0;
    userdata_tcp.stop_count = 0;
    pthread_mutex_init(&userdata_tcp.mutex, nullptr);
    userdata_tcp.tox = tox_new(opts, nullptr);
    tox_callback_loop_begin(userdata_tcp.tox, tox_loop_cb_start);
    tox_callback_loop_end(userdata_tcp.tox, tox_loop_cb_stop);
    pthread_create(&worker_tcp, nullptr, tox_loop_worker, &userdata_tcp);

    pthread_mutex_lock(&userdata_tcp.mutex);
    TOX_ERR_BOOTSTRAP error;
    ck_assert_msg(tox_add_tcp_relay(userdata_tcp.tox, TOX_LOCALHOST, TCP_RELAY_PORT, dpk, &error), "Add relay error, %i",
                  error);
    ck_assert_msg(tox_bootstrap(userdata_tcp.tox, TOX_LOCALHOST, 33445, dpk, &error), "Bootstrap error, %i", error);
    pthread_mutex_unlock(&userdata_tcp.mutex);

    c_sleep(1000);

    tox_loop_stop(userdata.tox);
    pthread_join(worker, (void **)(void *)&retval);
    ck_assert_msg(retval == 0, "tox_loop didn't return 0");

    tox_kill(userdata.tox);
    fprintf(stderr, "br3: udp %p (%d)\n", (volatile void *)&userdata.start_count, userdata.start_count);
    ck_assert_msg(userdata.start_count == userdata.stop_count, "start and stop must match (start = %d, stop = %d)",
                  userdata.start_count, userdata.stop_count);

    tox_loop_stop(userdata_tcp.tox);
    pthread_join(worker_tcp, (void **)(void *)&retval);
    ck_assert_msg(retval == 0, "tox_loop didn't return 0");

    tox_kill(userdata_tcp.tox);
    fprintf(stderr, "br4: tcp %p (%d)\n", (volatile void *)&userdata_tcp.start_count, userdata_tcp.start_count);
    ck_assert_msg(userdata_tcp.start_count == userdata_tcp.stop_count, "start and stop must match (start = %d, stop = %d)",
                  userdata_tcp.start_count, userdata_tcp.stop_count);
}

int main(int argc, char *argv[])
{
    test_tox_loop();
    return 0;
}