/*
 * Copyright (c) 2013 Niels Provos and Nick Mathewson
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "event2/event-config.h"
#include "evconfig-private.h"
#include "tinytest.h"
#include "tinytest_macros.h"
#include <stdlib.h>

#include "event2/event.h"
#include "event2/util.h"
#include "event-internal.h"
#include "defer-internal.h"

#include "regress.h"
#include "regress_thread.h"

static void
timer_callback(evutil_socket_t fd, short what, void *arg)
{
	int *int_arg = arg;
	*int_arg += 1;
	(void)fd;
	(void)what;
}
static void
simple_callback(struct event_callback *evcb, void *arg)
{
	int *int_arg = arg;
        *int_arg += 1;
	(void)evcb;
}
static void
event_finalize_callback_1(struct event *ev, void *arg)
{
	int *int_arg = arg;
        *int_arg += 100;
	(void)ev;
}
static void
callback_finalize_callback_1(struct event_callback *evcb, void *arg)
{
	int *int_arg = arg;
        *int_arg += 100;
	(void)evcb;
}


static void
test_fin_cb_invoked(void *arg)
{
	struct basic_test_data *data = arg;
	struct event_base *base = data->base;

	struct event *ev;
	struct event ev2;
	struct event_callback evcb;
	int cb_called = 0;
	int ev_called = 0;

	const struct timeval ten_sec = {10,0};

	event_deferred_cb_init_(&evcb, 0, simple_callback, &cb_called);
	ev = evtimer_new(base, timer_callback, &ev_called);
	/* Just finalize them; don't bother adding. */
	event_free_finalize(0, ev, event_finalize_callback_1);
	event_callback_finalize_(base, 0, &evcb, callback_finalize_callback_1);

	event_base_dispatch(base);

	tt_int_op(cb_called, ==, 100);
	tt_int_op(ev_called, ==, 100);

	ev_called = cb_called = 0;
	event_base_assert_ok_(base);

	/* Now try it when they're active. (actually, don't finalize: make
	 * sure activation can happen! */
	ev = evtimer_new(base, timer_callback, &ev_called);
	event_deferred_cb_init_(&evcb, 0, simple_callback, &cb_called);

	event_active(ev, EV_TIMEOUT, 1);
	event_callback_activate_(base, &evcb);

	event_base_dispatch(base);
	tt_int_op(cb_called, ==, 1);
	tt_int_op(ev_called, ==, 1);

	ev_called = cb_called = 0;
	event_base_assert_ok_(base);

	/* Great, it worked. Now activate and finalize and make sure only
	 * finalizing happens. */
	event_active(ev, EV_TIMEOUT, 1);
	event_callback_activate_(base, &evcb);
	event_free_finalize(0, ev, event_finalize_callback_1);
	event_callback_finalize_(base, 0, &evcb, callback_finalize_callback_1);

	event_base_dispatch(base);
	tt_int_op(cb_called, ==, 100);
	tt_int_op(ev_called, ==, 100);

	ev_called = 0;

	event_base_assert_ok_(base);

	/* Okay, now add but don't have it become active, and make sure *that*
	 * works. */
	ev = evtimer_new(base, timer_callback, &ev_called);
	event_add(ev, &ten_sec);
	event_free_finalize(0, ev, event_finalize_callback_1);

	event_base_dispatch(base);
	tt_int_op(ev_called, ==, 100);

	ev_called = 0;
	event_base_assert_ok_(base);

	/* Now try adding and deleting after finalizing. */
	ev = evtimer_new(base, timer_callback, &ev_called);
	evtimer_assign(&ev2, base, timer_callback, &ev_called);
	event_add(ev, &ten_sec);
	event_free_finalize(0, ev, event_finalize_callback_1);
	event_finalize(0, &ev2, event_finalize_callback_1);

	event_add(&ev2, &ten_sec);
	event_del(ev);
	event_active(&ev2, EV_TIMEOUT, 1);

	event_base_dispatch(base);
	tt_int_op(ev_called, ==, 200);

	event_base_assert_ok_(base);

end:
	;
}

#ifndef EVENT__DISABLE_MM_REPLACEMENT
static void *
tfff_malloc(size_t n)
{
	return malloc(n);
}
static void *tfff_p1=NULL, *tfff_p2=NULL;
static int tfff_p1_freed=0, tfff_p2_freed=0;
static void
tfff_free(void *p)
{
	if (! p)
		return;
	if (p == tfff_p1)
		++tfff_p1_freed;
	if (p == tfff_p2)
		++tfff_p2_freed;
	free(p);
}
static void *
tfff_realloc(void *p, size_t sz)
{
	return realloc(p,sz);
}
#endif

static void
test_fin_free_finalize(void *arg)
{
#ifdef EVENT__DISABLE_MM_REPLACEMENT
	tinytest_set_test_skipped_();
#else
	struct event_base *base = NULL;
	struct event *ev, *ev2;
	int ev_called = 0;
	int ev2_called = 0;

	(void)arg;

	event_set_mem_functions(tfff_malloc, tfff_realloc, tfff_free);

	base = event_base_new();
	tt_assert(base);

	ev = evtimer_new(base, timer_callback, &ev_called);
	ev2 = evtimer_new(base, timer_callback, &ev2_called);
	tfff_p1 = ev;
	tfff_p2 = ev2;
	event_free_finalize(0, ev, event_finalize_callback_1);
	event_finalize(0, ev2, event_finalize_callback_1);

	event_base_dispatch(base);

	tt_int_op(ev_called, ==, 100);
	tt_int_op(ev2_called, ==, 100);

	event_base_assert_ok_(base);
	tt_int_op(tfff_p1_freed, ==, 1);
	tt_int_op(tfff_p2_freed, ==, 0);

	event_free(ev2);

end:
	if (base)
		event_base_free(base);
#endif
}

/* For test_fin_within_cb */
struct event_and_count {
	struct event *ev;
	struct event *ev2;
	int count;
};
static void
event_finalize_callback_2(struct event *ev, void *arg)
{
	struct event_and_count *evc = arg;
	evc->count += 100;
	event_free(ev);
}
static void
timer_callback_2(evutil_socket_t fd, short what, void *arg)
{
	struct event_and_count *evc = arg;
	event_finalize(0, evc->ev, event_finalize_callback_2);
	event_finalize(0, evc->ev2, event_finalize_callback_2);
	++ evc->count;
	(void)fd;
	(void)what;
}

static void
test_fin_within_cb(void *arg)
{
	struct basic_test_data *data = arg;
	struct event_base *base = data->base;

	struct event_and_count evc1, evc2;
	evc1.count = evc2.count = 0;
	evc2.ev2 = evc1.ev = evtimer_new(base, timer_callback_2, &evc1);
	evc1.ev2 = evc2.ev = evtimer_new(base, timer_callback_2, &evc2);

	/* Activate both.  The first one will have its callback run, which
	 * will finalize both of them, preventing the second one's callback
	 * from running. */
	event_active(evc1.ev, EV_TIMEOUT, 1);
	event_active(evc2.ev, EV_TIMEOUT, 1);

	event_base_dispatch(base);
	tt_int_op(evc1.count, ==, 101);
	tt_int_op(evc2.count, ==, 100);

	event_base_assert_ok_(base);
	/* Now try with EV_PERSIST events. */
	evc1.count = evc2.count = 0;
	evc2.ev2 = evc1.ev = event_new(base, -1, EV_PERSIST, timer_callback_2, &evc1);
	evc1.ev2 = evc2.ev = event_new(base, -1, EV_PERSIST, timer_callback_2, &evc2);

	event_active(evc1.ev, EV_TIMEOUT, 1);
	event_active(evc2.ev, EV_TIMEOUT, 1);

	event_base_dispatch(base);
	tt_int_op(evc1.count, ==, 101);
	tt_int_op(evc2.count, ==, 100);

	event_base_assert_ok_(base);
end:
	;
}

static void
event_finalize_callback_free(struct event *ev, void *arg)
{
	struct event_base *base = arg;
	int err;
	if (base) {
		err = event_assign(ev, base, -1, EV_TIMEOUT, NULL, NULL);
		tt_int_op(err, ==, 0);
		test_ok += 1;
	} else {
		free(ev);
		test_ok += 1;
	}

end:
	;
}
static void
test_fin_debug_use_after_free(void *arg)
{
	struct basic_test_data *data = arg;
	struct event_base *base = data->base;
	struct event *ev;

	tt_ptr_op(ev = event_new(base, -1, EV_TIMEOUT, NULL, base), !=, NULL);
	tt_int_op(event_add(ev, NULL), ==, 0);
	tt_int_op(event_finalize(0, ev, event_finalize_callback_free), ==, 0);

	// Dispatch base to trigger callbacks
	event_base_dispatch(base);
	event_base_assert_ok_(base);
	tt_int_op(test_ok, ==, 1);

	// Now add again, since we did event_assign in event_finalize_callback_free
	// This used to fail in event_debug_assert_is_setup_
	tt_int_op(event_add(ev, NULL), ==, 0);

	// Finalize and dispatch again
	tt_int_op(event_finalize(0, ev, event_finalize_callback_free), ==, 0);
	event_base_dispatch(base);
	event_base_assert_ok_(base);
	tt_int_op(test_ok, ==, 2);

end:
	;
}

#if 0
static void
timer_callback_3(evutil_socket_t *fd, short what, void *arg)
{
	(void)fd;
	(void)what;

}
static void
test_fin_many(void *arg)
{
	struct basic_test_data *data = arg;
	struct event_base *base = data->base;

	struct event *ev1, *ev2;
	struct event_callback evcb1, evcb2;
	int ev1_count = 0, ev2_count = 0;
	int evcb1_count = 0, evcb2_count = 0;
	struct event_callback *array[4];

	int n;

	/* First attempt: call finalize_many with no events running */
	ev1 = evtimer_new(base, timer_callback, &ev1_count);
	ev1 = evtimer_new(base, timer_callback, &ev2_count);
	event_deferred_cb_init_(&evcb1, 0, simple_callback, &evcb1_called);
	event_deferred_cb_init_(&evcb2, 0, simple_callback, &evcb2_called);
	array[0] = &ev1->ev_evcallback;
	array[1] = &ev2->ev_evcallback;
	array[2] = &evcb1;
	array[3] = &evcb2;

	

	n = event_callback_finalize_many(base, 4, array,
	    callback_finalize_callback_1);

}
#endif


#define TEST(name, flags)					\
	{ #name, test_fin_##name, (flags), &basic_setup, NULL }

struct testcase_t finalize_testcases[] = {

	TEST(cb_invoked, TT_FORK|TT_NEED_BASE),
	TEST(free_finalize, TT_FORK),
	TEST(within_cb, TT_FORK|TT_NEED_BASE),
	TEST(debug_use_after_free, TT_FORK|TT_NEED_BASE|TT_ENABLE_DEBUG_MODE),
//	TEST(many, TT_FORK|TT_NEED_BASE),


	END_OF_TESTCASES
};

