/*
 * Copyright (C)2021-2022 Intel Corporation.
 * SPDX-License-Identifier: BSD-3-Clause
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <libgen.h>
#include "uart.h"
#include "uart_channel.h"
#include "command.h"
#include "socket.h"
#include "command_handler.h"
#include "log.h"
#include "config.h"

bool system_shutdown_flag;
bool get_system_shutdown_flag(void)
{
	return system_shutdown_flag;
}

bool user_vm_reboot_flag;
bool system_reboot_flag;
bool system_reboot_request_flag;
bool get_vm_reboot_flag(void)
{
	return user_vm_reboot_flag | system_reboot_flag;
}

/**
 * @brief check whether all acrn-dm instance have been exit or not
 *
 * @return true all acrn-dm instance have been exit
 * @return false at least one acrn-dm exist
 */
static bool wait_post_vms_shutdown(void)
{
	FILE *fp = NULL;
	char command[64], buf[8];
	char *endptr, *ret_str;
	long val;
	int check_time = SHUTDOWN_TIMEOUT/5;
	bool all_done = false;

	snprintf(command, sizeof(command), "pgrep -u root -f acrn-dm | wc -l");
	do {
		fp = popen(command, "r");
		ret_str = fgets(buf, sizeof(buf), fp);
		if (ret_str == NULL)
			LOG_WRITE("Failed to check acrn-dm process\n");
		val = strtol(buf, &endptr, 10) - 1;
		if (val == 0) {
			all_done = true;
			pclose(fp);
			break;
		}
		check_time--;
		LOG_PRINTF("Wait post launched VMs shutdown check_time:%d, Running VM num:%ld\n",
				check_time, val);
		pclose(fp);
		sleep(5);
	} while (check_time > 0);
	return all_done;
}
static void start_system_reboot(void)
{
	static bool platform_reboot;

	if (is_uart_channel_connection_list_empty(channel) && (!platform_reboot)) {
		platform_reboot = true;
		LOG_WRITE("UART connection list is empty, will trigger system reboot\n");
		close_socket(sock_server);
		stop_listen_uart_channel_dev(channel);
		if (wait_post_vms_shutdown()) {
			LOG_WRITE("Service VM starts to reboot.\n");
			system_reboot_flag = true;
		} else {
			LOG_WRITE("Some User VMs failed to power off, cancelled the platform reboot process.\n");
		}
	}
}
static void start_system_shutdown(void)
{
	static bool platform_shutdown;

	if (is_uart_channel_connection_list_empty(channel) && (!platform_shutdown)) {
		platform_shutdown = true;
		LOG_WRITE("UART connection list is empty, will trigger shutdown system\n");
		close_socket(sock_server);
		stop_listen_uart_channel_dev(channel);
		if (wait_post_vms_shutdown()) {
			LOG_WRITE("Service VM starts to power off.\n");
			system_shutdown_flag = true;
		} else {
			LOG_WRITE("Some User VMs failed to power off, cancelled the platform shutdown process.\n");
		}
	}
}

static int send_socket_ack(void *arg, int fd, char *ack)
{
	int ret = 0;
	struct socket_dev *sock = (struct socket_dev *)arg;
	struct socket_client *client = NULL;

	client = find_socket_client(sock, fd);
	if (client == NULL)
		return -1;

	LOG_PRINTF("Receive shutdown request from unix socket, fd=%d\n", client->fd);
	memset(client->buf, 0, CLIENT_BUF_LEN);
	memcpy(client->buf, ack, strlen(ack));
	client->len = strlen(ack);
	ret = write_socket_char(client);
	LOG_PRINTF("Send acked message to unix socket, message=%s\n", ack);
	return ret;
}
int socket_req_shutdown_service_vm_handler(void *arg, int fd)
{
	int ret;

	usleep(LISTEN_INTERVAL + SECOND_TO_US);
	ret = send_socket_ack(arg, fd, ACK_REQ_SYS_SHUTDOWN);
	if (ret < 0)
		return 0;
	start_all_uart_channel_dev_resend(channel, POWEROFF_CMD, VM_SHUTDOWN_RETRY_TIMES);
	notify_all_connected_uart_channel_dev(channel, POWEROFF_CMD);
	start_system_shutdown();
	return 0;
}
static int req_user_vm_shutdown_reboot(void *arg, int fd, char *msg, char *ack_msg)
{
	int ret;
	struct channel_dev *c_dev = NULL;
	struct socket_dev *sock = (struct socket_dev *)arg;
	struct socket_client *client = NULL;

	usleep(LISTEN_INTERVAL + SECOND_TO_US);
	client = find_socket_client(sock, fd);
	if (client == NULL)
		return -1;

	c_dev = find_uart_channel_dev_by_name(channel, client->name);
	if (c_dev == NULL) {
		(void) send_socket_ack(arg, fd, USER_VM_DISCONNECT);
		LOG_PRINTF("Failed to fail to find uart device to communicate with user VM (%s)\n",
					client->name);
		return 0;
	}
	ret = send_socket_ack(arg, fd, ack_msg);
	if (ret < 0) {
		LOG_WRITE("Failed to send ACK by socket\n");
		return 0;
	}
	LOG_PRINTF("Foward (%s) to user VM (%s) by UART\n", msg, c_dev->name);
	start_uart_channel_dev_resend(c_dev, msg, MIN_RESEND_TIME);
	ret = send_message_by_uart(c_dev->uart_device, msg, strlen(msg));
	if (ret < 0)
		LOG_PRINTF("Failed to foward (%s) to user VM by UART\n", msg);
	return ret;
}
int socket_req_user_vm_shutdown_handler(void *arg, int fd)
{
	return req_user_vm_shutdown_reboot(arg, fd, USER_VM_SHUTDOWN, ACK_REQ_USER_VM_SHUTDOWN);
}
int socket_req_user_vm_reboot_handler(void *arg, int fd)
{
	return req_user_vm_shutdown_reboot(arg, fd, USER_VM_REBOOT, ACK_REQ_USER_VM_REBOOT);
}

int req_system_shutdown_reboot(void *arg, int fd, char *msg, char *ack_msg)
{
	int ret;
	struct channel_dev *c_dev = NULL;

	usleep(LISTEN_INTERVAL + SECOND_TO_US);
	c_dev = (struct channel_dev *)LIST_FIRST(&channel->tty_conn_head);
	if (c_dev == NULL) {
		(void) send_socket_ack(arg, fd, USER_VM_DISCONNECT);
		LOG_WRITE("User VM is disconnect\n");
		return 0;
	}

	ret = send_socket_ack(arg, fd, ack_msg);
	if (ret < 0) {
		LOG_WRITE("Failed to send ACK by socket\n");
		return 0;
	}
	LOG_PRINTF("Foward (%s) req to service VM by UART\n", msg);
	start_uart_channel_dev_resend(c_dev, msg, MIN_RESEND_TIME);
	ret = send_message_by_uart(c_dev->uart_device, msg, strlen(msg));
	if (ret < 0)
		LOG_PRINTF("Failed to foward (%s) to service VM by UART\n", msg);
	return ret;
}

int socket_req_system_reboot_user_vm_handler(void *arg, int fd)
{
	return req_system_shutdown_reboot(arg, fd, REQ_SYS_REBOOT, ACK_REQ_SYS_REBOOT);
	}

int socket_req_system_shutdown_user_vm_handler(void *arg, int fd)
{
	return req_system_shutdown_reboot(arg, fd, REQ_SYS_SHUTDOWN, ACK_REQ_SYS_SHUTDOWN);
}

static int is_allowed_s5_channel_dev(struct life_mngr_config *conf, struct channel_dev *c_dev)
{
	return strncmp(get_allow_s5_config(conf), get_uart_dev_path(c_dev->uart_device),
					TTY_PATH_MAX);
}

static int is_allowed_sysreboot_channel_dev(struct life_mngr_config *conf, struct channel_dev *c_dev)
{
	return strncmp(get_allow_sysreboot_config(conf), get_uart_dev_path(c_dev->uart_device),
					TTY_PATH_MAX);
}

/**
 * @brief The handler of sync command of lifecycle manager in service VM
 *
 * @param arg uart channel device instance
 * @param fd the file directory of the uart which receives message
 * @return indicate this command is handled successful or not
 */
int sync_cmd_handler(void *arg, int fd)
{
	struct channel_dev *c_dev = NULL;
	struct uart_channel *c = (struct uart_channel *)arg;

	c_dev = find_uart_channel_dev(c, fd);
	if (c_dev == NULL)
		return 0;

	(void)send_message_by_uart(c_dev->uart_device, ACK_SYNC, strlen(ACK_SYNC));
	LOG_PRINTF("Receive sync message from user VM (%s), start to talk.\n",
		c_dev->name);
	usleep(2 * WAIT_RECV);
	return 0;
}
int req_reboot_handler(void *arg, int fd)
{
	int ret;
	struct channel_dev *c_dev = NULL;
	struct uart_channel *c = (struct uart_channel *)arg;

	c_dev = find_uart_channel_dev(c, fd);
	if (c_dev == NULL)
		return 0;

	if (is_allowed_sysreboot_channel_dev(&life_conf, c_dev)) {
		LOG_PRINTF("The user VM (%s) is not allowed to trigger system reboot\n",
			c_dev->name);
		return 0;
	}
	LOG_PRINTF("Receive reboot request from user VM (%s)\n", c_dev->name);
	ret = send_message_by_uart(c_dev->uart_device, ACK_REQ_SYS_REBOOT,
								strlen(ACK_REQ_SYS_REBOOT));
	if (ret < 0)
		LOG_WRITE("Sending a reboot acknowledgement message to user VM failed.\n");
	system_reboot_request_flag = true;
	usleep(SECOND_TO_US);
	LOG_PRINTF("Send acked shutdown request message to user VM (%s)\n", c_dev->name);
	start_all_uart_channel_dev_resend(c, POWEROFF_CMD, VM_SHUTDOWN_RETRY_TIMES);
	notify_all_connected_uart_channel_dev(c, POWEROFF_CMD);
	usleep(2 * WAIT_RECV);
	return ret;
}

/**
 * @brief The handler of system shutdown request command of lifecycle manager in service VM
 *
 * @param arg uart channel device instance
 * @param fd the file directory of the uart which receives message
 * @return indicate this command is handled successful or not
 */
int req_shutdown_handler(void *arg, int fd)
{
	int ret;
	struct channel_dev *c_dev = NULL;
	struct uart_channel *c = (struct uart_channel *)arg;

	c_dev = find_uart_channel_dev(c, fd);
	if (c_dev == NULL)
		return 0;

	if (is_allowed_s5_channel_dev(&life_conf, c_dev)) {
		LOG_PRINTF("The user VM (%s) is not allowed to trigger system shutdown\n",
			c_dev->name);
		return 0;
	}
	LOG_PRINTF("Receive shutdown request from user VM (%s)\n", c_dev->name);
	ret = send_message_by_uart(c_dev->uart_device, ACK_REQ_SYS_SHUTDOWN,
								strlen(ACK_REQ_SYS_SHUTDOWN));
	if (ret < 0)
		LOG_WRITE("Sending a shutdown acknowledgement message to user VM failed.\n");
	usleep(SECOND_TO_US);
	LOG_PRINTF("Send acked shutdown request message to user VM (%s)\n", c_dev->name);
	start_all_uart_channel_dev_resend(c, POWEROFF_CMD, VM_SHUTDOWN_RETRY_TIMES);
	notify_all_connected_uart_channel_dev(c, POWEROFF_CMD);
	usleep(2 * WAIT_RECV);
	return ret;
}

/**
 * @brief The handler of acked poweroff command of lifecycle manager in service VM
 *
 * @param arg uart channel instance
 * @param fd the file directory of the uart which receives message
 * @return indicate this command is handled successful or not
 */
int ack_poweroff_handler(void *arg, int fd)
{
	struct channel_dev *c_dev = NULL;
	struct uart_channel *c = (struct uart_channel *)arg;

	c_dev = find_uart_channel_dev(c, fd);
	if (c_dev == NULL)
		return 0;
	LOG_PRINTF("Receive poweroff ACK from user VM (%s)\n", c_dev->name);
	stop_uart_channel_dev_resend(c_dev);
	disconnect_uart_channel_dev(c_dev, c);
	usleep(WAIT_USER_VM_POWEROFF);
	if (system_reboot_request_flag) {
		start_system_reboot();
	} else {
		start_system_shutdown();
	}
	return 0;
}
/**
 * @brief The handler of ACK timeout command of lifecycle manager in service VM
 *
 * @param arg uart channel instance
 * @param fd the file directory of the uart which receives message
 * @return indicate this command is handled successful or not
 */
int ack_timeout_handler(void *arg, int fd)
{
	struct channel_dev *c_dev = NULL;
	struct uart_channel *c = (struct uart_channel *)arg;

	c_dev = find_uart_channel_dev(c, fd);
	if (c_dev == NULL)
		return 0;
	if (strncmp(c_dev->resend_buf, POWEROFF_CMD, strlen(POWEROFF_CMD)) == 0)
		ack_poweroff_handler(arg, fd);
	else
		stop_uart_channel_dev_resend(c_dev);
	return 0;
}
static int ack_user_vm_cmd(void *arg, int fd, char *ack_msg)
{
	struct channel_dev *c_dev = NULL;
	struct uart_channel *c = (struct uart_channel *)arg;

	c_dev = find_uart_channel_dev(c, fd);
	if (c_dev == NULL)
		return 0;
	LOG_PRINTF("Receive (%s) from user VM (%s)\n", ack_msg, c_dev->name);
	stop_uart_channel_dev_resend(c_dev);
	return 0;
}
int ack_user_vm_shutdown_cmd_handler(void *arg, int fd)
{
	return ack_user_vm_cmd(arg, fd, ACK_USER_VM_SHUTDOWN);
}

int ack_user_vm_reboot_cmd_handler(void *arg, int fd)
{
	return ack_user_vm_cmd(arg, fd, ACK_USER_VM_REBOOT);
}
/**
 * @brief The handler of acked sync command of lifecycle manager in user VM
 *
 * @param arg uart channel device instance
 * @param fd the file directory of the uart which receives message
 * @return indicate this command is handled successful or not
 */
int acked_sync_handler(void *arg, int fd)
{
	struct channel_dev *c_dev = NULL;
	struct uart_channel *c = (struct uart_channel *)arg;

	c_dev = find_uart_channel_dev(c, fd);
	if (c_dev == NULL)
		return 0;
	LOG_WRITE("Receive acked sync message from service VM\n");
	return 0;
}

/**
 * @brief The handler of acked system shutdown request command of lifecycle manager in user VM
 *
 * @param arg uart channel instance
 * @param fd the file directory of the uart which receives message
 * @return indicate this command is handled successful or not
 */
int acked_req_shutdown_reboot_handler(void *arg, int fd)
{
	struct channel_dev *c_dev = NULL;
	struct uart_channel *c = (struct uart_channel *)arg;

	c_dev = find_uart_channel_dev(c, fd);
	if (c_dev == NULL)
		return 0;
	stop_uart_channel_dev_resend(c_dev);
	LOG_WRITE("Receive shutdown request ACK from service VM\n");
	return 0;
}
static int user_vm_shutdown_reboot(struct uart_channel *c, int fd, char *ack, bool reboot)
{
	int ret;
	struct channel_dev *c_dev = NULL;

	c_dev = find_uart_channel_dev(c, fd);
	if (c_dev == NULL)
		return 0;

	ret = send_message_by_uart(c_dev->uart_device, ack, strlen(ack));
	if (ret < 0) {
		LOG_PRINTF("Failed to send (%s) to service VM\n", ack);
	}
	disconnect_uart_channel_dev(c_dev, c);
	usleep(2 * WAIT_RECV);
	close_socket(sock_server);
	if (reboot) {
		user_vm_reboot_flag = true;
	} else {
		system_shutdown_flag = true;
	}
	return 0;
}
/**
 * @brief The handler of poweroff command of lifecycle manager in user VM
 *
 * @param arg uart channel device instance
 * @param fd the file directory of the uart which receives message
 * @return indicate this command is handled successful or not
 */
int poweroff_cmd_handler(void *arg, int fd)
{

	struct uart_channel *c = (struct uart_channel *)arg;
	(void) user_vm_shutdown_reboot(c, fd, ACK_POWEROFF, false);
	return 0;
}
int user_vm_shutdown_cmd_handler(void *arg, int fd)
{
	struct uart_channel *c = (struct uart_channel *)arg;
	(void) user_vm_shutdown_reboot(c, fd, ACK_USER_VM_SHUTDOWN, false);
	return 0;
}
int user_vm_reboot_cmd_handler(void *arg, int fd)
{
	struct uart_channel *c = (struct uart_channel *)arg;
	(void) user_vm_shutdown_reboot(c, fd, ACK_USER_VM_REBOOT, true);
	return 0;
}
/**
 * @brief The handler of ACK timeout command of lifecycle manager in user VM
 *
 * @param arg uart channel instance
 * @param fd the file directory of the uart which receives message
 * @return indicate this command is handled successful or not
 */
int ack_timeout_default_handler(void *arg, int fd)
{
	struct channel_dev *c_dev = NULL;
	struct uart_channel *c = (struct uart_channel *)arg;

	c_dev = find_uart_channel_dev(c, fd);
	if (c_dev == NULL)
		return 0;
	stop_uart_channel_dev_resend(c_dev);
	disconnect_uart_channel_dev(c_dev, c);
	close_socket(sock_server);
	LOG_PRINTF("Failed to receive ACK message from service VM (fd = %d)\n", fd);
	return 0;
}
