/*
 * Copyright © 2019 Collabora Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "config.h"

#include <libweston/weston-log.h>
#include "shared/helpers.h"
#include <libweston/libweston.h>

#include "weston-log-internal.h"
#include "weston-debug-server-protocol.h"

#include <assert.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>

/** A debug stream created by a client
 *
 * A client provides a file descriptor for the server to write debug messages
 * into. A weston_log_debug_wayland is associated to one weston_log_scope via the
 * scope name, and the scope provides the messages.  There can be several
 * streams for the same scope, all streams getting the same messages.
 *
 * The following is specific to weston-debug protocol.
 * Subscription/unsubscription takes place in the stream_create(), respectively
 * in stream_destroy().
 */
struct weston_log_debug_wayland {
	struct weston_log_subscriber base;
	int fd;				/**< client provided fd */
	struct wl_resource *resource;	/**< weston_debug_stream_v1 object */
};

static struct weston_log_debug_wayland *
to_weston_log_debug_wayland(struct weston_log_subscriber *sub)
{
	return container_of(sub, struct weston_log_debug_wayland, base);
}

static void
stream_close_unlink(struct weston_log_debug_wayland *stream)
{
	if (stream->fd != -1)
		close(stream->fd);
	stream->fd = -1;
}

static void WL_PRINTF(2, 3)
stream_close_on_failure(struct weston_log_debug_wayland *stream,
			const char *fmt, ...)
{
	char *msg;
	va_list ap;
	int ret;

	stream_close_unlink(stream);

	va_start(ap, fmt);
	ret = vasprintf(&msg, fmt, ap);
	va_end(ap);

	if (ret > 0) {
		weston_debug_stream_v1_send_failure(stream->resource, msg);
		free(msg);
	} else {
		weston_debug_stream_v1_send_failure(stream->resource,
						    "MEMFAIL");
	}
}

/** Write data into a specific debug stream
 *
 * \param sub The subscriber's stream to write into; must not be NULL.
 * \param[in] data Pointer to the data to write.
 * \param len Number of bytes to write.
 *
 * Writes the given data (binary verbatim) into the debug stream.
 * If \c len is zero or negative, the write is silently dropped.
 *
 * Writing is continued until all data has been written or
 * a write fails. If the write fails due to a signal, it is re-tried.
 * Otherwise on failure, the stream is closed and
 * \c weston_debug_stream_v1.failure event is sent to the client.
 *
 * \memberof weston_log_debug_wayland
 */
static void
weston_log_debug_wayland_write(struct weston_log_subscriber *sub,
			       const char *data, size_t len)
{
	ssize_t len_ = len;
	ssize_t ret;
	int e;
	struct weston_log_debug_wayland *stream = to_weston_log_debug_wayland(sub);

	if (stream->fd == -1)
		return;

	while (len_ > 0) {
		ret = write(stream->fd, data, len_);
		e = errno;
		if (ret < 0) {
			if (e == EINTR)
				continue;

			stream_close_on_failure(stream,
					"Error writing %zd bytes: %s (%d)",
					len_, strerror(e), e);
			break;
		}

		len_ -= ret;
		data += ret;
	}
}

/** Close the debug stream and send success event
 *
 * \param sub Subscriber's stream to close.
 *
 * Closes the debug stream and sends \c weston_debug_stream_v1.complete
 * event to the client. This tells the client the debug information dump
 * is complete.
 *
 * \memberof weston_log_debug_wayland
 */
static void
weston_log_debug_wayland_complete(struct weston_log_subscriber *sub)
{
	struct weston_log_debug_wayland *stream = to_weston_log_debug_wayland(sub);

	stream_close_unlink(stream);
	weston_debug_stream_v1_send_complete(stream->resource);
}

static void
weston_log_debug_wayland_to_destroy(struct weston_log_subscriber *sub)
{
	struct weston_log_debug_wayland *stream = to_weston_log_debug_wayland(sub);

	if (stream->fd != -1)
		stream_close_on_failure(stream, "debug name removed");
}

static struct weston_log_debug_wayland *
stream_create(struct weston_log_context *log_ctx, const char *name,
	      int32_t streamfd, struct wl_resource *stream_resource)
{
	struct weston_log_debug_wayland *stream;
	struct weston_log_scope *scope;

	stream = zalloc(sizeof *stream);
	if (!stream)
		return NULL;

	stream->fd = streamfd;
	stream->resource = stream_resource;

	stream->base.write = weston_log_debug_wayland_write;
	stream->base.destroy = NULL;
	stream->base.destroy_subscription = weston_log_debug_wayland_to_destroy;
	stream->base.complete = weston_log_debug_wayland_complete;
	wl_list_init(&stream->base.subscription_list);

	scope = weston_log_get_scope(log_ctx, name);
	if (scope) {
		weston_log_subscription_create(&stream->base, scope);
	} else {
		stream_close_on_failure(stream,
					"Debug stream name '%s' is unknown.",
					name);
	}

	return stream;
}

static void
stream_destroy(struct wl_resource *stream_resource)
{
	struct weston_log_debug_wayland *stream;
	stream = wl_resource_get_user_data(stream_resource);

	stream_close_unlink(stream);
	weston_log_subscriber_release(&stream->base);
	free(stream);
}

static void
weston_debug_stream_destroy(struct wl_client *client,
			    struct wl_resource *stream_resource)
{
	wl_resource_destroy(stream_resource);
}

static const struct weston_debug_stream_v1_interface
						weston_debug_stream_impl = {
	weston_debug_stream_destroy
};

static void
weston_debug_destroy(struct wl_client *client,
		     struct wl_resource *global_resource)
{
	wl_resource_destroy(global_resource);
}

static void
weston_debug_subscribe(struct wl_client *client,
		       struct wl_resource *global_resource,
		       const char *name,
		       int32_t streamfd,
		       uint32_t new_stream_id)
{
	struct weston_log_context *log_ctx;
	struct wl_resource *stream_resource;
	uint32_t version;
	struct weston_log_debug_wayland *stream;

	log_ctx = wl_resource_get_user_data(global_resource);
	version = wl_resource_get_version(global_resource);

	stream_resource = wl_resource_create(client,
					&weston_debug_stream_v1_interface,
					version, new_stream_id);
	if (!stream_resource)
		goto fail;

	stream = stream_create(log_ctx, name, streamfd, stream_resource);
	if (!stream)
		goto fail;

	wl_resource_set_implementation(stream_resource,
				       &weston_debug_stream_impl,
				       stream, stream_destroy);
	return;

fail:
	close(streamfd);
	wl_client_post_no_memory(client);
}

static const struct weston_debug_v1_interface weston_debug_impl = {
	weston_debug_destroy,
	weston_debug_subscribe
};

void
weston_log_bind_weston_debug(struct wl_client *client,
			     void *data, uint32_t version, uint32_t id)
{
	struct weston_log_context *log_ctx = data;
	struct wl_resource *resource;

	resource = wl_resource_create(client,
				      &weston_debug_v1_interface,
				      version, id);
	if (!resource) {
		wl_client_post_no_memory(client);
		return;
	}
	wl_resource_set_implementation(resource, &weston_debug_impl,
				       log_ctx, NULL);

	weston_debug_protocol_advertise_scopes(log_ctx, resource);
}
