/*
   Unix SMB/CIFS implementation.
   Connect avahi to lib/tevents
   Copyright (C) Volker Lendecke 2009

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

   This program 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 General Public License for more details.

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

#include "includes.h"

#include <avahi-common/watch.h>

struct avahi_poll_context {
	struct tevent_context *ev;
	AvahiWatch **watches;
	AvahiTimeout **timeouts;
};

struct AvahiWatch {
	struct avahi_poll_context *ctx;
	struct tevent_fd *fde;
	int fd;
	AvahiWatchEvent latest_event;
	AvahiWatchCallback callback;
	void *userdata;
};

struct AvahiTimeout {
	struct avahi_poll_context *ctx;
	struct tevent_timer *te;
	AvahiTimeoutCallback callback;
	void *userdata;
};

static uint16_t avahi_flags_map_to_tevent(AvahiWatchEvent event)
{
	return ((event & AVAHI_WATCH_IN) ? TEVENT_FD_READ : 0)
		| ((event & AVAHI_WATCH_OUT) ? TEVENT_FD_WRITE : 0);
}

static void avahi_fd_handler(struct tevent_context *ev,
			     struct tevent_fd *fde, uint16_t flags,
			     void *private_data);

static AvahiWatch *avahi_watch_new(const AvahiPoll *api, int fd,
				   AvahiWatchEvent event,
				   AvahiWatchCallback callback,
				   void *userdata)
{
	struct avahi_poll_context *ctx = talloc_get_type_abort(
		api->userdata, struct avahi_poll_context);
	int num_watches = talloc_array_length(ctx->watches);
	AvahiWatch **tmp, *watch_ctx;

	tmp = talloc_realloc(ctx, ctx->watches, AvahiWatch *, num_watches + 1);
	if (tmp == NULL) {
		return NULL;
	}
	ctx->watches = tmp;

	watch_ctx = talloc(tmp, AvahiWatch);
	if (watch_ctx == NULL) {
		goto fail;
	}
	ctx->watches[num_watches] = watch_ctx;

	watch_ctx->ctx = ctx;
	watch_ctx->fde = tevent_add_fd(ctx->ev, watch_ctx, fd,
				       avahi_flags_map_to_tevent(event),
				       avahi_fd_handler, watch_ctx);
	if (watch_ctx->fde == NULL) {
		goto fail;
	}
	watch_ctx->callback = callback;
	watch_ctx->userdata = userdata;
	return watch_ctx;

 fail:
	TALLOC_FREE(watch_ctx);
	ctx->watches = talloc_realloc(ctx, ctx->watches, AvahiWatch *,
				      num_watches);
	return NULL;
}

static void avahi_fd_handler(struct tevent_context *ev,
			     struct tevent_fd *fde, uint16_t flags,
			     void *private_data)
{
	AvahiWatch *watch_ctx = talloc_get_type_abort(private_data, AvahiWatch);

	watch_ctx->latest_event =
		((flags & TEVENT_FD_READ) ? AVAHI_WATCH_IN : 0)
		| ((flags & TEVENT_FD_WRITE) ? AVAHI_WATCH_OUT : 0);

	watch_ctx->callback(watch_ctx, watch_ctx->fd, watch_ctx->latest_event,
			    watch_ctx->userdata);
}

static void avahi_watch_update(AvahiWatch *w, AvahiWatchEvent event)
{
	tevent_fd_set_flags(w->fde, avahi_flags_map_to_tevent(event));
}

static AvahiWatchEvent avahi_watch_get_events(AvahiWatch *w)
{
	return w->latest_event;
}

static void avahi_watch_free(AvahiWatch *w)
{
	int i, num_watches;
	AvahiWatch **watches = w->ctx->watches;
	struct avahi_poll_context *ctx;

	num_watches = talloc_array_length(watches);

	for (i=0; i<num_watches; i++) {
		if (w == watches[i]) {
			break;
		}
	}
	if (i == num_watches) {
		return;
	}
	ctx = w->ctx;
	TALLOC_FREE(w);
	memmove(&watches[i], &watches[i+1],
		sizeof(*watches) * (num_watches - i - 1));
	ctx->watches = talloc_realloc(ctx, watches, AvahiWatch *,
				      num_watches - 1);
}

static void avahi_timeout_handler(struct tevent_context *ev,
				  struct tevent_timer *te,
				  struct timeval current_time,
				  void *private_data);

static AvahiTimeout *avahi_timeout_new(const AvahiPoll *api,
				       const struct timeval *tv,
				       AvahiTimeoutCallback callback,
				       void *userdata)
{
	struct avahi_poll_context *ctx = talloc_get_type_abort(
		api->userdata, struct avahi_poll_context);
	int num_timeouts = talloc_array_length(ctx->timeouts);
	AvahiTimeout **tmp, *timeout_ctx;

	tmp = talloc_realloc(ctx, ctx->timeouts, AvahiTimeout *,
			     num_timeouts + 1);
	if (tmp == NULL) {
		return NULL;
	}
	ctx->timeouts = tmp;

	timeout_ctx = talloc(tmp, AvahiTimeout);
	if (timeout_ctx == NULL) {
		goto fail;
	}
	ctx->timeouts[num_timeouts] = timeout_ctx;

	timeout_ctx->ctx = ctx;
	if (tv == NULL) {
		timeout_ctx->te = NULL;
	} else {
		timeout_ctx->te = tevent_add_timer(ctx->ev, timeout_ctx,
						   *tv, avahi_timeout_handler,
						   timeout_ctx);
		if (timeout_ctx->te == NULL) {
			goto fail;
		}
	}
	timeout_ctx->callback = callback;
	timeout_ctx->userdata = userdata;
	return timeout_ctx;

 fail:
	TALLOC_FREE(timeout_ctx);
	ctx->timeouts = talloc_realloc(ctx, ctx->timeouts, AvahiTimeout *,
				       num_timeouts);
	return NULL;
}

static void avahi_timeout_handler(struct tevent_context *ev,
				  struct tevent_timer *te,
				  struct timeval current_time,
				  void *private_data)
{
	AvahiTimeout *timeout_ctx = talloc_get_type_abort(
		private_data, AvahiTimeout);

	TALLOC_FREE(timeout_ctx->te);
	timeout_ctx->callback(timeout_ctx, timeout_ctx->userdata);
}

static void avahi_timeout_update(AvahiTimeout *t, const struct timeval *tv)
{
	TALLOC_FREE(t->te);

	if (tv == NULL) {
		/*
		 * Disable this timer
		 */
		return;
	}

	t->te = tevent_add_timer(t->ctx->ev, t, *tv, avahi_timeout_handler, t);
	/*
	 * No failure mode defined here
	 */
	SMB_ASSERT(t->te != NULL);
}

static void avahi_timeout_free(AvahiTimeout *t)
{
	int i, num_timeouts;
	AvahiTimeout **timeouts = t->ctx->timeouts;
	struct avahi_poll_context *ctx;

	num_timeouts = talloc_array_length(timeouts);

	for (i=0; i<num_timeouts; i++) {
		if (t == timeouts[i]) {
			break;
		}
	}
	if (i == num_timeouts) {
		return;
	}
	ctx = t->ctx;
	TALLOC_FREE(t);
	memmove(&timeouts[i], &timeouts[i+1],
		sizeof(*timeouts) * (num_timeouts - i - 1));
	ctx->timeouts = talloc_realloc(ctx, timeouts, AvahiTimeout *,
				       num_timeouts - 1);
}

struct AvahiPoll *tevent_avahi_poll(TALLOC_CTX *mem_ctx,
				    struct tevent_context *ev)
{
	struct AvahiPoll *result;
	struct avahi_poll_context *ctx;

	result = talloc(mem_ctx, struct AvahiPoll);
	if (result == NULL) {
		return result;
	}
	ctx = talloc_zero(result, struct avahi_poll_context);
	if (ctx == NULL) {
		TALLOC_FREE(result);
		return NULL;
	}
	ctx->ev = ev;

	result->watch_new		= avahi_watch_new;
	result->watch_update		= avahi_watch_update;
	result->watch_get_events	= avahi_watch_get_events;
	result->watch_free		= avahi_watch_free;
	result->timeout_new		= avahi_timeout_new;
	result->timeout_update		= avahi_timeout_update;
	result->timeout_free		= avahi_timeout_free;
	result->userdata		= ctx;

	return result;
}