/*
 * Copyright © 2019 Advanced Driver Information Technology GmbH
 * Copyright © 2020 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.
 */

/*******************************************************************************
**                                                                            **
**  TARGET    : linux                                                         **
**                                                                            **
**  PROJECT   : waltham-receiver                                              **
**                                                                            **
**  PURPOSE   :  This file acts as interface to waltham IPC library           **
**                                                                            **
**                                                                            **
*******************************************************************************/

#include "wth-receiver-comm.h"
#include "wth-receiver-surface.h"
#include "wth-receiver-seat.h"
#include "wth-receiver-buffer.h"

extern uint16_t tcp_port;
extern const char *my_app_id;

void
client_post_out_of_memory(struct client *c)
{
	struct wth_display *disp;

	disp = wth_connection_get_display(c->connection);
	wth_object_post_error((struct wth_object *) disp, 1, "out of memory");
}

/*
 *  waltam ivi surface implementation
 */
static void
wthp_ivi_surface_destroy(struct wthp_ivi_surface * ivi_surface)
{
	struct ivisurface *ivisurf = wth_object_get_user_data((struct wth_object *)ivi_surface);

	if (ivisurf->surf->ivi_app_id)
		free(ivisurf->surf->ivi_app_id);

	free(ivisurf);
}

static const struct wthp_ivi_surface_interface wthp_ivi_surface_implementation = {
	wthp_ivi_surface_destroy,
};


/**
 * app_id version
 */
static void
wthp_ivi_app_id_surface_create(struct wthp_ivi_app_id *ivi_application,
			       const char *app_id,
			       struct wthp_surface * wthp_surface,
			       struct wthp_ivi_surface *obj)
{
	struct surface *surface = wth_object_get_user_data((struct wth_object *)wthp_surface);

	/* we destroy it wthp_ivi_surface_implementation:: */
	if (my_app_id) {
		surface->ivi_app_id = strdup(my_app_id);
		surface->shm_window->app_id = surface->ivi_app_id;
	}

	struct ivisurface *ivisurf;

	ivisurf = zalloc(sizeof *ivisurf);
	if (!ivisurf) {
		return;
	}

	ivisurf->obj = obj;
	ivisurf->surf = surface;

	wthp_ivi_surface_set_interface(obj,
				       &wthp_ivi_surface_implementation, ivisurf);

	if (my_app_id)
		wth_receiver_weston_main(surface->shm_window, my_app_id, tcp_port);
	else
		wth_receiver_weston_main(surface->shm_window, app_id, tcp_port);

	while (!surface->shm_window->ready)
		usleep(1);
}

static const struct wthp_ivi_app_id_interface wthp_ivi_app_id_implementation = {
	wthp_ivi_app_id_surface_create,
};

static void
client_bind_wthp_ivi_app_id(struct client *c, struct wthp_ivi_app_id *obj)
{
	struct application_id *app;

	app = zalloc(sizeof *app);
	if (!app) {
		client_post_out_of_memory(c);
		return;
	}

	app->obj = obj;
	app->client = c;
	wl_list_insert(&c->compositor_list, &app->link);

	wthp_ivi_app_id_set_interface(obj, &wthp_ivi_app_id_implementation, app);
}

/*
 * waltham registry implementation
 */
static void
registry_destroy(struct registry *reg)
{
	wthp_registry_free(reg->obj);
	wl_list_remove(&reg->link);
	free(reg);
}

static void
registry_handle_destroy(struct wthp_registry *registry)
{
	struct registry *reg = wth_object_get_user_data((struct wth_object *)registry);
	registry_destroy(reg);
}

static void
registry_handle_bind(struct wthp_registry *registry,
		     uint32_t name, struct wth_object *id,
		     const char *interface, uint32_t version)
{

	struct registry *reg = wth_object_get_user_data((struct wth_object *)registry);

	if (strcmp(interface, "wthp_compositor") == 0) {
		client_bind_compositor(reg->client, (struct wthp_compositor *)id);
	} else if (strcmp(interface, "wthp_blob_factory") == 0) {
		struct client *client = reg->client;
		struct seat *seat, *tmp, *get_seat;

		client_bind_blob_factory(reg->client, (struct wthp_blob_factory *)id);

		get_seat = NULL;
		wl_list_for_each_safe(seat, tmp, &client->seat_list, link) {
			get_seat = seat;
		}

		if (get_seat)
			seat_send_updated_caps(get_seat);
	} else if (strcmp(interface, "wthp_ivi_app_id") == 0) {
		client_bind_wthp_ivi_app_id(reg->client, (struct wthp_ivi_app_id *) id);
	} else if (strcmp(interface, "wthp_seat") == 0) {
		client_bind_seat(reg->client, (struct wthp_seat *)id);
	} else {
		wth_object_post_error((struct wth_object *)registry, 0,
				"%s: unknown name %u", __func__, name);
		wth_object_delete(id);
	}
}

static const struct wthp_registry_interface registry_implementation = {
	registry_handle_destroy,
	registry_handle_bind
};

/*
 * waltham display implementation
 */

static void
display_handle_client_version(struct wth_display *wth_display,
		uint32_t client_version)
{
	wth_object_post_error((struct wth_object *)wth_display, 0,
			"unimplemented: %s", __func__);
}

static void
display_handle_sync(struct wth_display * wth_display, struct wthp_callback * callback)
{
	wthp_callback_send_done(callback, 0);
	wthp_callback_free(callback);
}

static void
display_handle_get_registry(struct wth_display *wth_display,
			    struct wthp_registry *registry)
{
	struct client *c = wth_object_get_user_data((struct wth_object *)wth_display);
	struct registry *reg;

	reg = zalloc(sizeof *reg);
	if (!reg) {
		client_post_out_of_memory(c);
		return;
	}

	reg->obj = registry;
	reg->client = c;
	wl_list_insert(&c->registry_list, &reg->link);
	wthp_registry_set_interface(registry,
			&registry_implementation, reg);

	wthp_registry_send_global(registry, 1, "wthp_compositor", 4);
	wthp_registry_send_global(registry, 1, "wthp_ivi_app_id", 1);
	wthp_registry_send_global(registry, 1, "wthp_seat", 4);
	wthp_registry_send_global(registry, 1, "wthp_blob_factory", 4);

}

const struct wth_display_interface display_implementation = {
	display_handle_client_version,
	display_handle_sync,
	display_handle_get_registry
};

/*
 * utility functions
 */
static int
watch_ctl(struct watch *w, int op, uint32_t events)
{
	struct epoll_event ee;

	ee.events = events;
	ee.data.ptr = w;
	return epoll_ctl(w->receiver->epoll_fd, op, w->fd, &ee);
}

/**
* client_destroy
*
* Destroy client connection
*
* @param names        struct client *c
* @param value        client data
* @return             none
*/
void
client_destroy(struct client *c)
{
	struct region *region;
	struct compositor *comp;
	struct registry *reg;
	struct surface *surface;

	/* clean up remaining client resources in case the client
	 * did not.
	 */
	wl_list_last_until_empty(region, &c->region_list, link)
		region_destroy(region);

	wl_list_last_until_empty(comp, &c->compositor_list, link)
		compositor_destroy(comp);

	wl_list_last_until_empty(reg, &c->registry_list, link)
		registry_destroy(reg);

	wl_list_last_until_empty(surface, &c->surface_list, link)
		surface_destroy(surface);

	wl_list_remove(&c->link);
	watch_ctl(&c->conn_watch, EPOLL_CTL_DEL, 0);
	wth_connection_destroy(c->connection);
	free(c);
}

/*
 * functions to handle waltham client connections
 */
static void
connection_handle_data(struct watch *w, uint32_t events)
{

	struct client *c = container_of(w, struct client, conn_watch);
	int ret;

	if (events & EPOLLERR) {
		wth_error("Client %p errored out.\n", c);
		client_destroy(c);
		return;
	}

	if (events & EPOLLHUP) {
		wth_error("Client %p hung up.\n", c);
		client_destroy(c);
		return;
	}

	if (events & EPOLLOUT) {
		ret = wth_connection_flush(c->connection);
		if (ret == 0) {
			watch_ctl(&c->conn_watch, EPOLL_CTL_MOD, EPOLLIN);
		} else if (ret < 0 && errno != EAGAIN) {
			wth_error("Client %p flush error.\n", c);
			client_destroy(c);
			return;
		}
	}

	if (events & EPOLLIN) {
		ret = wth_connection_read(c->connection);
		if (ret < 0) {
			wth_error("Client %p read error.\n", c);
			client_destroy(c);
			return;
		}

		ret = wth_connection_dispatch(c->connection);
		if (ret < 0 && errno != EPROTO) {
			wth_error("Client %p dispatch error.\n", c);
			client_destroy(c);
			return;
		}
	}
}

/**
 * client_create
 *
 * Create new client connection
 *
 * @param srv                   receiver structure
 * @param wth_connection        Waltham connection handle
 * @return                      Pointer to client structure
 */
static struct client *
client_create(struct receiver *srv, struct wth_connection *conn)
{

	struct client *c;
	struct wth_display *disp;

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

	c->receiver = srv;
	c->connection = conn;

	c->conn_watch.receiver = srv;
	c->conn_watch.fd = wth_connection_get_fd(conn);
	c->conn_watch.cb = connection_handle_data;
	if (watch_ctl(&c->conn_watch, EPOLL_CTL_ADD, EPOLLIN) < 0) {
		free(c);
		return NULL;
	}


	wl_list_insert(&srv->client_list, &c->link);

	wl_list_init(&c->registry_list);
	wl_list_init(&c->compositor_list);
	wl_list_init(&c->seat_list);
	wl_list_init(&c->pointer_list);
	wl_list_init(&c->touch_list);
	wl_list_init(&c->region_list);
	wl_list_init(&c->surface_list);
	wl_list_init(&c->buffer_list);

	disp = wth_connection_get_display(c->connection);
	wth_display_set_interface(disp, &display_implementation, c);

	return c;
}


/**
* receiver_flush_clients
*
* write all the pending requests from the clients to socket
*
* @param names        struct receiver *srv
* @param value        socket connection info and client data
* @return             none
*/
void
receiver_flush_clients(struct receiver *srv)
{
	struct client *c, *tmp;
	int ret;

	wl_list_for_each_safe(c, tmp, &srv->client_list, link) {
		/* Flush out buffered requests. If the Waltham socket is
		 * full, poll it for writable too.
		 */
		ret = wth_connection_flush(c->connection);
		if (ret < 0 && errno == EAGAIN) {
			watch_ctl(&c->conn_watch, EPOLL_CTL_MOD, EPOLLIN | EPOLLOUT);
		} else if (ret < 0) {
			perror("Connection flush failed");
			client_destroy(c);
			return;
		}
	}
}

/**
* receiver_accept_client
*
* Accepts new waltham client connection and instantiates client structure
*
* @param names        struct receiver *srv
* @param value        socket connection info and client data
* @return             none
*/
void
receiver_accept_client(struct receiver *srv)
{
	struct client *client;
	struct wth_connection *conn;
	struct sockaddr_in addr;
	socklen_t len;

	len = sizeof(addr);
	conn = wth_accept(srv->listen_fd, (struct sockaddr *)&addr, &len);
	if (!conn) {
		wth_error("Failed to accept a connection.\n");
		return;
	}

	client = client_create(srv, conn);
	if (!client) {
		wth_error("Failed client_create().\n");
		return;
	}
}