/*
   Unix SMB/CIFS implementation.

   common events code for immediate events

   Copyright (C) Stefan Metzmacher 2009

     ** NOTE! The following LGPL license applies to the tevent
     ** library. This does NOT imply that all of Samba is released
     ** under the LGPL

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/

#include "replace.h"
#define TEVENT_DEPRECATED 1
#include "tevent.h"
#include "tevent_internal.h"
#include "tevent_util.h"

static void tevent_common_immediate_cancel(struct tevent_immediate *im)
{
	const char *create_location = im->create_location;
	bool busy = im->busy;
	uint64_t tag = im->tag;
	struct tevent_context *detach_ev_ctx = NULL;

	if (im->destroyed) {
		tevent_abort(im->event_ctx, "tevent_immediate use after free");
		return;
	}

	if (im->detach_ev_ctx != NULL) {
		detach_ev_ctx = im->detach_ev_ctx;
		im->detach_ev_ctx = NULL;
		tevent_trace_immediate_callback(detach_ev_ctx,
						im,
						TEVENT_EVENT_TRACE_DETACH);
		return;
	}

	if (!im->event_ctx) {
		return;
	}

	if (im->handler_name != NULL) {
		TEVENT_DEBUG(im->event_ctx, TEVENT_DEBUG_TRACE,
			     "Cancel immediate event %p \"%s\"\n",
			     im, im->handler_name);
	}

	/* let the backend free im->additional_data */
	if (im->cancel_fn) {
		im->cancel_fn(im);
	}

	if (busy && im->handler_name == NULL) {
		detach_ev_ctx = im->event_ctx;
	} else {
		tevent_trace_immediate_callback(im->event_ctx,
						im,
						TEVENT_EVENT_TRACE_DETACH);
	}
	DLIST_REMOVE(im->event_ctx->immediate_events, im);

	*im = (struct tevent_immediate) {
		.create_location	= create_location,
		.busy			= busy,
		.tag			= tag,
		.detach_ev_ctx		= detach_ev_ctx,
	};

	if (!busy) {
		talloc_set_destructor(im, NULL);
	}
}

/*
  destroy an immediate event
*/
static int tevent_common_immediate_destructor(struct tevent_immediate *im)
{
	if (im->destroyed) {
		tevent_common_check_double_free(im,
						"tevent_immediate double free");
		goto done;
	}

	tevent_common_immediate_cancel(im);

	im->destroyed = true;

done:
	if (im->busy) {
		return -1;
	}

	return 0;
}

/*
 * schedule an immediate event on
 */
void tevent_common_schedule_immediate(struct tevent_immediate *im,
				      struct tevent_context *ev,
				      tevent_immediate_handler_t handler,
				      void *private_data,
				      const char *handler_name,
				      const char *location)
{
	const char *create_location = im->create_location;
	bool busy = im->busy;
	uint64_t tag = im->tag;
	struct tevent_wrapper_glue *glue = im->wrapper;

	tevent_common_immediate_cancel(im);

	if (!handler) {
		return;
	}

	*im = (struct tevent_immediate) {
		.event_ctx		= ev,
		.wrapper		= glue,
		.handler		= handler,
		.private_data		= private_data,
		.handler_name		= handler_name,
		.create_location	= create_location,
		.schedule_location	= location,
		.busy			= busy,
		.tag			= tag,
	};

	tevent_trace_immediate_callback(im->event_ctx, im, TEVENT_EVENT_TRACE_ATTACH);
	DLIST_ADD_END(ev->immediate_events, im);
	talloc_set_destructor(im, tevent_common_immediate_destructor);

	TEVENT_DEBUG(ev, TEVENT_DEBUG_TRACE,
		     "Schedule immediate event \"%s\": %p\n",
		     handler_name, im);
}

int tevent_common_invoke_immediate_handler(struct tevent_immediate *im,
					   bool *removed)
{
	struct tevent_context *handler_ev = im->event_ctx;
	struct tevent_context *ev = im->event_ctx;
	struct tevent_immediate cur = *im;

	if (removed != NULL) {
		*removed = false;
	}

	TEVENT_DEBUG(ev, TEVENT_DEBUG_TRACE,
		     "Run immediate event \"%s\": %p\n",
		     im->handler_name, im);

	/*
	 * remember the handler and then clear the event
	 * the handler might reschedule the event
	 */

	im->busy = true;
	im->handler_name = NULL;
	tevent_common_immediate_cancel(im);
	if (cur.wrapper != NULL) {
		handler_ev = cur.wrapper->wrap_ev;

		tevent_wrapper_push_use_internal(handler_ev, cur.wrapper);
		cur.wrapper->ops->before_immediate_handler(
					cur.wrapper->wrap_ev,
					cur.wrapper->private_state,
					cur.wrapper->main_ev,
					im,
					cur.handler_name,
					cur.schedule_location);
	}
	tevent_trace_immediate_callback(cur.event_ctx, im, TEVENT_EVENT_TRACE_BEFORE_HANDLER);
	cur.handler(handler_ev, im, cur.private_data);
	if (cur.wrapper != NULL) {
		cur.wrapper->ops->after_immediate_handler(
					cur.wrapper->wrap_ev,
					cur.wrapper->private_state,
					cur.wrapper->main_ev,
					im,
					cur.handler_name,
					cur.schedule_location);
		tevent_wrapper_pop_use_internal(handler_ev, cur.wrapper);
	}
	im->busy = false;

	/* The event was removed in tevent_common_immediate_cancel(). */
	if (im->detach_ev_ctx != NULL) {
		struct tevent_context *detach_ev_ctx = im->detach_ev_ctx;
		im->detach_ev_ctx = NULL;
		tevent_trace_immediate_callback(detach_ev_ctx,
						im,
						TEVENT_EVENT_TRACE_DETACH);
	}

	if (im->destroyed) {
		talloc_set_destructor(im, NULL);
		TALLOC_FREE(im);
		if (removed != NULL) {
			*removed = true;
		}
	}

	return 0;
}

/*
  trigger the first immediate event and return true
  if no event was triggered return false
*/
bool tevent_common_loop_immediate(struct tevent_context *ev)
{
	struct tevent_immediate *im = ev->immediate_events;
	int ret;

	if (!im) {
		return false;
	}

	ret = tevent_common_invoke_immediate_handler(im, NULL);
	if (ret != 0) {
		tevent_abort(ev, "tevent_common_invoke_immediate_handler() failed");
	}

	return true;
}


void tevent_immediate_set_tag(struct tevent_immediate *im, uint64_t tag)
{
	if (im == NULL) {
		return;
	}

	im->tag = tag;
}

uint64_t tevent_immediate_get_tag(const struct tevent_immediate *im)
{
	if (im == NULL) {
		return 0;
	}

	return im->tag;
}
