/*
 * Back/forward button box
 *
 * Copyright (C) 2017  Patrick McDermott
 *
 * This file is part of Marquee.
 *
 * Marquee 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 3 of the License, or
 * (at your option) any later version.
 *
 * Marquee 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 Marquee.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "back-forward-button-box.h"

#include <glib.h>
#include <gtk/gtk.h>
#include <webkit2/webkit2.h>

#include "../../web-view.h"

struct _MqBackForwardButtonBox {
	GtkToolItem  parent_instance;
	MqWebView   *web_view;
	GtkWidget   *back_button;
	GtkWidget   *forward_button;
	gint         back_items;
	GtkWidget   *back_forward_popover;
};

enum {
	PROP_WEB_VIEW = 1,
	N_PROPERTIES
};

static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,};

struct _MqBackForwardButtonBoxClass {
	GtkToolItemClass parent_class;
};

G_DEFINE_TYPE(MqBackForwardButtonBox, mq_back_forward_button_box,
	GTK_TYPE_TOOL_ITEM)

static void
back_forward_list_changed_cb(
	WebKitBackForwardList G_GNUC_UNUSED *back_forward_list,
	WebKitBackForwardListItem G_GNUC_UNUSED *item_added,
	gpointer G_GNUC_UNUSED items_removed,
	MqBackForwardButtonBox *back_forward_button_box)
{
	gtk_widget_set_sensitive(back_forward_button_box->back_button,
		webkit_web_view_can_go_back(
			WEBKIT_WEB_VIEW(back_forward_button_box->web_view)));
	gtk_widget_set_sensitive(back_forward_button_box->forward_button,
		webkit_web_view_can_go_forward(
			WEBKIT_WEB_VIEW(back_forward_button_box->web_view)));
}

static void
back_clicked_cb(GtkButton G_GNUC_UNUSED *toolbutton,
	MqBackForwardButtonBox *back_forward_button_box)
{
	webkit_web_view_go_back(
		WEBKIT_WEB_VIEW(back_forward_button_box->web_view));
}

static void
forward_clicked_cb(GtkButton G_GNUC_UNUSED *toolbutton,
	MqBackForwardButtonBox *back_forward_button_box)
{
	webkit_web_view_go_forward(
		WEBKIT_WEB_VIEW(back_forward_button_box->web_view));
}

static GtkWidget *
list_item_new(WebKitBackForwardListItem *list_item, gint type)
{
	GtkWidget *label;
	GtkWidget *icon_stack;
	GtkWidget *icon;
	GtkWidget *box;

	label = gtk_label_new(webkit_back_forward_list_item_get_title(
			list_item));
	gtk_widget_set_halign(label, GTK_ALIGN_START);

	icon_stack = gtk_stack_new();
	switch (type) {
		case 0:
			icon = gtk_radio_button_new(NULL);
			gtk_stack_add_named(GTK_STACK(icon_stack), icon,
				"current");
			gtk_widget_set_can_focus(icon, FALSE);
			gtk_stack_add_named(GTK_STACK(icon_stack),
				gtk_image_new_from_icon_name("go-previous",
					GTK_ICON_SIZE_BUTTON), "back");
			break;
		case -1:
			gtk_stack_add_named(GTK_STACK(icon_stack),
				gtk_image_new_from_icon_name("go-previous",
					GTK_ICON_SIZE_BUTTON), "back");
			gtk_stack_add_named(GTK_STACK(icon_stack),
				gtk_radio_button_new(NULL), "current");
			break;
		case 1:
			gtk_stack_add_named(GTK_STACK(icon_stack),
				gtk_image_new_from_icon_name("go-next",
					GTK_ICON_SIZE_BUTTON), "forward");
			gtk_stack_add_named(GTK_STACK(icon_stack),
				gtk_radio_button_new(NULL), "current");
			break;
	}

	box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
	gtk_box_pack_start(GTK_BOX(box), icon_stack, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
	gtk_widget_set_tooltip_text(box,
		webkit_back_forward_list_item_get_uri(list_item));

	return box;
}

static void
list_box_row_activated_cb(GtkListBox G_GNUC_UNUSED *box,
	GtkListBoxRow *row, MqBackForwardButtonBox *back_forward_button_box)
{
	webkit_web_view_go_to_back_forward_list_item(
		WEBKIT_WEB_VIEW(back_forward_button_box->web_view),
		webkit_back_forward_list_get_nth_item(
			webkit_web_view_get_back_forward_list(WEBKIT_WEB_VIEW(
					back_forward_button_box->web_view)),
			gtk_list_box_row_get_index(row) -
			back_forward_button_box->back_items));

	gtk_widget_hide(back_forward_button_box->back_forward_popover);
}

static void
text_button_toggled_cb(GtkToggleButton *toggle_button,
	GtkStack *stack)
{
	/* Use gtk_widget_show() and gtk_widget_hide() instead of
	 * gtk_stack_set_visible_child() so that the stack fits the size of only
	 * the visible child. */
	gtk_widget_show_all(gtk_stack_get_child_by_name(stack,
			gtk_toggle_button_get_active(toggle_button) ?
				"text" : "list"));
	gtk_widget_hide(gtk_stack_get_child_by_name(stack,
			gtk_toggle_button_get_active(toggle_button) ?
				"list" : "text"));
}

static gboolean
event_box_button_press_cb(GtkWidget *widget,
	GdkEventButton G_GNUC_UNUSED *event,
	MqBackForwardButtonBox *back_forward_button_box)
{
	WebKitBackForwardList *back_forward_list;
	GtkWidget             *list_box;
	GtkWidget             *text_view;
	GtkTextBuffer         *text_buffer;
	GList                 *list_item;
	GtkTextIter            text_iter;
	gchar                 *str;
	GtkTextTag            *text_tag;
	GtkWidget             *list_scrolled_window;
	GtkWidget             *text_scrolled_window;
	GtkWidget             *stack;
	GtkWidget             *toggle_button;
	GtkWidget             *box;

	/* Get the back/forward list for the Web view. */
	back_forward_list = webkit_web_view_get_back_forward_list(
		WEBKIT_WEB_VIEW(back_forward_button_box->web_view));

	/* Set up the list box. */
	list_box = gtk_list_box_new();
	gtk_list_box_set_selection_mode(GTK_LIST_BOX(list_box),
		GTK_SELECTION_BROWSE);
	gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(list_box), TRUE);
	g_signal_connect(list_box, "row-activated",
		G_CALLBACK(list_box_row_activated_cb),
		back_forward_button_box);

	/* Set up the text view. */
	text_view = gtk_text_view_new();
	text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));
	gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view), FALSE);

	/* Insert forward list items. */
	/* The forward list is backwards, so we need to prepend each item to our
	 * list box and text buffer. */
	list_item = webkit_back_forward_list_get_forward_list(
		back_forward_list);
	for (; list_item; list_item = list_item->next) {
		gtk_list_box_prepend(GTK_LIST_BOX(list_box),
			list_item_new(list_item->data, 1));
		gtk_text_buffer_get_start_iter(text_buffer, &text_iter);
		str = g_strdup_printf("\n%s",
			webkit_back_forward_list_item_get_uri(list_item->data));
		gtk_text_buffer_insert(text_buffer, &text_iter, str, -1);
		g_free(str);
	}

	/* Insert the current item. */
	gtk_list_box_prepend(GTK_LIST_BOX(list_box), list_item_new(
			webkit_back_forward_list_get_current_item(
				back_forward_list), 0));
	gtk_list_box_select_row(GTK_LIST_BOX(list_box),
		gtk_list_box_get_row_at_index(GTK_LIST_BOX(list_box), 0));
	gtk_text_buffer_get_start_iter(text_buffer, &text_iter);
	text_tag = gtk_text_buffer_create_tag(text_buffer, NULL, "weight",
		PANGO_WEIGHT_BOLD, NULL);
	gtk_text_buffer_insert_with_tags(text_buffer, &text_iter,
		webkit_back_forward_list_item_get_uri(
			webkit_back_forward_list_get_current_item(
				back_forward_list)), -1, text_tag, NULL);

	/* Insert back list items. */
	list_item = webkit_back_forward_list_get_back_list(
		back_forward_list);
	back_forward_button_box->back_items = 0;
	for (; list_item; list_item = list_item->next) {
		gtk_list_box_prepend(GTK_LIST_BOX(list_box),
			list_item_new(list_item->data, -1));
		gtk_text_buffer_get_start_iter(text_buffer, &text_iter);
		str = g_strdup_printf("%s\n",
			webkit_back_forward_list_item_get_uri(list_item->data));
		gtk_text_buffer_insert(text_buffer, &text_iter, str, -1);
		g_free(str);
		++back_forward_button_box->back_items;
	}

	/*
	 * The following GtkScrolledWindow widgets have hardcoded minimum sizes,
	 * because there seems to be (in GTK+ versions before 3.22) no way to
	 * set the natural size of GtkScrolledWindow and its GtkViewport.
	 *
	 * I tried:
	 *
	 *   - Setting size requests of the sizes of child widgets plus
	 *     scrollbars (commit 2205669)
	 *   - Putting GtkScrolledWindow widgets in stacks with non-scrolled
	 *     versions of children, to allocate more area for the
	 *     GtkScrolledWindow widgets to fill (commits 922cdef and 90686fa)
	 *   - Setting policies to disable scrollbars upon GtkScrolledWindow
	 *     instantiation, then enabling scrollbars on the GtkScrolledWindow
	 *     "size-allocate" signal (commits c534c7e and 42ca783)
	 *
	 * Attempts to match the size of a GtkScrolledWindow's GtkViewport to
	 * its child's size don't allow the GtkScrolledWindow and GtkViewport to
	 * shrink when necessary to fit within the window.  Therefore, such
	 * methods are equivalent to simply not using GtkScrolledWindow at all.
	 * And there appears to be no easy way to calculate the available size
	 * (the size of the GtkWindow minus all of the surrounding widgets) to
	 * manually manage a GtkViewport's size.
	 *
	 * GtkScrolledWindow or GtkViewport ignores the child's preferred
	 * (minimum and natural) sizes and apparently just sets a hardcoded size
	 * of about 64x64 px.  I considered subclassing GtkScrolledWindow or
	 * GtkViewport, but I'm not sure where the hardcoded size is set.
	 *
	 * The functions gtk_scrolled_window_set_propagate_natural_width() and
	 * gtk_scrolled_window_set_propagate_natural_height() were introduced in
	 * GTK+ 3.22 and probably do what I want.  However, I want to maintain
	 * compatibility with at least GTK+ 3.12 or 3.14.
	 */

	/* Set up the list scrolled window. */
	list_scrolled_window = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_min_content_width(
		GTK_SCROLLED_WINDOW(list_scrolled_window), 400);
	gtk_scrolled_window_set_min_content_height(
		GTK_SCROLLED_WINDOW(list_scrolled_window), 200);
	gtk_container_add(GTK_CONTAINER(list_scrolled_window), list_box);

	/* Set up the text scrolled window. */
	text_scrolled_window = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_min_content_width(
		GTK_SCROLLED_WINDOW(text_scrolled_window), 400);
	gtk_scrolled_window_set_min_content_height(
		GTK_SCROLLED_WINDOW(text_scrolled_window), 200);
	gtk_container_add(GTK_CONTAINER(text_scrolled_window), text_view);

	/* Set up the stack. */
	stack = gtk_stack_new();
	gtk_stack_add_named(GTK_STACK(stack), list_scrolled_window, "list");
	gtk_stack_add_named(GTK_STACK(stack), text_scrolled_window, "text");

	/* Set up the toggle button. */
	toggle_button = gtk_toggle_button_new();
	gtk_button_set_image(GTK_BUTTON(toggle_button),
		gtk_image_new_from_icon_name("edit-select-all",
			GTK_ICON_SIZE_SMALL_TOOLBAR));
	gtk_widget_set_halign(toggle_button, GTK_ALIGN_START);
	g_signal_connect(toggle_button, "toggled",
		G_CALLBACK(text_button_toggled_cb), GTK_STACK(stack));

	/* Set up the containing box. */
	box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
	gtk_box_pack_start(GTK_BOX(box), toggle_button, TRUE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(box), stack, TRUE, FALSE, 0);

	/* Set up the popover. */
	back_forward_button_box->back_forward_popover = gtk_popover_new(widget);
	gtk_container_add(
		GTK_CONTAINER(back_forward_button_box->back_forward_popover),
		box);

	/* NB: gtk_popover_popup() is new in GTK+ 3.22. */
	gtk_widget_show_all(back_forward_button_box->back_forward_popover);
	gtk_widget_hide(text_view);

	return FALSE;
}

static void
set_web_view(MqBackForwardButtonBox *back_forward_button_box,
	MqWebView *web_view)
{
	back_forward_button_box->web_view = web_view;

	g_signal_connect(webkit_web_view_get_back_forward_list(
			WEBKIT_WEB_VIEW(web_view)),
		"changed", G_CALLBACK(back_forward_list_changed_cb),
		back_forward_button_box);
}

static void
get_property(GObject *object, guint property_id, GValue *value,
	GParamSpec *param_spec)
{
	MqBackForwardButtonBox *back_forward_button_box;

	back_forward_button_box = MQ_BACK_FORWARD_BUTTON_BOX(object);

	switch (property_id) {
		case PROP_WEB_VIEW:
			g_value_set_object(value,
				back_forward_button_box->web_view);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id,
				param_spec);
			break;
	}
}

static void
set_property(GObject *object, guint property_id, const GValue *value,
	GParamSpec *param_spec)
{
	MqBackForwardButtonBox *back_forward_button_box;

	back_forward_button_box = MQ_BACK_FORWARD_BUTTON_BOX(object);

	switch (property_id) {
		case PROP_WEB_VIEW:
			set_web_view(back_forward_button_box,
				g_value_get_object(value));
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id,
				param_spec);
			break;
	}
}

static void
mq_back_forward_button_box_class_init(MqBackForwardButtonBoxClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);

	object_class->get_property = get_property;
	object_class->set_property = set_property;

	obj_properties[PROP_WEB_VIEW] = g_param_spec_object(
		"web-view",
		"MqWebView",
		"The associated MqWebView instance",
		MQ_TYPE_WEB_VIEW,
		G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
		G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
	g_object_class_install_properties(object_class, N_PROPERTIES,
		obj_properties);
}

static void
mq_back_forward_button_box_init(MqBackForwardButtonBox *back_forward_button_box)
{
	GtkWidget *box;
	GtkWidget *event_box;

	/* Back button */
	back_forward_button_box->back_button = gtk_button_new_from_icon_name(
		"go-previous", GTK_ICON_SIZE_SMALL_TOOLBAR);
	gtk_widget_set_tooltip_text(back_forward_button_box->back_button,
		"Go back one page");
	g_signal_connect(back_forward_button_box->back_button, "clicked",
		G_CALLBACK(back_clicked_cb), back_forward_button_box);

	/* Forward button */
	back_forward_button_box->forward_button = gtk_button_new_from_icon_name(
		"go-next", GTK_ICON_SIZE_SMALL_TOOLBAR);
	gtk_widget_set_tooltip_text(back_forward_button_box->forward_button,
		"Go forward one page");
	g_signal_connect(back_forward_button_box->forward_button, "clicked",
		G_CALLBACK(forward_clicked_cb), back_forward_button_box);

	/* Box */
	box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
	gtk_box_pack_start(GTK_BOX(box),
		back_forward_button_box->back_button, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(box),
		back_forward_button_box->forward_button, FALSE, FALSE, 0);
	gtk_style_context_add_class(gtk_widget_get_style_context(box),
		"linked");

	/* Event box (MqBackForwardButtonBox) */
	event_box = gtk_event_box_new();
	gtk_container_add(GTK_CONTAINER(event_box), box);
	g_signal_connect(event_box, "button-press-event",
		G_CALLBACK(event_box_button_press_cb), back_forward_button_box);

	gtk_container_add(GTK_CONTAINER(back_forward_button_box), event_box);
}

GtkToolItem *
mq_back_forward_button_box_new(MqWebView *web_view)
{
	return g_object_new(MQ_TYPE_BACK_FORWARD_BUTTON_BOX,
		"web-view", web_view,
		NULL);
}