/*
 * This file is part of GtkSourceView
 *
 * Copyright 2020 Christian Hergert <chergert@redhat.com>
 *
 * GtkSourceView is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * GtkSourceView 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "config.h"

#include "gtksourceinformative-private.h"
#include "gtksourceview.h"

typedef struct
{
	GtkImage *icon;
	GtkLabel *message;
	GtkMessageType message_type;
} GtkSourceInformativePrivate;

enum {
	PROP_0,
	PROP_ICON_NAME,
	PROP_MESSAGE,
	PROP_MESSAGE_TYPE,
	N_PROPS
};

G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceInformative, gtk_source_informative, GTK_SOURCE_TYPE_ASSISTANT)

static GParamSpec *properties [N_PROPS];

const char *
gtk_source_informative_get_message (GtkSourceInformative *self)
{
	GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self);

	g_return_val_if_fail (GTK_SOURCE_IS_INFORMATIVE (self), NULL);

	return gtk_label_get_label (priv->message);
}

void
gtk_source_informative_set_message (GtkSourceInformative *self,
                                    const char           *message)
{
	GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self);

	g_return_if_fail (GTK_SOURCE_IS_INFORMATIVE (self));

	gtk_label_set_label (priv->message, message);
	g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
}

GtkMessageType
gtk_source_informative_get_message_type (GtkSourceInformative *self)
{
	GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self);

	g_return_val_if_fail (GTK_SOURCE_IS_INFORMATIVE (self), GTK_MESSAGE_OTHER);

	return priv->message_type;
}

void
gtk_source_informative_set_message_type (GtkSourceInformative *self,
                                         GtkMessageType        message_type)
{
	GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self);

	g_assert (GTK_SOURCE_IS_INFORMATIVE (self));

	priv->message_type = message_type;

	gtk_widget_remove_css_class (GTK_WIDGET (self), "error");
	gtk_widget_remove_css_class (GTK_WIDGET (self), "info");
	gtk_widget_remove_css_class (GTK_WIDGET (self), "question");
	gtk_widget_remove_css_class (GTK_WIDGET (self), "warning");
	gtk_widget_remove_css_class (GTK_WIDGET (self), "other");

	switch (priv->message_type)
	{
	case GTK_MESSAGE_INFO:
		gtk_widget_add_css_class (GTK_WIDGET (self), "info");
		break;

	case GTK_MESSAGE_WARNING:
		gtk_widget_add_css_class (GTK_WIDGET (self), "warning");
		break;

	case GTK_MESSAGE_QUESTION:
		gtk_widget_add_css_class (GTK_WIDGET (self), "question");
		break;

	case GTK_MESSAGE_ERROR:
		gtk_widget_add_css_class (GTK_WIDGET (self), "error");
		break;

	case GTK_MESSAGE_OTHER:
		gtk_widget_add_css_class (GTK_WIDGET (self), "other");
		break;

	default:
		break;
	}

	g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE_TYPE]);
}

const char *
gtk_source_informative_get_icon_name (GtkSourceInformative *self)
{
	GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self);

	g_assert (GTK_SOURCE_IS_INFORMATIVE (self));

	return gtk_image_get_icon_name (priv->icon);
}

void
gtk_source_informative_set_icon_name (GtkSourceInformative *self,
                                      const char           *icon_name)
{
	GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self);

	g_assert (GTK_SOURCE_IS_INFORMATIVE (self));

	gtk_image_set_from_icon_name (priv->icon, icon_name);
	g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ICON_NAME]);
}

static void
gtk_source_informative_get_offset (GtkSourceAssistant *assistant,
                                   int                *x_offset,
                                   int                *y_offset)
{
	GtkSourceInformative *self = GTK_SOURCE_INFORMATIVE (assistant);
	GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self);
	GtkStyleContext *style_context;
	GtkBorder margin;
	int min_width, min_baseline;

	GTK_SOURCE_ASSISTANT_CLASS (gtk_source_informative_parent_class)->get_offset (assistant, x_offset, y_offset);

	gtk_widget_measure (GTK_WIDGET (priv->icon),
			    GTK_ORIENTATION_HORIZONTAL,
			    -1,
			    &min_width,
			    NULL,
			    &min_baseline,
			    NULL);

	style_context = gtk_widget_get_style_context (GTK_WIDGET (priv->icon));
	gtk_style_context_get_margin (style_context, &margin);

	*x_offset -= min_width;
	*x_offset += margin.right;
}

static void
gtk_source_informative_get_target_location (GtkSourceAssistant *assistant,
                                            GdkRectangle       *rect)
{
	g_assert (GTK_SOURCE_IS_ASSISTANT (assistant));
	g_assert (rect != NULL);

	GTK_SOURCE_ASSISTANT_CLASS (gtk_source_informative_parent_class)->get_target_location (assistant, rect);

	/* Align to beginning of the character */
	rect->width = 0;
}

static gboolean
key_press_propagate_cb (GtkSourceInformative  *self,
                        guint                  keyval,
                        guint                  keycode,
                        GdkModifierType        modifiers,
                        GtkEventControllerKey *key)
{
	GtkWidget *parent;

	g_assert (GTK_SOURCE_IS_INFORMATIVE (self));
	g_assert (GTK_IS_EVENT_CONTROLLER_KEY (key));

	parent = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_SOURCE_TYPE_VIEW);

	if (GTK_SOURCE_IS_VIEW (parent))
	{
		return gtk_event_controller_key_forward (key, parent);
	}

	return GDK_EVENT_PROPAGATE;
}

static gboolean
key_release_propagate_cb (GtkSourceInformative  *self,
                          guint                  keyval,
                          guint                  keycode,
                          GdkModifierType        modifiers,
                          GtkEventControllerKey *key)
{
	GtkWidget *parent;

	g_assert (GTK_SOURCE_IS_INFORMATIVE (self));
	g_assert (GTK_IS_EVENT_CONTROLLER_KEY (key));

	parent = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_SOURCE_TYPE_VIEW);

	if (GTK_SOURCE_IS_VIEW (parent))
	{
		return gtk_event_controller_key_forward (key, parent);
	}

	return GDK_EVENT_PROPAGATE;
}

static void
gtk_source_informative_get_property (GObject    *object,
                                     guint       prop_id,
                                     GValue     *value,
                                     GParamSpec *pspec)
{
	GtkSourceInformative *self = GTK_SOURCE_INFORMATIVE (object);

	switch (prop_id)
	{
	case PROP_ICON_NAME:
		g_value_set_string (value, gtk_source_informative_get_icon_name (self));
		break;

	case PROP_MESSAGE:
		g_value_set_string (value, gtk_source_informative_get_message (self));
		break;

	case PROP_MESSAGE_TYPE:
		g_value_set_enum (value, gtk_source_informative_get_message_type (self));
		break;

	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

static void
gtk_source_informative_set_property (GObject      *object,
                                     guint         prop_id,
                                     const GValue *value,
                                     GParamSpec   *pspec)
{
	GtkSourceInformative *self = GTK_SOURCE_INFORMATIVE (object);

	switch (prop_id)
	{
	case PROP_ICON_NAME:
		gtk_source_informative_set_icon_name (self, g_value_get_string (value));
		break;

	case PROP_MESSAGE:
		gtk_source_informative_set_message (self, g_value_get_string (value));
		break;

	case PROP_MESSAGE_TYPE:
		gtk_source_informative_set_message_type (self, g_value_get_enum (value));
		break;

	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

static void
gtk_source_informative_class_init (GtkSourceInformativeClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
	GtkSourceAssistantClass *assistant_class = GTK_SOURCE_ASSISTANT_CLASS (klass);

	object_class->get_property = gtk_source_informative_get_property;
	object_class->set_property = gtk_source_informative_set_property;

	assistant_class->get_offset = gtk_source_informative_get_offset;
	assistant_class->get_target_location = gtk_source_informative_get_target_location;

	properties [PROP_ICON_NAME] =
		g_param_spec_string ("icon-name",
		                     "Icon Name",
		                     "Icon Name",
		                     NULL,
		                     (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	properties [PROP_MESSAGE] =
		g_param_spec_string ("message",
		                     "Message",
		                     "The message for the popover",
		                     NULL,
		                     (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	properties [PROP_MESSAGE_TYPE] =
		g_param_spec_enum ("message-type",
		                   "Message Type",
		                   "The message type for the popover",
		                   GTK_TYPE_MESSAGE_TYPE,
		                   GTK_MESSAGE_INFO,
		                   (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	g_object_class_install_properties (object_class, N_PROPS, properties);

	gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gtksourceview/ui/gtksourceinformative.ui");
	gtk_widget_class_bind_template_child_private (widget_class, GtkSourceInformative, icon);
	gtk_widget_class_bind_template_child_private (widget_class, GtkSourceInformative, message);
}

static void
gtk_source_informative_init (GtkSourceInformative *self)
{
	GtkEventController *key;

	gtk_widget_init_template (GTK_WIDGET (self));

	key = gtk_event_controller_key_new ();
	gtk_event_controller_set_propagation_phase (key, GTK_PHASE_BUBBLE);
	g_signal_connect_object (key,
	                         "key-pressed",
	                         G_CALLBACK (key_press_propagate_cb),
	                         self,
	                         G_CONNECT_SWAPPED);
	g_signal_connect_object (key,
	                         "key-released",
	                         G_CALLBACK (key_release_propagate_cb),
	                         self,
	                         G_CONNECT_SWAPPED);
	gtk_widget_add_controller (GTK_WIDGET (self), g_steal_pointer (&key));
}
