/*
 * Copyright (C)2018-2022 Intel Corporation.
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/queue.h>
#include <sys/un.h>
#include <pthread.h>
#include <errno.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <dirent.h>
#include "mevent.h"
#include "acrn_mngr.h"

/* helpers */
/* Check if @path exists and if is a directory, if not existence, create or warn according to the flag */
int check_dir(const char *path, int flags)
{
        struct stat st;

        if (stat(path, &st)) {
                if (flags) {
                        if (mkdir(path, 0666)) {
                                perror(path);
                                return -1;
                        }
                        return 0;
                } else {
                        printf("%s doesn't exist!\n", path);
                        return -1;
                }
        }

        if (S_ISDIR(st.st_mode))
                return 0;

        fprintf(stderr, "%s exist, and not a directory!\n", path);
        return -1;
}


#define MNGR_SOCK_FMT		"/run/acrn/mngr/%s.%d.socket"
#define MNGR_MAX_HANDLER	8
#define MNGR_MAX_CLIENT		4

#define CLIENT_BUF_LEN  4096

struct mngr_client {
	/* the rest should be invisible for msg_handler */
	struct sockaddr_un addr;
	int fd;
	socklen_t addr_len;
	void *buf;
	int len;		/* buf len */
	LIST_ENTRY(mngr_client) list;
};

struct mngr_handler {
	unsigned id;
	void (*cb) (struct mngr_msg * msg, int client_fd, void *priv);
	void *priv;
	LIST_ENTRY(mngr_handler) list;
};

struct mngr_fd {
	int type;
	int desc;		/* unique int to this descripter */
				/* returned by mngr_open_un */
	LIST_ENTRY(mngr_fd) list;

	/* Unix socket stuff */
	int fd;			/* the unix socket fd */
	struct sockaddr_un addr;
	socklen_t addr_len;

	/* for servet */
	int listening;
	int polling;
	pthread_t listen_thread;	/* for connect/disconnect */
	pthread_t poll_thread;	/* poll requests */

	/* a server can have many client connection */
	LIST_HEAD(client_list, mngr_client) client_head;	/* clients for this server */
	pthread_mutex_t client_mtx;
	int num_client;

	/* handlers */
	LIST_HEAD(handler_list, mngr_handler) handler_head;	/* clients for this server */
	pthread_mutex_t handler_mtx;
};

static struct mngr_client *mngr_client_new(struct mngr_fd *mfd)
{
	struct mngr_client *client;

	client = calloc(1, sizeof(*client));
	if (!client) {
		printf("%s: Failed to alloc mem for client\n", __func__);
		goto alloc_client;
	}

	client->buf = calloc(1, CLIENT_BUF_LEN);
	if (!client->buf) {
		printf("%s: Failed to alloc mem for client buf\n", __func__);
		goto alloc_buf;
	}

	client->addr_len = sizeof(client->addr);
	client->fd =
	    accept(mfd->fd, (struct sockaddr *)&client->addr,
		   &client->addr_len);
	if (client->fd < 0) {
		printf("%s: Failed to accept from fd %d, err: %s\n", __func__, mfd->fd, strerror(errno));
		goto accept_con;
	}

	pthread_mutex_lock(&mfd->client_mtx);
	LIST_INSERT_HEAD(&mfd->client_head, client, list);
	pthread_mutex_unlock(&mfd->client_mtx);

	return client;

 accept_con:
	free(client->buf);
	client->buf = NULL;
 alloc_buf:
	free(client);
 alloc_client:
	return NULL;
}

static void mngr_client_free_res(struct mngr_client *client)
{
	close(client->fd);
	client->fd = -1;
	free(client->buf);
	client->buf = NULL;
	free(client);
}

static void mngr_client_free(struct mngr_fd *mfd, struct mngr_client *client)
{
	pthread_mutex_lock(&mfd->client_mtx);
	LIST_REMOVE(client, list);
	pthread_mutex_unlock(&mfd->client_mtx);

	mngr_client_free_res(client);
}

static LIST_HEAD(mngr_fd_list, mngr_fd) mngr_fd_head;
static pthread_mutex_t mngr_fd_mtx = PTHREAD_MUTEX_INITIALIZER;

static void *server_listen_func(void *arg)
{
	struct mngr_fd *mfd = arg;
	struct mngr_client *client;

	printf("Listening %d...\n", mfd->desc);
	while (mfd->listening) {
		/* wait connection */
		if (mfd->num_client >= MNGR_MAX_CLIENT) {
			usleep(500000);
			continue;
		}

		client = mngr_client_new(mfd);
		if (!client) {
			usleep(500000);
			continue;
		}
		printf("Connected:%d\n", client->fd);
		mfd->num_client++;
	}
	printf("Stop listening %d...\n", mfd->desc);
	return NULL;
}

static int server_parse_buf(struct mngr_fd *mfd, struct mngr_client *client)
{
	struct mngr_msg *msg;
	struct mngr_handler *handler;
	size_t p = 0;
	int handled = 0;

	if (client->len < sizeof(struct mngr_msg))
		return -1;

	do {
		msg = client->buf + p;

		/* do we out-of-boundary? */
		if (p + sizeof(struct mngr_msg) > client->len) {
			printf("%s: Out of boundary, client len: %d, p: %lu\n", __func__, client->len, p);
			break;
		}

		LIST_FOREACH(handler, &mfd->handler_head, list) {
			if (msg->magic != MNGR_MSG_MAGIC)
				return -1;
			if (handler->id != msg->msgid)
				continue;

			handler->cb(msg, client->fd, handler->priv);
			handled = 1;
			break;
		}
		p += sizeof(struct mngr_msg);
	} while (p < client->len);

	if (!handled)
		fprintf(stderr, "Unknown message id: %d\n", msg->msgid);

	return 0;
}

static void *server_poll_func(void *arg)
{
	struct mngr_fd *mfd = arg;
	struct mngr_client *client;
	fd_set rfd;
	int max_fd = 0;
	struct timeval timeout;
	struct mngr_client *poll_client[MNGR_MAX_CLIENT];
	int nfd, i;

	printf("polling %d...\n", mfd->desc);
	while (mfd->polling) {
		max_fd = 0;
		nfd = 0;
		pthread_mutex_lock(&mfd->client_mtx);
		FD_ZERO(&rfd);
		LIST_FOREACH(client, &mfd->client_head, list) {
			FD_SET(client->fd, &rfd);
			poll_client[nfd] = client;
			nfd++;
			if (client->fd > max_fd)
				max_fd = client->fd;
		}
		pthread_mutex_unlock(&mfd->client_mtx);

		timeout.tv_sec = 0;
		timeout.tv_usec = 10000;
		select(max_fd + 1, &rfd, NULL, NULL, &timeout);

		for (i = 0; i < nfd; i++) {
			client = poll_client[i];
			if (!FD_ISSET(client->fd, &rfd))
				continue;
			client->len =
			    read(client->fd, client->buf, CLIENT_BUF_LEN);
			if (client->len <= 0) {
				fprintf(stderr, "Disconnect(%d)!\r\n",
					client->fd);
				mngr_client_free(mfd, client);
				mfd->num_client--;
				continue;
			}
			if (client->len == CLIENT_BUF_LEN) {
				fprintf(stderr, "TODO: buf overflow!\r\n");
				continue;
			}

			server_parse_buf(mfd, client);
		}
	}
	printf("Stop polling %d...\n", mfd->desc);

	return NULL;
}

static struct mngr_fd *desc_to_mfd_nolock(int val)
{
	struct mngr_fd *fd;
	struct mngr_fd *find = NULL;

	LIST_FOREACH(fd, &mngr_fd_head, list)
	    if (val == fd->desc) {
		find = fd;
		break;
	}

	return find;
}

/* Does this integer number has a mngr_fd behind? */
static struct mngr_fd *desc_to_mfd(int val)
{
	struct mngr_fd *find = NULL;

	pthread_mutex_lock(&mngr_fd_mtx);
	find = desc_to_mfd_nolock(val);
	pthread_mutex_unlock(&mngr_fd_mtx);

	return find;
}

static int create_new_server(const char *name)
{
	struct mngr_fd *mfd;
	int ret;
	char path[128] = { };

	if (snprintf(path, sizeof(path), MNGR_SOCK_FMT, name, getpid()) >= sizeof(path)) {
		printf("WARN: the path is truncated\n");
		return -1;
	}

	mfd = calloc(1, sizeof(*mfd));
	if (!mfd) {
		perror("Alloc struct mngr_fd");
		ret = errno;
		goto alloc_mfd;
	}
	pthread_mutex_init(&mfd->client_mtx, NULL);
	mfd->type = MNGR_SERVER;

	/* Socket stuff */
	unlink(path);
	mfd->fd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (mfd->fd < 0) {
		printf("%s: Failed to call socket, err: %s\n", __func__, strerror(errno));
		ret = mfd->fd;
		goto sock_err;
	}
	mfd->addr.sun_family = AF_UNIX;
	strncpy(mfd->addr.sun_path, path, sizeof(mfd->addr.sun_path));

	ret = bind(mfd->fd, (struct sockaddr *)&mfd->addr, sizeof(mfd->addr));
	if (ret < 0) {
		printf("%s: Failed to bind fd %d, err: %s\n", __func__, mfd->fd, strerror(errno));
		goto bind_err;
	}
	listen(mfd->fd, 1);

	/* create a listen_thread */
	mfd->listening = 1;
	ret =
	    pthread_create(&mfd->listen_thread, NULL, server_listen_func, mfd);
	if (ret < 0) {
		printf("%s: Failed to create listen_thread, err: %s\n", __func__, strerror(errno));
		goto listen_err;
	}
	pthread_setname_np(mfd->listen_thread, "mngr_listen");

	/* create a poll_thread */
	mfd->polling = 1;
	ret = pthread_create(&mfd->poll_thread, NULL, server_poll_func, mfd);
	if (ret < 0) {
		printf("%s: Failed to create poll_thread, err: %s\n", __func__, strerror(errno));
		goto poll_err;
	}
	pthread_setname_np(mfd->poll_thread, "mngr_pull");

	mfd->desc = mfd->fd;
	/* add this to mngr_fd_head */
	pthread_mutex_lock(&mngr_fd_mtx);
	LIST_INSERT_HEAD(&mngr_fd_head, mfd, list);
	pthread_mutex_unlock(&mngr_fd_mtx);

	return mfd->desc;

 poll_err:
	mfd->listening = 0;
	pthread_join(mfd->listen_thread, NULL);
 listen_err:
	unlink(path);
 bind_err:
	close(mfd->fd);
 sock_err:
	free(mfd);
 alloc_mfd:
	return ret;
}

static void close_server(struct mngr_fd *mfd)
{
	struct mngr_client *client, *tclient;
	struct mngr_handler *handler, *thandler;

	shutdown(mfd->fd, SHUT_RDWR);

	mfd->listening = 0;
	pthread_join(mfd->listen_thread, NULL);

	mfd->polling = 0;
	pthread_join(mfd->poll_thread, NULL);

	pthread_mutex_lock(&mfd->client_mtx);
	list_foreach_safe(client, &mfd->client_head, list, tclient) {
		LIST_REMOVE(client, list);
		mngr_client_free_res(client);
	}
	pthread_mutex_unlock(&mfd->client_mtx);

	pthread_mutex_lock(&mfd->handler_mtx);
	list_foreach_safe(handler, &mfd->handler_head, list, thandler) {
		LIST_REMOVE(handler, list);
		free(handler);
	}
	pthread_mutex_unlock(&mfd->handler_mtx);

	unlink(mfd->addr.sun_path);
	close(mfd->fd);

	free(mfd);
}

static int connect_to_server(const char *name)
{
	struct mngr_fd *mfd;
	int ret;
	DIR *dir;
	char *s_name = NULL, *p = NULL;
	struct dirent *entry;

	dir = opendir("/run/acrn/mngr");
	if (!dir) {
		printf("%s: Failed to open directory /run/acrn/mngr\n", __func__);
		return -1;
	}

	while ((entry = readdir(dir))) {
		p = strchr(entry->d_name, '.');
		if (!p || p == entry->d_name)
			continue;
		else
			ret = p - entry->d_name;

		if (!strncmp(entry->d_name, name, ret)) {
			s_name = entry->d_name;
			break;
		}
	}

	if (!s_name) {
		printf("%s: Can't find %s\n", __func__, name);
		closedir(dir);
		return -1;
	}

	mfd = calloc(1, sizeof(*mfd));
	if (!mfd) {
		perror("Alloc struct mngr_fd");
		ret = errno;
		goto alloc_mfd;
	}

	mfd->fd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (mfd->fd < 0) {
		printf("%s %d\n", __FUNCTION__, __LINE__);
		ret = -1;
		goto sock_err;
	}

	mfd->addr.sun_family = AF_UNIX;
	ret = snprintf(mfd->addr.sun_path, sizeof(mfd->addr.sun_path),
		 "/run/acrn/mngr/%s", s_name);
	if (ret >= sizeof(mfd->addr.sun_path))
		printf("WARN: %s is truncated\n", s_name);

	ret =
	    connect(mfd->fd, (struct sockaddr *)&mfd->addr, sizeof(mfd->addr));
	if (ret < 0) {
		printf("%s %d\n", __FUNCTION__, __LINE__);
		goto connect_err;
	}

	mfd->desc = mfd->fd;
	/* add this to mngr_fd_head */
	pthread_mutex_lock(&mngr_fd_mtx);
	LIST_INSERT_HEAD(&mngr_fd_head, mfd, list);
	pthread_mutex_unlock(&mngr_fd_mtx);

	closedir(dir);
	return mfd->desc;

 connect_err:
	close(mfd->fd);
 sock_err:
	free(mfd);
 alloc_mfd:
	closedir(dir);
	return ret;
}

static void close_client(struct mngr_fd *mfd)
{
	close(mfd->fd);
	free(mfd);
}

int mngr_open_un(const char *name, int flags)
{
	check_dir(ACRN_DM_BASE_PATH, CHK_ONLY);
	check_dir(ACRN_DM_SOCK_PATH, CHK_ONLY);

	if (!name) {
		printf("%s: No socket name configured\n", __func__);
		return -1;
	}

	switch (flags) {
	case MNGR_SERVER:
		return create_new_server(name);
	case MNGR_CLIENT:
		return connect_to_server(name);
	default:
		printf("%s: Unknow flag %d\n", __func__, flags);
	}

	return -1;
}

void mngr_close(int val)
{
	struct mngr_fd *mfd;

	mfd = desc_to_mfd(val);
	if (!mfd) {
		printf("%s: No mngr_fd binded to fd %d\n", __func__, val);
		return;
	}

	pthread_mutex_lock(&mngr_fd_mtx);
	LIST_REMOVE(mfd, list);
	pthread_mutex_unlock(&mngr_fd_mtx);

	switch (mfd->type) {
	case MNGR_SERVER:
		close_server(mfd);
		break;
	case MNGR_CLIENT:
		close_client(mfd);
		break;
	default:
		printf("%s: Unknown mfd type %d\n", __func__, mfd->type);
	}

}

int mngr_add_handler(int server_fd, unsigned id,
		     void (*cb) (struct mngr_msg * msg, int client_fd,
				 void *param), void *param)
{
	struct mngr_fd *mfd;
	struct mngr_handler *handler;

	mfd = desc_to_mfd(server_fd);
	if (!mfd) {
		printf("%s: No mngr_fd binded to fd %d\n", __func__, server_fd);
		return -1;
	}

	handler = calloc(1, sizeof(*handler));
	if (!handler) {
		printf("%s: Failed to alloc mem for handler\n", __func__);
		return -1;
	}

	handler->id = id;
	handler->cb = cb;
	handler->priv = param;

	pthread_mutex_lock(&mfd->handler_mtx);
	LIST_INSERT_HEAD(&mfd->handler_head, handler, list);
	pthread_mutex_unlock(&mfd->handler_mtx);

	return 0;
}

int mngr_send_msg(int fd, struct mngr_msg *req, struct mngr_msg *ack,
		  unsigned timeout)
{
	int socket_fd = fd;
	fd_set rfd, wfd;
	struct timeval t;
	int ret;

	if (!req) {
		printf("%s %d\n", __FUNCTION__, __LINE__);
		return -1;
	}

	t.tv_sec = timeout;
	t.tv_usec = 0;

	FD_ZERO(&rfd);
	FD_ZERO(&wfd);
	FD_SET(socket_fd, &rfd);
	FD_SET(socket_fd, &wfd);

	if (timeout)
		select(socket_fd + 1, NULL, &wfd, NULL, &t);
	else
		select(socket_fd + 1, NULL, &wfd, NULL, NULL);

	if (!FD_ISSET(socket_fd, &wfd)) {
		printf("%s %d\n", __FUNCTION__, __LINE__);
		return -1;
	}

	ret = write(socket_fd, req, sizeof(struct mngr_msg));
	if (ret != sizeof(struct mngr_msg)) {
		printf("%s %d\n", __FUNCTION__, __LINE__);
		return -1;
	}

	if (!ack)
		return 0;

	if (timeout)
		select(socket_fd + 1, &rfd, NULL, NULL, &t);
	else
		select(socket_fd + 1, &rfd, NULL, NULL, NULL);

	if (!FD_ISSET(socket_fd, &rfd)) {
		printf("%s %d\n", __FUNCTION__, __LINE__);
		return -1;
	}

	ret = read(socket_fd, ack, sizeof(struct mngr_msg));

	return ret;
}
