/* SPDX-License-Identifier: Zlib */

#include "callbacks.h"

#include <girara-gtk/statusbar.h>
#include <girara-gtk/session.h>
#include <girara-gtk/settings.h>
#include <girara/log.h>
#include <girara/utils.h>
#include <stdlib.h>
#include <gtk/gtk.h>
#include <string.h>
#include <glib/gi18n.h>
#include <math.h>

#include "links.h"
#include "zathura.h"
#include "render.h"
#include "document.h"
#include "document-widget.h"
#include "utils.h"
#include "shortcuts.h"
#include "page-widget.h"
#include "page.h"
#include "adjustment.h"
#include "synctex.h"
#include "dbus-interface.h"

gboolean cb_destroy(GtkWidget* UNUSED(widget), zathura_t* zathura) {
  if (zathura_has_document(zathura) == true) {
    document_close(zathura, false);
  }

  gtk_main_quit();
  return TRUE;
}

void cb_buffer_changed(girara_session_t* session) {
  g_return_if_fail(session != NULL);
  g_return_if_fail(session->global.data != NULL);

  zathura_t* zathura = session->global.data;

  g_autofree char* buffer = girara_buffer_get(session);
  if (buffer != NULL) {
    girara_statusbar_item_set_text(session, zathura->ui.statusbar.buffer, buffer);
  } else {
    girara_statusbar_item_set_text(session, zathura->ui.statusbar.buffer, "");
  }
}

void update_visible_pages(zathura_t* zathura) {
  zathura_document_t* document       = zathura_get_document(zathura);
  const unsigned int number_of_pages = zathura_document_get_number_of_pages(document);
  const unsigned int pages_per_row   = zathura_document_widget_get_pages_per_row(zathura->ui.document_widget);

  for (unsigned int page_id = 0; page_id < number_of_pages; page_id++) {
    zathura_page_t* page                   = zathura_document_get_page(document, page_id);
    GtkWidget* page_widget                 = zathura_page_get_widget(zathura, page);
    ZathuraPageWidget* zathura_page_widget = ZATHURA_PAGE_WIDGET(page_widget);

    if (page_is_visible(zathura, page_id) == true) {
      // make page visible
      if (zathura_page_get_visibility(page) == false) {
        zathura_page_set_visibility(page, true);
        zathura_renderer_page_cache_add(zathura->sync.render_thread, zathura_page_get_index(page));
      }

      // keep adjacent pages rendered so scrolling lands on ready content
      // consider pages_per_row pages before and after, with the more recents ones close to the page itself
      for (unsigned int i = pages_per_row; i; --i) {
        if (page_id >= i) {
          zathura_page_widget_update_view_time(
              ZATHURA_PAGE_WIDGET(zathura_page_get_widget_by_number(zathura, page_id - i)));
        }
        if (page_id + i < number_of_pages) {
          zathura_page_widget_update_view_time(
              ZATHURA_PAGE_WIDGET(zathura_page_get_widget_by_number(zathura, page_id + i)));
        }
      }

      zathura_page_widget_update_view_time(zathura_page_widget);
    } else {
      /* make page invisible */
      if (zathura_page_get_visibility(page) == true) {
        zathura_page_set_visibility(page, false);
        /* If a page becomes invisible, abort the render request. */
        zathura_page_widget_abort_render_request(zathura_page_widget);
      }

      /* reset current search result */
      girara_list_t* results   = NULL;
      GObject* obj_page_widget = G_OBJECT(page_widget);
      g_object_get(obj_page_widget, "search-results", &results, NULL);
      if (results != NULL) {
        g_object_set(obj_page_widget, "search-current", 0, NULL);
      }
    }
  }
}

void cb_view_hadjustment_value_changed(GtkAdjustment* adjustment, gpointer data) {
  zathura_t* zathura = data;
  if (zathura_has_document(zathura) == false) {
    return;
  }

  /* Do nothing in index mode */
  if (girara_mode_get(zathura->ui.session) == zathura->modes.index) {
    return;
  }

  update_visible_pages(zathura);

  zathura_document_t* document = zathura_get_document(zathura);
  const double position_x      = zathura_adjustment_get_ratio(adjustment);
  const double position_y      = zathura_document_get_position_y(document);
  unsigned int page_id         = position_to_page_number(zathura, position_x, position_y);

  zathura_document_set_position_x(document, position_x);
  zathura_document_set_position_y(document, position_y);
  zathura_document_set_current_page_number(document, page_id);

  statusbar_page_number_update(zathura);
}

void cb_view_vadjustment_value_changed(GtkAdjustment* adjustment, gpointer data) {
  zathura_t* zathura = data;
  if (zathura_has_document(zathura) == false) {
    return;
  }

  /* Do nothing in index mode */
  if (girara_mode_get(zathura->ui.session) == zathura->modes.index) {
    return;
  }

  update_visible_pages(zathura);

  zathura_document_t* document = zathura_get_document(zathura);
  const double position_x      = zathura_document_get_position_x(document);
  const double position_y      = zathura_adjustment_get_ratio(adjustment);
  const unsigned int page_id   = position_to_page_number(zathura, position_x, position_y);

  zathura_document_set_position_x(document, position_x);
  zathura_document_set_position_y(document, position_y);
  zathura_document_set_current_page_number(document, page_id);

  statusbar_page_number_update(zathura);
}

static void cb_view_adjustment_changed(GtkAdjustment* adjustment, zathura_t* zathura, bool width) {
  /* Do nothing in index mode */
  if (girara_mode_get(zathura->ui.session) == zathura->modes.index) {
    return;
  }

  zathura_document_t* document            = zathura_get_document(zathura);
  const zathura_adjust_mode_t adjust_mode = zathura_document_get_adjust_mode(document);

  /* Don't scroll, we're focusing the inputbar. */
  if (adjust_mode == ZATHURA_ADJUST_INPUTBAR) {
    return;
  }

  /* Save the viewport size */
  const unsigned int size = floor(gtk_adjustment_get_page_size(adjustment));
  if (width == true) {
    zathura_document_set_viewport_width(document, size);
  } else {
    zathura_document_set_viewport_height(document, size);
  }

  /* reset the adjustment, in case bounds have changed */
  const double ratio =
      width == true ? zathura_document_get_position_x(document) : zathura_document_get_position_y(document);

  zathura_adjustment_set_value_from_ratio(adjustment, ratio);
}

void cb_view_hadjustment_changed(GtkAdjustment* adjustment, gpointer data) {
  zathura_t* zathura = data;
  g_return_if_fail(zathura != NULL);

  cb_view_adjustment_changed(adjustment, zathura, true);
}

void cb_view_vadjustment_changed(GtkAdjustment* adjustment, gpointer data) {
  zathura_t* zathura = data;
  g_return_if_fail(zathura != NULL);

  cb_view_adjustment_changed(adjustment, zathura, false);
}

void cb_refresh_view(GtkWidget* GIRARA_UNUSED(view), gpointer data) {
  zathura_t* zathura = data;
  if (zathura_has_document(zathura) == false) {
    return;
  }

  zathura_document_t* document = zathura_get_document(zathura);
  unsigned int page_id         = zathura_document_get_current_page_number(document);
  zathura_page_t* page         = zathura_document_get_page(document, page_id);
  if (page == NULL) {
    return;
  }

  GtkWidget* widget = zathura_page_get_widget(zathura, page);
  if (widget) {
    ZathuraPageWidget* page_widget = ZATHURA_PAGE_WIDGET(widget);
    if (zathura_page_widget_have_surface(page_widget)) {
      document_predecessor_free(zathura);
    }
  }

  GtkAdjustment* vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(zathura->ui.view));
  GtkAdjustment* hadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(zathura->ui.view));

  const double position_x = zathura_document_get_position_x(document);
  const double position_y = zathura_document_get_position_y(document);

  zathura_adjustment_set_value_from_ratio(vadj, position_y);
  zathura_adjustment_set_value_from_ratio(hadj, position_x);

  statusbar_page_number_update(zathura);
}

void cb_monitors_changed(GdkScreen* screen, gpointer data) {
  girara_debug("signal received");

  zathura_t* zathura = data;
  if (screen == NULL || zathura == NULL) {
    return;
  }

  zathura_update_view_ppi(zathura);
}

void cb_widget_screen_changed(GtkWidget* widget, GdkScreen* previous_screen, gpointer data) {
  girara_debug("called");

  zathura_t* zathura = data;
  if (widget == NULL || zathura == NULL) {
    return;
  }

  /* disconnect previous screen handler if present */
  if (previous_screen != NULL && zathura->signals.monitors_changed_handler > 0) {
    g_signal_handler_disconnect(previous_screen, zathura->signals.monitors_changed_handler);
    zathura->signals.monitors_changed_handler = 0;
  }

  if (gtk_widget_has_screen(widget)) {
    GdkScreen* screen = gtk_widget_get_screen(widget);

    /* connect to new screen */
    zathura->signals.monitors_changed_handler =
        g_signal_connect(G_OBJECT(screen), "monitors-changed", G_CALLBACK(cb_monitors_changed), zathura);
  }

  zathura_update_view_ppi(zathura);
}

gboolean cb_widget_configured(GtkWidget* UNUSED(widget), GdkEvent* UNUSED(event), gpointer data) {
  zathura_t* zathura = data;
  if (zathura == NULL) {
    return false;
  }

  zathura_update_view_ppi(zathura);

  return false;
}

void cb_scale_factor(GObject* object, GParamSpec* UNUSED(pspec), gpointer data) {
  zathura_t* zathura = data;
  if (zathura_has_document(zathura) == false) {
    return;
  }

  int new_factor = gtk_widget_get_scale_factor(GTK_WIDGET(object));
  if (new_factor == 0) {
    girara_debug("Ignoring new device scale factor = 0");
    return;
  }

  zathura_document_t* document     = zathura_get_document(zathura);
  zathura_device_factors_t current = zathura_document_get_device_factors(document);
  if (fabs(new_factor - current.x) >= DBL_EPSILON || fabs(new_factor - current.y) >= DBL_EPSILON) {
    zathura_document_set_device_factors(document, new_factor, new_factor);
    girara_debug("New device scale factor: %d", new_factor);
    zathura_update_view_ppi(zathura);
    zathura_document_widget_render_all(zathura->ui.document_widget);
  }
}

void cb_page_layout_value_changed(girara_session_t* session, const char* name, girara_setting_type_t UNUSED(type),
                                  const void* value, void* UNUSED(data)) {
  g_return_if_fail(value != NULL);
  g_return_if_fail(session != NULL);
  g_return_if_fail(session->global.data != NULL);
  zathura_t* zathura = session->global.data;

  /* pages-per-row must not be 0 */
  if (g_strcmp0(name, "pages-per-row") == 0) {
    unsigned int pages_per_row = *((const unsigned int*)value);
    if (pages_per_row == 0) {
      pages_per_row = 1;
      girara_setting_set(session, name, &pages_per_row);
      girara_notify(session, GIRARA_WARNING, _("'%s' must not be 0. Set to 1."), name);
      return;
    }
  }

  if (zathura_has_document(zathura) == false) {
    /* no document has been openend yet */
    return;
  }

  unsigned int pages_per_row = 1;
  girara_setting_get(session, "pages-per-row", &pages_per_row);

  /* get list of first_page_column settings */
  g_autofree char* first_page_column_list = NULL;
  girara_setting_get(session, "first-page-column", &first_page_column_list);

  /* find value for first_page_column */
  unsigned int first_page_column = find_first_page_column(first_page_column_list, pages_per_row);

  unsigned int page_v_padding = 1;
  girara_setting_get(zathura->ui.session, "page-v-padding", &page_v_padding);

  unsigned int page_h_padding = 1;
  girara_setting_get(zathura->ui.session, "page-h-padding", &page_h_padding);

  bool page_right_to_left = false;
  girara_setting_get(zathura->ui.session, "page-right-to-left", &page_right_to_left);

  ZathuraDocumentWidget* document_widget = ZATHURA_DOCUMENT_WIDGET(zathura->ui.document_widget);
  zathura_document_widget_set_page_layout(document_widget, page_v_padding, page_h_padding, pages_per_row,
                                          first_page_column);

  g_auto(GValue) page_right_to_left_value = G_VALUE_INIT;
  g_value_init(&page_right_to_left_value, G_TYPE_BOOLEAN);
  g_value_set_boolean(&page_right_to_left_value, page_right_to_left);
  g_object_set_property(G_OBJECT(zathura->ui.document_widget), "pages-right-to-left", &page_right_to_left_value);

  zathura_document_widget_refresh_layout(document_widget);
}

void cb_index_row_activated(GtkTreeView* tree_view, GtkTreePath* path, GtkTreeViewColumn* UNUSED(column), void* data) {
  zathura_t* zathura = data;
  if (tree_view == NULL || zathura == NULL || zathura->ui.session == NULL) {
    return;
  }

  GtkTreeModel* model;
  GtkTreeIter iter;

  g_object_get(G_OBJECT(tree_view), "model", &model, NULL);

  if (gtk_tree_model_get_iter(model, &iter, path)) {
    zathura_index_element_t* index_element;
    gtk_tree_model_get(model, &iter, 3, &index_element, -1);

    if (index_element == NULL) {
      return;
    }

    sc_toggle_index(zathura->ui.session, NULL, NULL, 0);
    zathura_link_evaluate(zathura, index_element->link);
  }

  g_object_unref(model);
}

typedef enum zathura_link_action_e {
  ZATHURA_LINK_ACTION_FOLLOW,
  ZATHURA_LINK_ACTION_COPY,
  ZATHURA_LINK_ACTION_DISPLAY
} zathura_link_action_t;

static gboolean handle_link(GtkEntry* entry, girara_session_t* session, zathura_link_action_t action) {
  g_return_val_if_fail(session != NULL, FALSE);
  g_return_val_if_fail(session->global.data != NULL, FALSE);

  zathura_t* zathura = session->global.data;

  g_autofree char* input = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
  if (input == NULL || strlen(input) == 0) {
    return FALSE;
  }

  unsigned int index = 0;
  guint64 num;
  if (g_ascii_string_to_unsigned(input, 10, 1, UINT_MAX, &num, NULL)) {
    index = num - 1;
  } else {
    girara_notify(session, GIRARA_WARNING, _("Invalid input '%s' given."), input);
    return FALSE;
  }

  zathura_document_t* document = zathura_get_document(zathura);
  /* find the link*/
  zathura_link_t* link         = NULL;
  unsigned int number_of_pages = zathura_document_get_number_of_pages(document);
  for (unsigned int page_id = 0; page_id < number_of_pages && !link; page_id++) {
    zathura_page_t* page = zathura_document_get_page(document, page_id);
    if (page == NULL || zathura_page_get_visibility(page) == false) {
      continue;
    }
    GtkWidget* page_widget = zathura_page_get_widget(zathura, page);
    link                   = zathura_page_widget_link_get(ZATHURA_PAGE_WIDGET(page_widget), index);
  }

  if (link == NULL) {
    girara_notify(session, GIRARA_WARNING, _("Invalid index '%s' given."), input);
  } else {
    switch (action) {
    case ZATHURA_LINK_ACTION_FOLLOW:
      zathura_link_evaluate(zathura, link);
      break;
    case ZATHURA_LINK_ACTION_DISPLAY:
      zathura_link_display(zathura, link);
      break;
    case ZATHURA_LINK_ACTION_COPY: {
      g_autofree GdkAtom* selection = get_selection(zathura);
      if (selection == NULL) {
        break;
      }

      zathura_link_copy(zathura, link, selection);
      break;
    }
    }
  }

  return TRUE;
}

gboolean cb_sc_follow(GtkEntry* entry, void* data) {
  girara_session_t* session = data;
  return handle_link(entry, session, ZATHURA_LINK_ACTION_FOLLOW);
}

gboolean cb_sc_display_link(GtkEntry* entry, void* data) {
  girara_session_t* session = data;
  return handle_link(entry, session, ZATHURA_LINK_ACTION_DISPLAY);
}

gboolean cb_sc_copy_link(GtkEntry* entry, void* data) {
  girara_session_t* session = data;
  return handle_link(entry, session, ZATHURA_LINK_ACTION_COPY);
}

static gboolean file_monitor_reload(void* data) {
  sc_reload((girara_session_t*)data, NULL, NULL, 0);
  return FALSE;
}

void cb_file_monitor(ZathuraFileMonitor* monitor, girara_session_t* session) {
  g_return_if_fail(monitor != NULL);
  g_return_if_fail(session != NULL);

  g_main_context_invoke(NULL, file_monitor_reload, session);
}

static void password_dialog_info_free(zathura_password_dialog_info_t* dialog) {
  if (dialog == NULL) {
    return;
  }
  if (dialog->hide_handler != 0) {
    g_signal_handler_disconnect(dialog->zathura->ui.session->gtk.inputbar_dialog, dialog->hide_handler);
    dialog->hide_handler = 0;
  }
  g_free(dialog->path);
  g_free(dialog->uri);
  g_free(dialog);
}

static void cb_password_dialog_hide(GtkWidget* UNUSED(w), void* data) {
  password_dialog_info_free(data);
}

static void password_dialog_arm_hide(zathura_password_dialog_info_t* dialog) {
  if (dialog == NULL || dialog->zathura == NULL || dialog->zathura->ui.session == NULL ||
      dialog->zathura->ui.session->gtk.inputbar_dialog == NULL) {
    return;
  }
  dialog->hide_handler = g_signal_connect(dialog->zathura->ui.session->gtk.inputbar_dialog, "hide",
                                          G_CALLBACK(cb_password_dialog_hide), dialog);
}

gboolean document_open_password_dialog(gpointer data) {
  zathura_password_dialog_info_t* dialog = data;

  password_dialog_arm_hide(dialog);
  girara_dialog(dialog->zathura->ui.session, _("Enter password:"), true, NULL, cb_password_dialog, dialog);
  return FALSE;
}

static gboolean password_dialog(gpointer data) {
  zathura_password_dialog_info_t* dialog = data;

  if (dialog != NULL) {
    password_dialog_arm_hide(dialog);
    girara_dialog(dialog->zathura->ui.session, "Incorrect password. Enter password:", true, NULL, cb_password_dialog,
                  dialog);
  }

  return FALSE;
}

gboolean cb_password_dialog(GtkEntry* entry, void* data) {
  zathura_password_dialog_info_t* dialog = data;
  if (entry == NULL || dialog == NULL || dialog->path == NULL || dialog->zathura == NULL) {
    password_dialog_info_free(dialog);
    return false;
  }

  if (dialog->hide_handler != 0) {
    g_signal_handler_disconnect(dialog->zathura->ui.session->gtk.inputbar_dialog, dialog->hide_handler);
    dialog->hide_handler = 0;
  }

  g_autofree char* input = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);

  /* no or empty password: ask again */
  if (input == NULL || strlen(input) == 0) {
    gdk_threads_add_idle(password_dialog, dialog);
    return false;
  }

  /* try to open document again */
  if (document_open(dialog->zathura, dialog->path, dialog->uri, input, ZATHURA_PAGE_NUMBER_UNSPECIFIED, NULL) ==
      false) {
    gdk_threads_add_idle(password_dialog, dialog);
  } else {
    password_dialog_info_free(dialog);
  }

  return true;
}

void cb_setting_recolor_change(girara_session_t* session, const char* name, girara_setting_type_t UNUSED(type),
                               const void* value, void* UNUSED(data)) {
  g_return_if_fail(value != NULL);
  g_return_if_fail(session != NULL);
  g_return_if_fail(session->global.data != NULL);
  g_return_if_fail(name != NULL);
  zathura_t* zathura = session->global.data;

  const bool bool_value = *((const bool*)value);

  if (zathura->sync.render_thread != NULL &&
      zathura_renderer_recolor_enabled(zathura->sync.render_thread) != bool_value) {
    zathura_renderer_enable_recolor(zathura->sync.render_thread, bool_value);
    zathura_document_widget_render_all(zathura->ui.document_widget);

    // the old page thumbnails have been rendered with the old setting, so we need to drop all of them
    // TODO: move this somewhere it makes sense
    if (zathura->ui.document_widget) {
      zathura_document_widget_clear_thumbnails(ZATHURA_DOCUMENT_WIDGET(zathura->ui.document_widget));
    }
  }
}

void cb_setting_recolor_keep_hue_change(girara_session_t* session, const char* name, girara_setting_type_t UNUSED(type),
                                        const void* value, void* UNUSED(data)) {
  g_return_if_fail(value != NULL);
  g_return_if_fail(session != NULL);
  g_return_if_fail(session->global.data != NULL);
  g_return_if_fail(name != NULL);
  zathura_t* zathura = session->global.data;

  const bool bool_value = *((const bool*)value);

  if (zathura->sync.render_thread != NULL &&
      zathura_renderer_recolor_hue_enabled(zathura->sync.render_thread) != bool_value) {
    zathura_renderer_enable_recolor_hue(zathura->sync.render_thread, bool_value);
    zathura_document_widget_render_all(zathura->ui.document_widget);
  }
}

void cb_setting_recolor_keep_reverse_video_change(girara_session_t* session, const char* name,
                                                  girara_setting_type_t UNUSED(type), const void* value,
                                                  void* UNUSED(data)) {
  g_return_if_fail(value != NULL);
  g_return_if_fail(session != NULL);
  g_return_if_fail(session->global.data != NULL);
  g_return_if_fail(name != NULL);
  zathura_t* zathura = session->global.data;

  const bool bool_value = *((const bool*)value);

  if (zathura->sync.render_thread != NULL &&
      zathura_renderer_recolor_reverse_video_enabled(zathura->sync.render_thread) != bool_value) {
    zathura_renderer_enable_recolor_reverse_video(zathura->sync.render_thread, bool_value);
    zathura_document_widget_render_all(zathura->ui.document_widget);
  }
}

bool cb_unknown_command(girara_session_t* session, const char* input) {
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  g_return_val_if_fail(input != NULL, false);

  zathura_t* zathura = session->global.data;
  if (zathura_has_document(zathura) == false) {
    return false;
  }

  unsigned int index = 0;
  guint64 num;
  if (g_ascii_string_to_unsigned(input, 10, 1, UINT_MAX, &num, NULL)) {
    index = num - 1;
  } else {
    return false;
  }

  zathura_jumplist_add(zathura);
  page_set(zathura, index);
  zathura_jumplist_add(zathura);

  return true;
}

void cb_page_widget_text_selected(ZathuraPageWidget* page, const char* text, void* data) {
  g_return_if_fail(page != NULL);
  g_return_if_fail(text != NULL);
  g_return_if_fail(data != NULL);

  zathura_t* zathura = data;
  girara_mode_t mode = girara_mode_get(zathura->ui.session);
  if (mode != zathura->modes.normal && mode != zathura->modes.fullscreen) {
    return;
  }

  g_autofree GdkAtom* selection = get_selection(zathura);
  if (selection == NULL) {
    return;
  }

  /* copy to clipboard */
  gtk_clipboard_set_text(gtk_clipboard_get(*selection), text, -1);

  bool notification = true;
  girara_setting_get(zathura->ui.session, "selection-notification", &notification);

  if (notification == true) {
    g_autofree char* target = NULL;
    girara_setting_get(zathura->ui.session, "selection-clipboard", &target);

    g_autofree char* stripped_text = g_strdelimit(g_strdup(text), "\n\t\r\n", ' ');
    g_autofree char* escaped_text =
        g_markup_printf_escaped(_("Copied selected text to selection %s: %s"), target, stripped_text);

    girara_notify(zathura->ui.session, GIRARA_INFO, "%s", escaped_text);
  }
}

void cb_page_widget_image_selected(ZathuraPageWidget* page, GdkPixbuf* pixbuf, void* data) {
  g_return_if_fail(page != NULL);
  g_return_if_fail(pixbuf != NULL);
  g_return_if_fail(data != NULL);

  zathura_t* zathura            = data;
  g_autofree GdkAtom* selection = get_selection(zathura);
  if (selection == NULL) {
    return;
  }

  gtk_clipboard_set_image(gtk_clipboard_get(*selection), pixbuf);

  bool notification = true;
  girara_setting_get(zathura->ui.session, "selection-notification", &notification);

  if (notification == true) {
    g_autofree char* target = NULL;
    girara_setting_get(zathura->ui.session, "selection-clipboard", &target);

    g_autofree char* escaped_text = g_markup_printf_escaped(_("Copied selected image to selection %s"), target);

    girara_notify(zathura->ui.session, GIRARA_INFO, "%s", escaped_text);
  }
}

void cb_page_widget_link(ZathuraPageWidget* page, void* data) {
  g_return_if_fail(page != NULL);

  bool enter = (bool)data;

  GdkWindow* window   = gtk_widget_get_parent_window(GTK_WIDGET(page));
  GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(page));
  GdkCursor* cursor   = gdk_cursor_new_for_display(display, enter == true ? GDK_HAND1 : GDK_LEFT_PTR);
  gdk_window_set_cursor(window, cursor);
  g_object_unref(cursor);
}

void cb_page_widget_scaled_button_release(ZathuraPageWidget* page_widget, GdkEventButton* event, void* data) {
  zathura_t* zathura   = data;
  zathura_page_t* page = zathura_page_widget_get_page(page_widget);

  if (event->button != GDK_BUTTON_PRIMARY) {
    return;
  }

  /* set page number (but don't scroll there. it was clicked on, so it's visible) */
  zathura_document_set_current_page_number(zathura_get_document(zathura), zathura_page_get_index(page));
  refresh_view(zathura);

  if (event->state & zathura->global.synctex_edit_modmask) {
    bool synctex = false;
    girara_setting_get(zathura->ui.session, "synctex", &synctex);
    if (synctex == false) {
      return;
    }

    if (zathura->dbus != NULL) {
      zathura_dbus_edit(zathura, zathura_page_get_index(page), event->x, event->y);
    }

    g_autofree char* editor = NULL;
    girara_setting_get(zathura->ui.session, "synctex-editor-command", &editor);
    if (editor == NULL || *editor == '\0') {
      girara_debug("No SyncTeX editor specified.");
      return;
    }

    synctex_edit(zathura, editor, page, event->x, event->y);
  }
}

void cb_window_update_icon(ZathuraRenderRequest* GIRARA_UNUSED(request), cairo_surface_t* surface, void* data) {
  zathura_t* zathura = data;

  girara_debug("updating window icon");
  GdkPixbuf* pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, cairo_image_surface_get_width(surface),
                                                  cairo_image_surface_get_height(surface));
  if (pixbuf == NULL) {
    girara_error("Unable to convert cairo surface to Gdk Pixbuf.");
    return;
  }

  gtk_window_set_icon(GTK_WINDOW(zathura->ui.session->gtk.window), pixbuf);
  g_object_unref(pixbuf);
}

void cb_gesture_zoom_begin(GtkGesture* UNUSED(self), GdkEventSequence* UNUSED(sequence), void* data) {
  zathura_t* zathura = data;
  if (zathura_has_document(zathura) == false) {
    return;
  }
  zathura->gesture.initial_zoom = zathura_document_get_zoom(zathura_get_document(zathura));
}

void cb_gesture_zoom_scale_changed(GtkGestureZoom* UNUSED(self), gdouble scale, void* data) {
  zathura_t* zathura = data;
  if (zathura_has_document(zathura) == false) {
    return;
  }

  const double next_zoom     = zathura->gesture.initial_zoom * scale;
  girara_argument_t argument = {.n = ZOOM_SPECIFIC};

  sc_zoom(zathura->ui.session, &argument, NULL, next_zoom * 100);
}

void cb_hide_links(GtkWidget* widget, gpointer data) {
  g_return_if_fail(widget != NULL);
  g_return_if_fail(data != NULL);

  /* disconnect from signal */
  gulong handler_id = GPOINTER_TO_UINT(g_object_steal_data(G_OBJECT(widget), "handler_id"));
  g_signal_handler_disconnect(G_OBJECT(widget), handler_id);

  zathura_t* zathura           = data;
  zathura_document_t* document = zathura_get_document(zathura);
  unsigned int number_of_pages = zathura_document_get_number_of_pages(document);
  for (unsigned int page_id = 0; page_id < number_of_pages; page_id++) {
    zathura_page_t* page = zathura_document_get_page(document, page_id);
    if (page == NULL) {
      continue;
    }

    GtkWidget* page_widget = zathura_page_get_widget(zathura, page);
    g_object_set(G_OBJECT(page_widget), "draw-links", FALSE, NULL);
  }
}
