/* SPDX-FileCopyrightText: 2024 - Sébastien Wilmet
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include "tepl-window-state.h"

/**
 * SECTION:window-state
 * @Title: TeplWindowState
 * @Short_description: Handle #GtkWindow state settings
 *
 * A utility for #GtkWindow. It permits to handle and remember some of its
 * state:
 * - Its size.
 * - Whether it is maximized.
 *
 * It is especially useful for the main window of an application, where it is
 * generally desirable to restore the state.
 *
 * It is assumed that #GSettings is used. When creating the first window, the
 * initial state is taken from the provided #GSettings keys. When a window is
 * closed the #GSettings are saved. When opening another window of the same
 * type, its initial state is copied from the last focused window of that type.
 */

/* The window size when it is unmaximized. */
typedef struct
{
	gint width;
	gint height;

	/* Whether the width and height are set / known. */
	guint set : 1;
} UnmaximizedSize;

typedef struct
{
	GSettings *settings;
	UnmaximizedSize unmaximized_size;
} WindowState;

#define WINDOW_STATE_KEY "tepl-window-state-key"

static WindowState *
window_state_new (GSettings *settings)
{
	WindowState *window_state;

	window_state = g_new0 (WindowState, 1);
	window_state->settings = g_object_ref (settings);

	return window_state;
}

static void
window_state_free (WindowState *window_state)
{
	if (window_state != NULL)
	{
		g_clear_object (&window_state->settings);
		g_free (window_state);
	}
}

static void
set_state (GtkWindow *window,
	   gint       width,
	   gint       height,
	   gboolean   maximized)
{
	if (width >= 0 && height >= 0)
	{
		gtk_window_set_default_size (window, width, height);
	}

	if (maximized)
	{
		gtk_window_maximize (window);
	}
}

static void
get_state (GtkWindow *window,
	   gint      *width,
	   gint      *height,
	   gboolean  *maximized)
{
	WindowState *window_state;

	*width = 0;
	*height = 0;
	*maximized = gtk_window_is_maximized (window);

	window_state = g_object_get_data (G_OBJECT (window), WINDOW_STATE_KEY);
	g_return_if_fail (window_state != NULL);

	if (*maximized)
	{
		if (window_state->unmaximized_size.set)
		{
			*width = window_state->unmaximized_size.width;
			*height = window_state->unmaximized_size.height;
		}
		else
		{
			gtk_window_get_default_size (window, width, height);
		}
	}
	else
	{
		gtk_window_get_size (window, width, height);
	}
}

static void
copy_state (GtkWindow *from_window,
	    GtkWindow *to_window)
{
	gint width = 0;
	gint height = 0;
	gboolean maximized = FALSE;

	get_state (from_window, &width, &height, &maximized);
	set_state (to_window, width, height, maximized);
}

static void
restore_from_settings (GtkWindow *window)
{
	WindowState *window_state;
	gint width;
	gint height;
	gboolean maximized;

	window_state = g_object_get_data (G_OBJECT (window), WINDOW_STATE_KEY);
	g_return_if_fail (window_state != NULL);

	width = g_settings_get_int (window_state->settings, "width");
	height = g_settings_get_int (window_state->settings, "height");
	maximized = g_settings_get_boolean (window_state->settings, "maximized");

	set_state (window, width, height, maximized);
}

static void
save_settings (GtkWindow *window)
{
	WindowState *window_state;
	gint width = 0;
	gint height = 0;
	gboolean maximized = FALSE;

	window_state = g_object_get_data (G_OBJECT (window), WINDOW_STATE_KEY);
	g_return_if_fail (window_state != NULL);

	get_state (window, &width, &height, &maximized);

	g_settings_set_int (window_state->settings, "width", width);
	g_settings_set_int (window_state->settings, "height", height);
	g_settings_set_boolean (window_state->settings, "maximized", maximized);
}

static gboolean
delete_event_cb (GtkWidget *widget,
		 GdkEvent  *event,
		 gpointer   user_data)
{
	GtkWindow *window = GTK_WINDOW (widget);

	save_settings (window);

	return GDK_EVENT_PROPAGATE;
}

static void
size_allocate_cb (GtkWidget    *widget,
		  GdkRectangle *allocation,
		  gpointer      user_data)
{
	GtkWindow *window = GTK_WINDOW (widget);

	if (!gtk_window_is_maximized (window))
	{
		WindowState *window_state;

		window_state = g_object_get_data (G_OBJECT (window), WINDOW_STATE_KEY);
		g_return_if_fail (window_state != NULL);

		gtk_window_get_size (window,
				     &window_state->unmaximized_size.width,
				     &window_state->unmaximized_size.height);

		window_state->unmaximized_size.set = TRUE;
	}
}

static void
setup_state (GtkWindow *window,
	     GType      window_type)
{
	GtkApplication *app;
	GList *windows_list;
	GList *l;
	GtkWindow *from_window = NULL;

	app = GTK_APPLICATION (g_application_get_default ());
	windows_list = gtk_application_get_windows (app);

	for (l = windows_list; l != NULL; l = l->next)
	{
		GtkWindow *cur_window = GTK_WINDOW (l->data);

		if (cur_window != window &&
		    G_TYPE_CHECK_INSTANCE_TYPE (cur_window, window_type))
		{
			from_window = cur_window;
			break;
		}
	}

	if (from_window != NULL)
	{
		copy_state (from_window, window);
	}
	else
	{
		restore_from_settings (window);
	}
}

/**
 * tepl_window_state_init:
 * @window: a #GtkWindow.
 * @settings: a #GSettings object.
 * @window_type: the type of #GtkWindow.
 *
 * When creating a new #GtkWindow, you can call this function to initialize some
 * of its state. Then things will be taken care of to remember the state.
 *
 * The provided #GSettings object must have at least these keys:
 * - `"maximized"` of type boolean.
 * - `"width"` of type integer.
 * - `"height"` of type integer.
 *
 * The @window_type (for example a subclass of #GtkApplicationWindow for the
 * main window) permits to use this utility for different types of windows. Each
 * window type will need its own set of #GSettings keys (in a different
 * sub-schema because the key names must be the same).
 *
 * A good place to call this function is in the GObject `init()` function.
 *
 * Since: 6.12
 */
void
tepl_window_state_init (GtkWindow *window,
			GSettings *settings,
			GType      window_type)
{
	WindowState *window_state;

	g_return_if_fail (GTK_IS_WINDOW (window));
	g_return_if_fail (G_IS_SETTINGS (settings));
	g_return_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (window, window_type));

	window_state = g_object_get_data (G_OBJECT (window), WINDOW_STATE_KEY);
	g_return_if_fail (window_state == NULL);

	window_state = window_state_new (settings);

	g_object_set_data_full (G_OBJECT (window),
				WINDOW_STATE_KEY,
				window_state,
				(GDestroyNotify) window_state_free);

	g_signal_connect (window,
			  "delete-event",
			  G_CALLBACK (delete_event_cb),
			  NULL);

	g_signal_connect (window,
			  "size-allocate",
			  G_CALLBACK (size_allocate_cb),
			  NULL);

	setup_state (window, window_type);
}
