/*
 * Remmina - The GTK+ Remote Desktop Client
 * Copyright (C) 2009-2011 Vic Lee
 * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
 * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 *  In addition, as a special exception, the copyright holders give
 *  permission to link the code of portions of this program with the
 *  OpenSSL library under certain conditions as described in each
 *  individual source file, and distribute linked combinations
 *  including the two.
 *  You must obey the GNU General Public License in all respects
 *  for all of the code used other than OpenSSL. *  If you modify
 *  file(s) with this exception, you may extend this exception to your
 *  version of the file(s), but you are not obligated to do so. *  If you
 *  do not wish to do so, delete this exception statement from your
 *  version. *  If you delete this exception statement from all source
 *  files in the program, then also delete it here.
 *
 */

#include "config.h"
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#elif defined(GDK_WINDOWING_WAYLAND)
#include <gdk/gdkwayland.h>
#endif
#include "remmina_public.h"
#include "remmina/remmina_trace_calls.h"

GtkWidget*
remmina_public_create_combo_entry(const gchar *text, const gchar *def, gboolean descending)
{
	TRACE_CALL(__func__);
	GtkWidget *combo;
	gboolean found;
	gchar *buf, *ptr1, *ptr2;
	gint i;

	combo = gtk_combo_box_text_new_with_entry();
	found = FALSE;

	if (text && text[0] != '\0') {
		buf = g_strdup(text);
		ptr1 = buf;
		i = 0;
		while (ptr1 && *ptr1 != '\0') {
			ptr2 = strchr(ptr1, CHAR_DELIMITOR);
			if (ptr2)
				*ptr2++ = '\0';

			if (descending) {
				gtk_combo_box_text_prepend_text(GTK_COMBO_BOX_TEXT(combo), ptr1);
				if (!found && g_strcmp0(ptr1, def) == 0) {
					gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
					found = TRUE;
				}
			}else  {
				gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), ptr1);
				if (!found && g_strcmp0(ptr1, def) == 0) {
					gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
					found = TRUE;
				}
			}

			ptr1 = ptr2;
			i++;
		}

		g_free(buf);
	}

	if (!found && def && def[0] != '\0') {
		gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))), def);
	}

	return combo;
}

GtkWidget*
remmina_public_create_combo_text_d(const gchar *text, const gchar *def, const gchar *empty_choice)
{
	TRACE_CALL(__func__);
	GtkWidget *combo;
	GtkListStore *store;
	GtkCellRenderer *text_renderer;

	store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
	combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));

	text_renderer = gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(combo), text_renderer, TRUE);
	gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), text_renderer, "text", 1);

	remmina_public_load_combo_text_d(combo, text, def, empty_choice);

	return combo;
}

void remmina_public_load_combo_text_d(GtkWidget *combo, const gchar *text, const gchar *def, const gchar *empty_choice)
{
	TRACE_CALL(__func__);
	GtkListStore *store;
	GtkTreeIter iter;
	gint i;
	gchar *buf, *ptr1, *ptr2;

	store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combo)));
	gtk_list_store_clear(store);

	i = 0;

	if (empty_choice) {
		gtk_list_store_append(store, &iter);
		gtk_list_store_set(store, &iter, 0, "", 1, empty_choice, -1);
		gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
		i++;
	}

	if (text == NULL || text[0] == '\0')
		return;

	buf = g_strdup(text);
	ptr1 = buf;
	while (ptr1 && *ptr1 != '\0') {
		ptr2 = strchr(ptr1, CHAR_DELIMITOR);
		if (ptr2)
			*ptr2++ = '\0';

		gtk_list_store_append(store, &iter);
		gtk_list_store_set(store, &iter, 0, ptr1, 1, ptr1, -1);

		if (i == 0 || g_strcmp0(ptr1, def) == 0) {
			gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
		}

		i++;
		ptr1 = ptr2;
	}

	g_free(buf);
}

GtkWidget*
remmina_public_create_combo(gboolean use_icon)
{
	TRACE_CALL(__func__);
	GtkWidget *combo;
	GtkListStore *store;
	GtkCellRenderer *renderer;

	if (use_icon) {
		store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
	}else  {
		store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
	}
	combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
	gtk_widget_set_hexpand(combo, TRUE);

	if (use_icon) {
		renderer = gtk_cell_renderer_pixbuf_new();
		gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
		gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "icon-name", 2);
	}
	renderer = gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
	gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "text", 1);
	if (use_icon)
		g_object_set(G_OBJECT(renderer), "xpad", 5, NULL);

	return combo;
}

GtkWidget*
remmina_public_create_combo_map(const gpointer *key_value_list, const gchar *def, gboolean use_icon, const gchar *domain)
{
	TRACE_CALL(__func__);
	gint i;
	GtkWidget *combo;
	GtkListStore *store;
	GtkTreeIter iter;

	combo = remmina_public_create_combo(use_icon);
	store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combo)));

	for (i = 0; key_value_list[i]; i += (use_icon ? 3 : 2)) {
		gtk_list_store_append(store, &iter);
		gtk_list_store_set(
			store,
			&iter,
			0,
			key_value_list[i],
			1,
			key_value_list[i + 1] && ((char*)key_value_list[i + 1])[0] ?
			g_dgettext(domain, key_value_list[i + 1]) : "", -1);
		if (use_icon) {
			gtk_list_store_set(store, &iter, 2, key_value_list[i + 2], -1);
		}
		if (i == 0 || g_strcmp0(key_value_list[i], def) == 0) {
			gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i / (use_icon ? 3 : 2));
		}
	}
	return combo;
}

GtkWidget*
remmina_public_create_combo_mapint(const gpointer *key_value_list, gint def, gboolean use_icon, const gchar *domain)
{
	TRACE_CALL(__func__);
	gchar buf[20];
	g_snprintf(buf, sizeof(buf), "%i", def);
	return remmina_public_create_combo_map(key_value_list, buf, use_icon, domain);
}

void remmina_public_create_group(GtkGrid *grid, const gchar *group, gint row, gint rows, gint cols)
{
	TRACE_CALL(__func__);
	GtkWidget *widget;
	gchar *str;

	widget = gtk_label_new(NULL);
	gtk_widget_show(widget);
	gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
	gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
	str = g_markup_printf_escaped("<b>%s</b>", group);
	gtk_label_set_markup(GTK_LABEL(widget), str);
	g_free(str);
	gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 2);

	widget = gtk_label_new(NULL);
	gtk_widget_show(widget);
	gtk_grid_attach(GTK_GRID(grid), widget, 0, row + 1, 1, 1);
}

gchar*
remmina_public_combo_get_active_text(GtkComboBox *combo)
{
	TRACE_CALL(__func__);
	GtkTreeModel *model;
	GtkTreeIter iter;
	gchar *s;

	if (GTK_IS_COMBO_BOX_TEXT(combo)) {
		return gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
	}

	if (!gtk_combo_box_get_active_iter(combo, &iter))
		return NULL;

	model = gtk_combo_box_get_model(combo);
	gtk_tree_model_get(model, &iter, 0, &s, -1);

	return s;
}

#if !GTK_CHECK_VERSION(3, 22, 0)
void remmina_public_popup_position(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data)
{
	TRACE_CALL(__func__);
	GtkWidget *widget;
	gint tx, ty;
	GtkAllocation allocation;

	widget = GTK_WIDGET(user_data);
	if (gtk_widget_get_window(widget) == NULL) {
		*x = 0;
		*y = 0;
		*push_in = TRUE;
		return;
	}
	gdk_window_get_origin(gtk_widget_get_window(widget), &tx, &ty);
	gtk_widget_get_allocation(widget, &allocation);
	/* I’m unsure why the author made the check about a GdkWindow inside the
	 * widget argument. This function generally is called passing by a ToolButton
	 * which hasn’t any GdkWindow, therefore the positioning is wrong
	 * I think the gtk_widget_get_has_window() check should be removed
	 *
	 * While leaving the previous check intact I’m checking also if the provided
	 * widget is a GtkToggleToolButton and position the menu accordingly. */
	if (gtk_widget_get_has_window(widget) ||
	    g_strcmp0(gtk_widget_get_name(widget), "GtkToggleToolButton") == 0) {
		tx += allocation.x;
		ty += allocation.y;
	}

	*x = tx;
	*y = ty + allocation.height - 1;
	*push_in = TRUE;
}
#endif

gchar*
remmina_public_combine_path(const gchar *path1, const gchar *path2)
{
	TRACE_CALL(__func__);
	if (!path1 || path1[0] == '\0')
		return g_strdup(path2);
	if (path1[strlen(path1) - 1] == '/')
		return g_strdup_printf("%s%s", path1, path2);
	return g_strdup_printf("%s/%s", path1, path2);
}

gint remmina_public_open_unix_sock(const char *unixsock)
{
    struct sockaddr_un addr;
    int fd;

    if (strlen(unixsock) + 1 > sizeof(addr.sun_path)) {
		g_debug(_("Address is too long for UNIX socket_path: %s"), unixsock);
        return -1;
    }

    memset(&addr, 0, sizeof addr);
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, unixsock);

    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        g_debug(_("Creating UNIX socket failed: %s"), g_strerror(errno));
        return -1;
    }

    if (connect(fd, (struct sockaddr *)&addr, sizeof addr) < 0) {
        g_debug(_("Connecting to UNIX socket failed: %s"), g_strerror(errno));
        close(fd);
        return -1;
    }

    return fd;
}

void remmina_public_get_server_port_old(const gchar *server, gint defaultport, gchar **host, gint *port)
{
	TRACE_CALL(__func__);
	gchar *str, *ptr, *ptr2;

	str = g_strdup(server);

	if (str) {
		/* [server]:port format */
		ptr = strchr(str, '[');
		if (ptr) {
			ptr++;
			ptr2 = strchr(ptr, ']');
			if (ptr2) {
				*ptr2++ = '\0';
				if (*ptr2 == ':')
					defaultport = atoi(ptr2 + 1);
			}
			if (host)
				*host = g_strdup(ptr);
			if (port)
				*port = defaultport;
			g_free(str);
			g_debug ("(%s) - host: %s", __func__, *host);
			g_debug ("(%s) - port: %d", __func__, *port);
			return;
		}

		/* server:port format, IPv6 cannot use this format */
		ptr = strchr(str, ':');
		if (ptr) {
			ptr2 = strchr(ptr + 1, ':');
			if (ptr2 == NULL) {
				*ptr++ = '\0';
				defaultport = atoi(ptr);
			}
			/* More than one ':' means this is IPv6 address. Treat it as a whole address */
		}
	}

	if (host)
		*host = str;
	else
		g_free(str);
	if (port)
		*port = defaultport;

	g_debug ("(%s) - host: %s", __func__, *host);
	g_debug ("(%s) - port: %d", __func__, *port);
}

void remmina_public_get_server_port(const gchar *server, gint defaultport, gchar **host, gint *port)
{
	TRACE_CALL(__func__);

	const gchar *nul_terminated_server = NULL;
	if (server != NULL) {
		if(strstr(server, "ID:") != NULL) {
			g_debug ("(%s) - Using remmina_public_get_server_port_old to parse the repeater ID", __func__);
			remmina_public_get_server_port_old (server, defaultport, host, port);
			return;
		}

		GNetworkAddress *address;
		GError *err = NULL;

		nul_terminated_server = server;
		g_debug ("(%s) - Parsing server: %s, default port: %d", __func__, server, defaultport);
		address = (GNetworkAddress*)g_network_address_parse ((const gchar *)nul_terminated_server, defaultport, &err);

		if (address == NULL) {
			g_debug ("(%s) - Error converting server string: %s, with error: %s", __func__, nul_terminated_server, err->message);
			*host = NULL;
			if (err)
				g_error_free(err);
		} else {

			*host = g_strdup(g_network_address_get_hostname (address));
			*port = g_network_address_get_port (address);
			g_object_unref(address);
		}
	} else
		*host = NULL;

	if (port == 0)
		*port = defaultport;

	g_debug ("(%s) - host: %s", __func__, *host);
	g_debug ("(%s) - port: %d", __func__, *port);

	return;
}

gboolean remmina_public_get_xauth_cookie(const gchar *display, gchar **msg)
{
	TRACE_CALL(__func__);
	gchar buf[200];
	gchar *out = NULL;
	gchar *ptr;
	GError *error = NULL;
	gboolean ret;

	if (!display)
		display = gdk_display_get_name(gdk_display_get_default());

	g_snprintf(buf, sizeof(buf), "xauth list %s", display);
	ret = g_spawn_command_line_sync(buf, &out, NULL, NULL, &error);
	if (ret) {
		if ((ptr = g_strrstr(out, "MIT-MAGIC-COOKIE-1")) == NULL) {
			*msg = g_strdup_printf("xauth returns %s", out);
			ret = FALSE;
		}else  {
			ptr += 19;
			while (*ptr == ' ')
				ptr++;
			*msg = g_strndup(ptr, 32);
		}
		g_free(out);
	}else  {
		*msg = g_strdup(error->message);
	}
	return ret;
}

gint remmina_public_open_xdisplay(const gchar *disp)
{
	TRACE_CALL(__func__);
	gchar *display;
	gchar *ptr;
	gint port;
	struct sockaddr_un addr;
	gint sock = -1;

	display = g_strdup(disp);
	ptr = g_strrstr(display, ":");
	if (ptr) {
		*ptr++ = '\0';
		/* Assume you are using a local display… might need to implement remote display in the future */
		if (display[0] == '\0' || strcmp(display, "unix") == 0) {
			port = atoi(ptr);
			sock = socket(AF_UNIX, SOCK_STREAM, 0);
			if (sock >= 0) {
				memset(&addr, 0, sizeof(addr));
				addr.sun_family = AF_UNIX;
				snprintf(addr.sun_path, sizeof(addr.sun_path), X_UNIX_SOCKET, port);
				if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
					close(sock);
					sock = -1;
				}
			}
		}
	}

	g_free(display);
	return sock;
}

/* Find hardware keycode for the requested keyval */
guint16 remmina_public_get_keycode_for_keyval(GdkKeymap *keymap, guint keyval)
{
	TRACE_CALL(__func__);
	GdkKeymapKey *keys = NULL;
	gint length = 0;
	guint16 keycode = 0;

	if (gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &length)) {
		keycode = keys[0].keycode;
		g_free(keys);
	}
	return keycode;
}

/* Check if the requested keycode is a key modifier */
gboolean remmina_public_get_modifier_for_keycode(GdkKeymap *keymap, guint16 keycode)
{
	TRACE_CALL(__func__);
	if (keycode > 0) return FALSE;
#ifdef GDK_WINDOWING_X11
	return gdk_x11_keymap_key_is_modifier(keymap, keycode);
#else
	return FALSE;
#endif
}

/* Load a GtkBuilder object from a filename */
GtkBuilder* remmina_public_gtk_builder_new_from_file(gchar *filename)
{
	TRACE_CALL(__func__);
	GError *err = NULL;
	gchar *ui_path = g_strconcat(REMMINA_RUNTIME_UIDIR, G_DIR_SEPARATOR_S, filename, NULL);
	GtkBuilder *builder = gtk_builder_new();
	gtk_builder_add_from_file(builder, ui_path, &err);
	if (err != NULL) {
		g_print("Error adding build from file. Error: %s", err->message);
		g_error_free(err);
	}
	g_free(ui_path);
	return builder;
}

/* Load a GtkBuilder object from a resource */
GtkBuilder* remmina_public_gtk_builder_new_from_resource(gchar *resource)
{
	TRACE_CALL(__func__);
	GError *err = NULL;
	GtkBuilder *builder = gtk_builder_new();
	gtk_builder_add_from_resource (builder, resource, &err);
	if (err != NULL) {
		g_print("Error adding build from resource. Error: %s", err->message);
		g_error_free(err);
	}
	return builder;
}

/* Change parent container for a widget
 * If possible use this function instead of the deprecated gtk_widget_reparent */
void remmina_public_gtk_widget_reparent(GtkWidget *widget, GtkContainer *container)
{
	TRACE_CALL(__func__);
	g_object_ref(widget);
	gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(widget)), widget);
	gtk_container_add(container, widget);
	g_object_unref(widget);
}

/* Validate the inserted value for a new resolution */
gboolean remmina_public_resolution_validation_func(const gchar *new_str, gchar **error)
{
	TRACE_CALL(__func__);
	gint i;
	gint width, height;
	gboolean splitted;
	gboolean result;

	width = 0;
	height = 0;
	splitted = FALSE;
	result = TRUE;
	for (i = 0; new_str[i] != '\0'; i++) {
		if (new_str[i] == 'x') {
			if (splitted) {
				result = FALSE;
				break;
			}
			splitted = TRUE;
			continue;
		}
		if (new_str[i] < '0' || new_str[i] > '9') {
			result = FALSE;
			break;
		}
		if (splitted) {
			height = 1;
		}else  {
			width = 1;
		}
	}

	if (width == 0 || height == 0)
		result = FALSE;

	if (!result)
		*error = g_strdup(_("Please enter format 'widthxheight'."));
	return result;
}

/* Used to send desktop notifications */
void remmina_public_send_notification(const gchar *notification_id,
				      const gchar *notification_title, const gchar *notification_message)
{
	TRACE_CALL(__func__);

	g_autoptr(GNotification) n = NULL;
	gint priority = G_NOTIFICATION_PRIORITY_NORMAL;

	n = g_notification_new(notification_title);
	g_notification_set_body(n, notification_message);
	if (g_strcmp0 (notification_id, "remmina-security-trust-all-id") == 0) {
		g_debug ("remmina_public_send_notification: We got a remmina-security-trust-all-id notification");
		priority = G_NOTIFICATION_PRIORITY_HIGH;
		/** parameter 5 is the tab index for the security tab in the preferences
		 * TODO: Do not hardcode the parameter
		 * TODO: Do not hardcode implement DBus interface correctly of this won't work*/
		g_notification_set_default_action_and_target (n, "app.preferences", "i", 5);
		g_notification_add_button_with_target (n, _("Change security settings"), "app.preferences", "i", 5);
	}
#if GLIB_CHECK_VERSION(2, 42, 0)
	g_notification_set_priority(n, priority);
#endif
	g_application_send_notification(g_application_get_default(), notification_id, n);
}

/* Replaces all occurrences of search in a new copy of string by replacement. */
gchar* remmina_public_str_replace(const gchar *string, const gchar *search, const gchar *replacement)
{
	TRACE_CALL(__func__);
	gchar *str, **arr;

	g_return_val_if_fail(string != NULL, NULL);
	g_return_val_if_fail(search != NULL, NULL);

	if (replacement == NULL)
		replacement = "";

	arr = g_strsplit(string, search, -1);
	if (arr != NULL && arr[0] != NULL)
		str = g_strjoinv(replacement, arr);
	else
		str = g_strdup(string);

	g_strfreev(arr);
	return str;
}

/* Replaces all occurrences of search in a new copy of string by replacement
 * and overwrites the original string */
gchar* remmina_public_str_replace_in_place(gchar *string, const gchar *search, const gchar *replacement)
{
	TRACE_CALL(__func__);
	gchar *new_string = remmina_public_str_replace(string, search, replacement);
	string = g_strdup(new_string);
	g_free(new_string);
	return string;
}

int remmina_public_split_resolution_string(const char *resolution_string, int *w, int *h)
{
	int lw, lh;

	if (resolution_string == NULL || resolution_string[0] == 0)
		return 0;
	if (sscanf(resolution_string, "%dx%d", &lw, &lh) != 2)
		return 0;
	*w = lw;
	*h = lh;
	return 1;
}

/* Return TRUE if current gtk version library in use is greater or equal than
 * the required major.minor.micro */
gboolean remmina_gtk_check_version(guint major, guint minor, guint micro)
{
	guint rtmajor, rtminor, rtmicro;
	rtmajor = gtk_get_major_version();
	if (rtmajor > major) {
		return TRUE;
	}else if (rtmajor == major) {
		rtminor = gtk_get_minor_version();
		if (rtminor > minor) {
			return TRUE;
		}else if (rtminor == minor) {
			rtmicro = gtk_get_micro_version();
			if (rtmicro >= micro) {
				return TRUE;
			}else {
				return FALSE;
			}
		}else {
			return FALSE;
		}
	}else {
		return FALSE;
	}
}
