/* SPDX-FileCopyrightText: 2005 - Paolo Borelli
 * SPDX-FileCopyrightText: 2025 - Sébastien Wilmet
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "gedit-window-status.h"
#include <glib/gi18n.h>
#include "gedit-app-private.h"
#include "gedit-settings.h"

/* An internal class all about GeditWindow and its statusbar.
 * To offload some work that was done in GeditWindow, to avoid a god class.
 */

struct _GeditWindowStatusPrivate
{
	/* Weak ref */
	GeditWindow *window;

	TeplStatusbar *statusbar;
	TeplOverwriteIndicator *overwrite_indicator;
	TeplLineColumnIndicator *line_column_indicator;
	TeplStatusMenuButton *tab_width_button;

	TeplStatusMenuButton *language_button;
	GtkWidget *language_popover;
	TeplLanguageChooserWidget *language_chooser_widget;
	guint language_timeout_id;

	TeplSignalGroup *view_signal_group;
	TeplSignalGroup *buffer_signal_group;

	guint bracket_match_message_context_id;
};

G_DEFINE_TYPE_WITH_PRIVATE (GeditWindowStatus, _gedit_window_status, G_TYPE_OBJECT)

static void
update_whole_statusbar_visibility (GeditWindowStatus *self)
{
	GeditSettings *settings;
	GSettings *ui_settings;
	gboolean visible;

	if (self->priv->window == NULL)
	{
		return;
	}

	if (_gedit_window_is_fullscreen (self->priv->window))
	{
		gtk_widget_hide (GTK_WIDGET (self->priv->statusbar));
		return;
	}

	settings = _gedit_settings_get_singleton ();
	ui_settings = _gedit_settings_peek_ui_settings (settings);

	visible = g_settings_get_boolean (ui_settings, GEDIT_SETTINGS_STATUSBAR_VISIBLE);
	gtk_widget_set_visible (GTK_WIDGET (self->priv->statusbar), visible);
}

static void
statusbar_visible_setting_changed_cb (GSettings   *ui_settings,
				      const gchar *key,
				      gpointer     user_data)
{
	GeditWindowStatus *self = GEDIT_WINDOW_STATUS (user_data);
	update_whole_statusbar_visibility (self);
}

static gboolean
window_state_event_cb (GtkWidget *window,
		       GdkEvent  *event,
		       gpointer   user_data)
{
	GeditWindowStatus *self = GEDIT_WINDOW_STATUS (user_data);
	GdkEventWindowState *event_window_state = (GdkEventWindowState *) event;

	if (event_window_state->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
	{
		update_whole_statusbar_visibility (self);
	}

	return GDK_EVENT_PROPAGATE;
}

static void
bracket_matched_cb (GtkSourceBuffer           *buffer,
		    GtkTextIter               *iter,
		    GtkSourceBracketMatchType  state,
		    gpointer                   user_data)
{
	GeditWindowStatus *self = GEDIT_WINDOW_STATUS (user_data);
	gchar *msg;

	if (self->priv->window == NULL)
	{
		return;
	}

	msg = gtk_source_utils_get_bracket_matched_message (iter, state);

	if (msg != NULL)
	{
		tepl_statusbar_flash_message (self->priv->statusbar,
					      self->priv->bracket_match_message_context_id,
					      msg);
		g_free (msg);
	}
	else
	{
		gtk_statusbar_remove_all (GTK_STATUSBAR (self->priv->statusbar),
					  self->priv->bracket_match_message_context_id);
	}
}

static void
tab_width_changed (GeditWindowStatus *self,
		   GtkSourceView     *view)
{
	if (view != NULL)
	{
		guint new_tab_width;
		gchar *label;

		new_tab_width = gtk_source_view_get_tab_width (view);

		label = g_strdup_printf (_("Tab Width: %u"), new_tab_width);
		tepl_status_menu_button_set_label_text (self->priv->tab_width_button, label);
		g_free (label);
	}

	gtk_widget_set_visible (GTK_WIDGET (self->priv->tab_width_button),
				view != NULL);
}

static void
tab_width_notify_cb (GtkSourceView *view,
		     GParamSpec    *pspec,
		     gpointer       user_data)
{
	GeditWindowStatus *self = GEDIT_WINDOW_STATUS (user_data);
	tab_width_changed (self, view);
}

static void
buffer_language_changed (GeditWindowStatus *self,
			 GtkSourceBuffer   *buffer)
{
	if (buffer != NULL)
	{
		GtkSourceLanguage *new_language;
		const gchar *label_text;

		new_language = gtk_source_buffer_get_language (buffer);

		if (new_language != NULL)
		{
			label_text = gtk_source_language_get_name (new_language);
		}
		else
		{
			label_text = _("Plain Text");
		}

		tepl_status_menu_button_set_label_text (self->priv->language_button, label_text);
	}

	gtk_widget_set_visible (GTK_WIDGET (self->priv->language_button),
				buffer != NULL);
}

static void
buffer_language_notify_cb (GtkSourceBuffer   *buffer,
			   GParamSpec        *pspec,
			   GeditWindowStatus *self)
{
	buffer_language_changed (self, buffer);
}

static void
active_tab_changed (GeditWindowStatus *self)
{
	GeditView *active_view;
	GeditDocument *active_doc;

	tepl_signal_group_clear (&self->priv->view_signal_group);
	tepl_signal_group_clear (&self->priv->buffer_signal_group);

	if (self->priv->window == NULL)
	{
		return;
	}

	active_view = gedit_window_get_active_view (self->priv->window);
	active_doc = gedit_window_get_active_document (self->priv->window);

	tepl_overwrite_indicator_set_view (self->priv->overwrite_indicator,
					   GTK_TEXT_VIEW (active_view));
	tepl_line_column_indicator_set_view (self->priv->line_column_indicator,
					     TEPL_VIEW (active_view));
	tab_width_changed (self, GTK_SOURCE_VIEW (active_view));
	buffer_language_changed (self, GTK_SOURCE_BUFFER (active_doc));

	if (active_view != NULL)
	{
		self->priv->view_signal_group = tepl_signal_group_new (G_OBJECT (active_view));

		tepl_signal_group_add (self->priv->view_signal_group,
				       g_signal_connect (active_view,
							 "notify::tab-width",
							 G_CALLBACK (tab_width_notify_cb),
							 self));
	}

	if (active_doc != NULL)
	{
		self->priv->buffer_signal_group = tepl_signal_group_new (G_OBJECT (active_doc));

		tepl_signal_group_add (self->priv->buffer_signal_group,
				       g_signal_connect (active_doc,
							 "bracket-matched",
							 G_CALLBACK (bracket_matched_cb),
							 self));

		tepl_signal_group_add (self->priv->buffer_signal_group,
				       g_signal_connect (active_doc,
							 "notify::language",
							 G_CALLBACK (buffer_language_notify_cb),
							 self));
	}
}

static void
active_tab_changed_cb (GeditWindow *window,
		       gpointer     user_data)
{
	GeditWindowStatus *self = GEDIT_WINDOW_STATUS (user_data);
	active_tab_changed (self);
}

static void
add_overwrite_indicator (GeditWindowStatus *self)
{
	g_assert (self->priv->overwrite_indicator == NULL);
	self->priv->overwrite_indicator = tepl_overwrite_indicator_new ();
	g_object_ref_sink (self->priv->overwrite_indicator);

	gtk_widget_show (GTK_WIDGET (self->priv->overwrite_indicator));
	gtk_box_pack_end (GTK_BOX (self->priv->statusbar),
			  GTK_WIDGET (self->priv->overwrite_indicator),
			  FALSE, FALSE, 0);
}

static void
add_line_column_indicator (GeditWindowStatus *self)
{
	g_assert (self->priv->line_column_indicator == NULL);
	self->priv->line_column_indicator = tepl_line_column_indicator_new ();
	g_object_ref_sink (self->priv->line_column_indicator);

	gtk_widget_show (GTK_WIDGET (self->priv->line_column_indicator));
	gtk_box_pack_end (GTK_BOX (self->priv->statusbar),
			  GTK_WIDGET (self->priv->line_column_indicator),
			  FALSE, FALSE, 0);
}

static void
add_tab_width_button (GeditWindowStatus *self)
{
	g_assert (self->priv->tab_width_button == NULL);
	self->priv->tab_width_button = tepl_status_menu_button_new ();
	g_object_ref_sink (self->priv->tab_width_button);

	gtk_box_pack_end (GTK_BOX (self->priv->statusbar),
			  GTK_WIDGET (self->priv->tab_width_button),
			  FALSE, FALSE, 0);
	gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (self->priv->tab_width_button),
					_gedit_app_get_tab_width_menu (GEDIT_APP (g_application_get_default ())));
}

static void
language_activated_cb (TeplLanguageChooser *language_chooser,
		       GtkSourceLanguage   *language,
		       GeditWindowStatus   *self)
{
	GeditDocument *active_document;

	if (self->priv->window == NULL)
	{
		return;
	}

	active_document = gedit_window_get_active_document (self->priv->window);
	if (active_document != NULL)
	{
		gedit_document_set_language (active_document, language);
	}

	gtk_widget_hide (self->priv->language_popover);
}

static void
select_language_from_active_document (GeditWindowStatus *self)
{
	GeditDocument *active_document;

	if (self->priv->window == NULL ||
	    self->priv->language_chooser_widget == NULL)
	{
		return;
	}

	active_document = gedit_window_get_active_document (self->priv->window);
	if (active_document != NULL)
	{
		GtkSourceLanguage *language;

		language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (active_document));
		tepl_language_chooser_select_language (TEPL_LANGUAGE_CHOOSER (self->priv->language_chooser_widget),
						       language);
	}
}

/* Small hack. Scrolling to the selected language row didn't work. It works out
 * of the box with TeplLanguageChooserDialog but not with the popover, and I
 * don't know why. Scrolling with GtkListBox is quite complicated. A proper fix
 * would be to connect to the right signal (which one? it's like a maze), or
 * just stick with GtkTreeView for next times.
 */
static gboolean
language_timeout_cb (gpointer user_data)
{
	GeditWindowStatus *self = GEDIT_WINDOW_STATUS (user_data);

	select_language_from_active_document (self);

	self->priv->language_timeout_id = 0;
	return G_SOURCE_REMOVE;
}

static void
language_button_toggled_cb (TeplStatusMenuButton *language_button,
			    gpointer              user_data)
{
	GeditWindowStatus *self = GEDIT_WINDOW_STATUS (user_data);

	if (self->priv->window == NULL)
	{
		return;
	}

	if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (language_button)))
	{
		return;
	}

	if (self->priv->language_chooser_widget != NULL)
	{
		gtk_widget_destroy (GTK_WIDGET (self->priv->language_chooser_widget));
		g_clear_object (&self->priv->language_chooser_widget);
	}

	self->priv->language_chooser_widget = tepl_language_chooser_widget_new ();
	g_object_ref_sink (self->priv->language_chooser_widget);

	g_signal_connect_object (self->priv->language_chooser_widget,
				 "language-activated",
				 G_CALLBACK (language_activated_cb),
				 self,
				 G_CONNECT_DEFAULT);

	gtk_widget_show (GTK_WIDGET (self->priv->language_chooser_widget));
	gtk_container_add (GTK_CONTAINER (self->priv->language_popover),
			   GTK_WIDGET (self->priv->language_chooser_widget));
	gtk_widget_grab_focus (self->priv->language_popover);

	g_clear_handle_id (&self->priv->language_timeout_id, g_source_remove);
	self->priv->language_timeout_id = g_timeout_add (1, language_timeout_cb, self);
}

static void
add_language_button (GeditWindowStatus *self)
{
	g_assert (self->priv->language_button == NULL);
	self->priv->language_button = tepl_status_menu_button_new ();
	g_object_ref_sink (self->priv->language_button);

	gtk_widget_set_margin_end (GTK_WIDGET (self->priv->language_button), 3);
	gtk_box_pack_end (GTK_BOX (self->priv->statusbar),
			  GTK_WIDGET (self->priv->language_button),
			  FALSE, FALSE, 0);

	g_assert (self->priv->language_popover == NULL);
	self->priv->language_popover = gtk_popover_new (GTK_WIDGET (self->priv->language_button));
	g_object_ref_sink (self->priv->language_popover);

	gtk_menu_button_set_popover (GTK_MENU_BUTTON (self->priv->language_button),
	                             self->priv->language_popover);

	g_signal_connect_object (self->priv->language_button,
				 "toggled",
				 G_CALLBACK (language_button_toggled_cb),
				 self,
				 G_CONNECT_DEFAULT);
}

static void
setup_amtk_application_window (GeditWindowStatus *self)
{
	AmtkApplicationWindow *amtk_window;

	amtk_window = amtk_application_window_get_from_gtk_application_window (GTK_APPLICATION_WINDOW (self->priv->window));
	amtk_application_window_set_statusbar (amtk_window, GTK_STATUSBAR (self->priv->statusbar));
}

static void
set_window (GeditWindowStatus *self,
	    GeditWindow       *window)
{
	GeditSettings *settings;
	GSettings *ui_settings;

	g_assert (self->priv->window == NULL);
	g_set_weak_pointer (&self->priv->window, window);

	setup_amtk_application_window (self);

	add_overwrite_indicator (self);
	add_line_column_indicator (self);
	add_tab_width_button (self);
	add_language_button (self);

	settings = _gedit_settings_get_singleton ();
	ui_settings = _gedit_settings_peek_ui_settings (settings);

	g_signal_connect_object (ui_settings,
				 "changed::" GEDIT_SETTINGS_STATUSBAR_VISIBLE,
				 G_CALLBACK (statusbar_visible_setting_changed_cb),
				 self,
				 G_CONNECT_DEFAULT);

	g_signal_connect_object (window,
				 "window-state-event",
				 G_CALLBACK (window_state_event_cb),
				 self,
				 G_CONNECT_AFTER);

	g_signal_connect_object (window,
				 "active-tab-changed",
				 G_CALLBACK (active_tab_changed_cb),
				 self,
				 G_CONNECT_DEFAULT);

	update_whole_statusbar_visibility (self);
	active_tab_changed (self);
}

static void
_gedit_window_status_dispose (GObject *object)
{
	GeditWindowStatus *self = GEDIT_WINDOW_STATUS (object);

	g_clear_weak_pointer (&self->priv->window);

	g_clear_object (&self->priv->statusbar);
	g_clear_object (&self->priv->overwrite_indicator);
	g_clear_object (&self->priv->line_column_indicator);
	g_clear_object (&self->priv->tab_width_button);
	g_clear_object (&self->priv->language_button);
	g_clear_object (&self->priv->language_popover);
	g_clear_object (&self->priv->language_chooser_widget);

	tepl_signal_group_clear (&self->priv->view_signal_group);
	tepl_signal_group_clear (&self->priv->buffer_signal_group);

	g_clear_handle_id (&self->priv->language_timeout_id, g_source_remove);

	G_OBJECT_CLASS (_gedit_window_status_parent_class)->dispose (object);
}

static void
_gedit_window_status_class_init (GeditWindowStatusClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->dispose = _gedit_window_status_dispose;
}

static void
_gedit_window_status_init (GeditWindowStatus *self)
{
	self->priv = _gedit_window_status_get_instance_private (self);

	self->priv->statusbar = tepl_statusbar_new ();
	g_object_ref_sink (self->priv->statusbar);

	self->priv->bracket_match_message_context_id =
		gtk_statusbar_get_context_id (GTK_STATUSBAR (self->priv->statusbar),
					      "bracket_match_message");
}

GeditWindowStatus *
_gedit_window_status_new (GeditWindow *window)
{
	GeditWindowStatus *self;

	g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL);

	self = g_object_new (GEDIT_TYPE_WINDOW_STATUS, NULL);

	set_window (self, window);

	return self;
}
