/*
 * transmission-remote-gtk - A GTK RPC client to Transmission
 * Copyright (C) 2011-2013  Alan Fitton

 * This program 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 2 of the License, or
 * (at your option) any later version.

 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"

#include <math.h>
#include <stdint.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <json-glib/json-glib.h>
#if ENABLE_NL_LANGINFO
#include <langinfo.h>
#endif

#include "trg-main-window.h"
#include "trg-remote-prefs-dialog.h"
#include "hig.h"
#include "util.h"
#include "requests.h"
#include "json.h"
#include "trg-json-widgets.h"
#include "session-get.h"

/* Using the widget creation functions in trg-json-widgets.c, load remote
 * preferences from the latest session object held by TrgClient.
 * If the user clicks OK, use trg-json-widgets to build up a request object
 * and send that in a session-set request.
 *
 * The on_session_set callback should cause that session object to be refreshed
 * as soon as the set is complete.
 */

G_DEFINE_TYPE(TrgRemotePrefsDialog, trg_remote_prefs_dialog,
              GTK_TYPE_DIALOG)
#define TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_REMOTE_PREFS_DIALOG, TrgRemotePrefsDialogPrivate))
enum {
    PROP_0, PROP_PARENT, PROP_CLIENT
};

typedef struct _TrgRemotePrefsDialogPrivate TrgRemotePrefsDialogPrivate;

struct _TrgRemotePrefsDialogPrivate {
    TrgClient *client;
    TrgMainWindow *parent;

    GList *widgets;
    GtkWidget *encryption_combo;
    GtkWidget *port_test_label, *port_test_button;
    GtkWidget *blocklist_update_button, *blocklist_check;
    GtkWidget *alt_check, *alt_time_check;
};

static GObject *instance = NULL;

static void update_session(GtkDialog * dlg)
{
    TrgRemotePrefsDialogPrivate *priv =
        TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(dlg);

    JsonNode *request = session_set();
    JsonObject *args = node_get_arguments(request);
    gchar *encryption;

    /* Connection */

    switch (gtk_combo_box_get_active
            (GTK_COMBO_BOX(priv->encryption_combo))) {
    case 0:
        encryption = "required";
        break;
    case 2:
        encryption = "tolerated";
        break;
    default:
        encryption = "preferred";
        break;
    }

    json_object_set_string_member(args, SGET_ENCRYPTION, encryption);

    trg_json_widgets_save(priv->widgets, args);

    dispatch_async(priv->client, request, on_session_set, priv->parent);
}

static void
trg_remote_prefs_response_cb(GtkDialog * dlg, gint res_id,
                             gpointer data G_GNUC_UNUSED)
{
    TrgRemotePrefsDialogPrivate *priv =
        TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(dlg);

    if (res_id == GTK_RESPONSE_OK)
        update_session(dlg);

    trg_json_widget_desc_list_free(priv->widgets);

    gtk_widget_destroy(GTK_WIDGET(dlg));
    instance = NULL;
}

static void
trg_remote_prefs_dialog_get_property(GObject * object,
                                     guint property_id,
                                     GValue * value, GParamSpec * pspec)
{
    TrgRemotePrefsDialogPrivate *priv =
        TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(object);
    switch (property_id) {
    case PROP_PARENT:
        g_value_set_object(value, priv->parent);
        break;
    case PROP_CLIENT:
        g_value_set_pointer(value, priv->client);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
        break;
    }
}

static void
trg_remote_prefs_dialog_set_property(GObject * object,
                                     guint property_id,
                                     const GValue * value,
                                     GParamSpec * pspec)
{
    TrgRemotePrefsDialogPrivate *priv =
        TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(object);
    switch (property_id) {
    case PROP_PARENT:
        priv->parent = g_value_get_object(value);
        break;
    case PROP_CLIENT:
        priv->client = g_value_get_pointer(value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
        break;
    }
}

static void
trg_remote_prefs_double_special_dependent(GtkWidget * widget,
                                          gpointer data)
{
    TrgRemotePrefsDialogPrivate *priv =
        TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(gtk_widget_get_toplevel
                                            (GTK_WIDGET(widget)));

    gtk_widget_set_sensitive(GTK_WIDGET(data),
                             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
                                                          (priv->
                                                           alt_time_check))
                             ||
                             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
                                                          (priv->
                                                           alt_check)));
}

static void
trg_rprefs_time_widget_savefunc(GtkWidget * w, JsonObject * obj,
                                gchar * key)
{
    GtkWidget *hourSpin = g_object_get_data(G_OBJECT(w), "hours-spin");
    GtkWidget *minutesSpin = g_object_get_data(G_OBJECT(w), "mins-spin");
    gint hoursValue =
        gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(hourSpin));
    gint minutesValue =
        gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(minutesSpin));

    json_object_set_int_member(obj, key,
                               (gint64) ((hoursValue * 60) +
                                         minutesValue));

}

static gboolean on_output(GtkSpinButton * spin, gpointer data)
{
    GtkAdjustment *adj;
    gchar *text;
    int value;
    adj = gtk_spin_button_get_adjustment(spin);
    value = (int) gtk_adjustment_get_value(adj);
    text = g_strdup_printf("%02d", value);
    gtk_entry_set_text(GTK_ENTRY(spin), text);
    g_free(text);

    return TRUE;
}

static GtkWidget *trg_rprefs_timer_widget_spin_new(gint max,
                                                   GtkWidget *
                                                   alt_time_check)
{
    GtkWidget *w = gtk_spin_button_new_with_range(0, max, 1);
    gtk_widget_set_sensitive(w,
                             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
                                                          (alt_time_check)));
    g_signal_connect(G_OBJECT(alt_time_check), "toggled",
                     G_CALLBACK(toggle_active_arg_is_sensitive), w);
    g_signal_connect(G_OBJECT(w), "output", G_CALLBACK(on_output), NULL);
    return w;
}

static GtkWidget *trg_rprefs_time_widget_new(GList ** wl, JsonObject * obj,
                                             const gchar * key,
                                             GtkWidget * alt_time_check)
{
    GtkWidget *hbox = trg_hbox_new(FALSE, 0);
    GtkWidget *colonLabel = gtk_label_new(":");
    GtkWidget *hourSpin =
        trg_rprefs_timer_widget_spin_new(23, alt_time_check);
    GtkWidget *minutesSpin = trg_rprefs_timer_widget_spin_new(69,
                                                              alt_time_check);
    gint64 value = json_object_get_int_member(obj, key);

    trg_json_widget_desc *wd = g_new0(trg_json_widget_desc, 1);
    wd->key = g_strdup(key);
    wd->widget = hbox;
    wd->saveFunc = trg_rprefs_time_widget_savefunc;

    g_object_set_data(G_OBJECT(hbox), "hours-spin", hourSpin);
    g_object_set_data(G_OBJECT(hbox), "mins-spin", minutesSpin);

    gtk_spin_button_set_value(GTK_SPIN_BUTTON(hourSpin),
                              floor((gdouble) value / 60.0));
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(minutesSpin),
                              (gdouble) (value % 60));

    gtk_box_pack_start(GTK_BOX(hbox), hourSpin, TRUE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), colonLabel, TRUE, FALSE, 2);
    gtk_box_pack_start(GTK_BOX(hbox), minutesSpin, TRUE, FALSE, 0);

    *wl = g_list_append(*wl, wd);

    return hbox;
}

static GtkWidget *trg_rprefs_time_begin_end_new(GList ** wl,
                                                JsonObject * obj,
                                                GtkWidget * alt_time_check)
{
    GtkWidget *hbox = trg_hbox_new(FALSE, 0);
    GtkWidget *begin = trg_rprefs_time_widget_new(wl, obj,
                                                  SGET_ALT_SPEED_TIME_BEGIN,
                                                  alt_time_check);
    GtkWidget *end = trg_rprefs_time_widget_new(wl, obj,
                                                SGET_ALT_SPEED_TIME_END,
                                                alt_time_check);

    gtk_box_pack_start(GTK_BOX(hbox), begin, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("-"), FALSE, FALSE,
                       10);
    gtk_box_pack_start(GTK_BOX(hbox), end, FALSE, FALSE, 0);

    return hbox;
}

static GtkWidget *trg_rprefs_alt_speed_spin_new(GList ** wl,
                                                JsonObject * obj,
                                                const gchar * key,
                                                GtkWidget * alt_check,
                                                GtkWidget * alt_time_check)
{
    GtkWidget *w = trg_json_widget_spin_int_new(wl, obj, key,
                                                NULL, 0, INT_MAX, 5);
    gtk_widget_set_sensitive(w,
                             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
                                                          (alt_check))
                             ||
                             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
                                                          (alt_time_check)));
    g_signal_connect(G_OBJECT(alt_time_check), "toggled",
                     G_CALLBACK(trg_remote_prefs_double_special_dependent),
                     w);
    g_signal_connect(G_OBJECT(alt_check), "toggled",
                     G_CALLBACK(trg_remote_prefs_double_special_dependent),
                     w);
    return w;
}

static void
trg_rprefs_alt_days_savefunc(GtkWidget * grid, JsonObject * obj,
                                gchar * key)
{
    guint64 days = 0;

    for(gint i = 0, x = 1; i < 7; i++, x<<=1) {
        GtkWidget *w = gtk_grid_get_child_at (GTK_GRID(grid), i, 0);
        if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)))
            days += x;
    }

    json_object_set_int_member(obj, key, days);
}

static GtkWidget *trg_rprefs_alt_days(GList ** wl,
                                      JsonObject * Obj,
                                      const gchar * key,
                                      GtkWidget *alt_time_check)
{
    gchar *abdays_fallback[] = {_("Sun"), _("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat")};
#if ENABLE_NL_LANGINFO
    nl_item abdays[] = {ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7};
#endif
    GtkWidget *grid = gtk_grid_new();
    gtk_grid_set_column_homogeneous (GTK_GRID(grid), TRUE);
    g_signal_connect(G_OBJECT(alt_time_check), "toggled",
                     G_CALLBACK(toggle_active_arg_is_sensitive), grid);

    gint64 days = json_object_get_int_member(Obj, key);

    for(gint i = 0, x = 1; i < 7; i++, x<<=1) {
#if ENABLE_NL_LANGINFO
        gchar *utf8 = g_convert_with_fallback(nl_langinfo(abdays[i]), -1, "utf-8",
                                              nl_langinfo(CODESET), NULL, NULL,
                                              NULL, NULL);
        GtkWidget *w = gtk_check_button_new_with_label (utf8 ? utf8 : abdays_fallback[i]);
        g_free(utf8);
#else
        GtkWidget *w = gtk_check_button_new_with_label (abdays_fallback[i]);
#endif
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), (days & x) == x);
        gtk_grid_attach(GTK_GRID(grid), w, i, 0, 1, 1);
    }

    gtk_widget_set_sensitive(grid,
    gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(alt_time_check)));

    trg_json_widget_desc *wd = g_new0(trg_json_widget_desc, 1);
    wd->key = g_strdup(key);
    wd->widget = grid;
    wd->saveFunc = trg_rprefs_alt_days_savefunc;
    *wl = g_list_append(*wl, wd);

    return grid;
}

static GtkWidget *trg_rprefs_bandwidthPage(TrgRemotePrefsDialog * win,
                                           JsonObject * json)
{
    TrgRemotePrefsDialogPrivate *priv =
        TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(win);
    GtkWidget *w, *tb, *t;
    guint row = 0;

    t = hig_workarea_create();

    hig_workarea_add_section_title(t, &row, _("Bandwidth limits"));

    tb = trg_json_widget_check_new(&priv->widgets, json,
                                   SGET_SPEED_LIMIT_DOWN_ENABLED,
                                   _("Down Limit (KiB/s)"), NULL);
    w = trg_json_widget_spin_int_new(&priv->widgets, json,
                                     SGET_SPEED_LIMIT_DOWN, tb, 0, INT_MAX, 5);
    hig_workarea_add_row_w(t, &row, tb, w, NULL);

    tb = trg_json_widget_check_new(&priv->widgets, json,
                                   SGET_SPEED_LIMIT_UP_ENABLED,
                                   _("Up Limit (KiB/s)"), NULL);
    w = trg_json_widget_spin_int_new(&priv->widgets, json, SGET_SPEED_LIMIT_UP,
                                     tb, 0, INT_MAX, 5);
    hig_workarea_add_row_w(t, &row, tb, w, NULL);

    hig_workarea_add_section_title(t, &row, _("Alternate limits"));

    w = priv->alt_check = trg_json_widget_check_new(&priv->widgets, json,
                                                    SGET_ALT_SPEED_ENABLED,
                                                    _
                                                    ("Alternate speed limits active"),
                                                    NULL);
    hig_workarea_add_wide_control(t, &row, w);

    tb = priv->alt_time_check =
        trg_json_widget_check_new(&priv->widgets, json,
                                  SGET_ALT_SPEED_TIME_ENABLED,
                                  _("Alternate time range"), NULL);
    w = trg_rprefs_time_begin_end_new(&priv->widgets, json, tb);
    hig_workarea_add_row_w(t, &row, tb, w, NULL);

    w = trg_rprefs_alt_days(&priv->widgets, json,
                            SGET_ALT_SPEED_TIME_DAY, priv->alt_time_check);
    hig_workarea_add_row(t, &row, _("Alternate days"), w, NULL);

    w = trg_rprefs_alt_speed_spin_new(&priv->widgets, json,
                                      SGET_ALT_SPEED_DOWN, priv->alt_check,
                                      tb);
    hig_workarea_add_row(t, &row, _("Alternate down limit (KiB/s)"), w, w);

    w = trg_rprefs_alt_speed_spin_new(&priv->widgets, json,
                                      SGET_ALT_SPEED_UP, priv->alt_check,
                                      tb);
    hig_workarea_add_row(t, &row, _("Alternate up limit (KiB/s)"), w, w);

    return t;
}

static GtkWidget *trg_rprefs_limitsPage(TrgRemotePrefsDialog * win,
                                        JsonObject * json)
{
    TrgRemotePrefsDialogPrivate *priv =
        TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(win);
    GtkWidget *w, *tb, *t;
    guint row = 0;

    t = hig_workarea_create();

    hig_workarea_add_section_title(t, &row, _("Seeding"));

    tb = trg_json_widget_check_new(&priv->widgets, json,
                                   SGET_SEED_RATIO_LIMITED,
                                   _("Seed ratio limit"), NULL);
    w = trg_json_widget_spin_double_new(&priv->widgets, json,
                                        SGET_SEED_RATIO_LIMIT, tb,
                                        0, INT_MAX, 0.1);
    hig_workarea_add_row_w(t, &row, tb, w, NULL);

    if (json_object_has_member(json, SGET_DOWNLOAD_QUEUE_ENABLED)) {
        hig_workarea_add_section_title(t, &row, _("Queues"));

        tb = trg_json_widget_check_new(&priv->widgets, json,
                                       SGET_DOWNLOAD_QUEUE_ENABLED,
                                       _("Download queue size"), NULL);
        w = trg_json_widget_spin_int_new(&priv->widgets, json,
                                         SGET_DOWNLOAD_QUEUE_SIZE, tb, 0,
                                         INT_MAX, 1);
        hig_workarea_add_row_w(t, &row, tb, w, NULL);

        tb = trg_json_widget_check_new(&priv->widgets, json,
                                       SGET_SEED_QUEUE_ENABLED,
                                       _("Seed queue size"), NULL);
        w = trg_json_widget_spin_int_new(&priv->widgets, json,
                                         SGET_SEED_QUEUE_SIZE, tb, 0,
                                         INT_MAX, 1);
        hig_workarea_add_row_w(t, &row, tb, w, NULL);

        tb = trg_json_widget_check_new(&priv->widgets, json,
                                       SGET_QUEUE_STALLED_ENABLED,
                                       _("Ignore stalled (minutes)"),
                                       NULL);
        w = trg_json_widget_spin_int_new(&priv->widgets, json,
                                         SGET_QUEUE_STALLED_MINUTES, tb,
                                          0, INT_MAX, 1);
        hig_workarea_add_row_w(t, &row, tb, w, NULL);
    }

    hig_workarea_add_section_title(t, &row, _("Peers"));

    w = trg_json_widget_spin_int_new(&priv->widgets, json,
                                     SGET_PEER_LIMIT_GLOBAL, NULL, 0,
                                     INT_MAX, 5);
    hig_workarea_add_row(t, &row, _("Global peer limit"), w, w);

    w = trg_json_widget_spin_int_new(&priv->widgets, json,
                                     SGET_PEER_LIMIT_PER_TORRENT, NULL,
                                     0, INT_MAX, 5);
    hig_workarea_add_row(t, &row, _("Per torrent peer limit"), w, w);

    return t;
}

static gboolean on_port_tested(gpointer data)
{
    trg_response *response = (trg_response *) data;
    if (TRG_IS_REMOTE_PREFS_DIALOG(response->cb_data)) {
        TrgRemotePrefsDialogPrivate *priv =
            TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(response->cb_data);

        gtk_button_set_label(GTK_BUTTON(priv->port_test_button),
                             _("Retest"));
        gtk_widget_set_sensitive(priv->port_test_button, TRUE);

        if (response->status == CURLE_OK) {
            gboolean isOpen =
                json_object_get_boolean_member(get_arguments
                                               (response->obj),
                                               "port-is-open");
            if (isOpen)
                gtk_label_set_markup(GTK_LABEL(priv->port_test_label),
                                     _
                                     ("Port is <span font_weight=\"bold\" fgcolor=\"darkgreen\">open</span>"));
            else
                gtk_label_set_markup(GTK_LABEL(priv->port_test_label),
                                     _
                                     ("Port is <span font_weight=\"bold\" fgcolor=\"red\">closed</span>"));
        } else {
          trg_error_dialog(GTK_WINDOW(response->cb_data), response);
        }
    }

    trg_response_free(response);
    return FALSE;
}

static void port_test_cb(GtkButton * b, gpointer data)
{
    TrgRemotePrefsDialogPrivate *priv =
        TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(data);
    JsonNode *req = port_test();

    gtk_label_set_text(GTK_LABEL(priv->port_test_label), _("Port test"));
    gtk_button_set_label(b, _("Testing..."));
    gtk_widget_set_sensitive(GTK_WIDGET(b), FALSE);

    dispatch_async(priv->client, req, on_port_tested, data);
}

static gboolean on_blocklist_updated(gpointer data)
{
    trg_response *response = (trg_response *) data;
    if (TRG_IS_REMOTE_PREFS_DIALOG(response->cb_data)) {
        TrgRemotePrefsDialogPrivate *priv =
            TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(response->cb_data);

        gtk_widget_set_sensitive(priv->blocklist_update_button, TRUE);
        gtk_button_set_label(GTK_BUTTON(priv->blocklist_update_button),
                             _("Update"));

        if (response->status == CURLE_OK) {
            JsonObject *args = get_arguments(response->obj);
            gchar *labelText =
                g_strdup_printf(_("Blocklist (%" G_GINT64_FORMAT " entries)"),
                                json_object_get_int_member(args,
                                                           SGET_BLOCKLIST_SIZE));
            gtk_button_set_label(GTK_BUTTON(priv->blocklist_check),
                                 labelText);
            g_free(labelText);
        } else {
            trg_error_dialog(GTK_WINDOW(response->cb_data), response);
        }
    }

    trg_response_free(response);

    return FALSE;
}

static gboolean update_blocklist_cb(GtkButton * b, gpointer data)
{
    TrgRemotePrefsDialogPrivate *priv =
        TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(data);
    JsonNode *req = blocklist_update();

    gtk_widget_set_sensitive(GTK_WIDGET(b), FALSE);
    gtk_button_set_label(b, _("Updating..."));

    dispatch_async(priv->client, req, on_blocklist_updated, data);

    return FALSE;
}

static GtkWidget *trg_rprefs_connPage(TrgRemotePrefsDialog * win,
                                      JsonObject * s)
{
    TrgRemotePrefsDialogPrivate *priv =
        TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(win);

    GtkWidget *w, *tb, *t;
    const gchar *stringValue;
    guint row = 0;

    t = hig_workarea_create();

    hig_workarea_add_section_title(t, &row, _("Connections"));

    w = trg_json_widget_spin_int_new(&priv->widgets, s, SGET_PEER_PORT,
                                     NULL, 0, 65535, 1);
    hig_workarea_add_row(t, &row, _("Peer port"), w, w);

    priv->port_test_label = gtk_label_new(_("Port test"));
    w = priv->port_test_button = gtk_button_new_with_label(_("Test"));
    g_signal_connect(w, "clicked", G_CALLBACK(port_test_cb), win);
    hig_workarea_add_row_w(t, &row, priv->port_test_label, w, NULL);

    w = priv->encryption_combo = gtr_combo_box_new_enum(_("Required"), 0,
                                                        _("Preferred"), 1,
                                                        _("Tolerated"), 2,
                                                        NULL);
    stringValue = session_get_encryption(s);
    if (!g_strcmp0(stringValue, "required")) {
        gtk_combo_box_set_active(GTK_COMBO_BOX(w), 0);
    } else if (!g_strcmp0(stringValue, "tolerated")) {
        gtk_combo_box_set_active(GTK_COMBO_BOX(w), 2);
    } else {
        gtk_combo_box_set_active(GTK_COMBO_BOX(w), 1);
    }

    hig_workarea_add_row(t, &row, _("Encryption"), w, NULL);

    w = trg_json_widget_check_new(&priv->widgets, s,
                                  SGET_PEER_PORT_RANDOM_ON_START,
                                  _("Random peer port on start"), NULL);
    hig_workarea_add_wide_control(t, &row, w);

    w = trg_json_widget_check_new(&priv->widgets, s,
                                  SGET_PORT_FORWARDING_ENABLED,
                                  _("Request forwarding of the peer port using UPnP or NAT-PMP"), NULL);
    hig_workarea_add_wide_control(t, &row, w);

    hig_workarea_add_section_title(t, &row, _("Protocol"));

    w = trg_json_widget_check_new(&priv->widgets, s, SGET_PEX_ENABLED,
                                  _("Peer exchange (PEX)"), NULL);
    hig_workarea_add_wide_control(t, &row, w);

    w = trg_json_widget_check_new(&priv->widgets, s, SGET_DHT_ENABLED,
                                  _("Distributed Hash Table (DHT)"), NULL);
    hig_workarea_add_wide_control(t, &row, w);

    w = trg_json_widget_check_new(&priv->widgets, s, SGET_LPD_ENABLED,
                                  _("Local peer discovery"), NULL);
    hig_workarea_add_wide_control(t, &row, w);

    hig_workarea_add_section_title(t, &row, _("Blocklist"));

    stringValue = g_strdup_printf(_("Blocklist (%" G_GINT64_FORMAT " entries)"),
                                  session_get_blocklist_size(s));
    tb = priv->blocklist_check =
        trg_json_widget_check_new(&priv->widgets, s,
                                  SGET_BLOCKLIST_ENABLED, stringValue,
                                  NULL);
    g_free((gchar *) stringValue);

    w = priv->blocklist_update_button =
        gtk_button_new_with_label(_("Update"));
    g_signal_connect(G_OBJECT(w), "clicked",
                     G_CALLBACK(update_blocklist_cb), win);
    hig_workarea_add_row_w(t, &row, tb, w, NULL);

    stringValue = session_get_blocklist_url(s);
    if (stringValue) {
        w = trg_json_widget_entry_new(&priv->widgets, s,
                                      SGET_BLOCKLIST_URL, NULL);
        hig_workarea_add_row(t, &row, _("Blocklist URL:"), w, NULL);
    }

    return t;
}

static GtkWidget *trg_rprefs_generalPage(TrgRemotePrefsDialog * win,
                                         JsonObject * s)
{
    TrgRemotePrefsDialogPrivate *priv =
        TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(win);

    GtkWidget *w, *tb, *t;
    guint row = 0;
    gint64 cache_size_mb;

    t = hig_workarea_create();

    hig_workarea_add_section_title(t, &row, _("Environment"));

    w = trg_json_widget_entry_new(&priv->widgets, s, SGET_DOWNLOAD_DIR,
                                  NULL);
    hig_workarea_add_row(t, &row, _("Download directory"), w, NULL);

    tb = trg_json_widget_check_new(&priv->widgets, s,
                                   SGET_INCOMPLETE_DIR_ENABLED,
                                   _("Incomplete download dir"), NULL);
    w = trg_json_widget_entry_new(&priv->widgets, s, SGET_INCOMPLETE_DIR,
                                  tb);
    hig_workarea_add_row_w(t, &row, tb, w, NULL);

    tb = trg_json_widget_check_new(&priv->widgets, s,
                                   SGET_SCRIPT_TORRENT_DONE_ENABLED,
                                   _("Torrent done script"), NULL);
    w = trg_json_widget_entry_new(&priv->widgets, s,
                                  SGET_SCRIPT_TORRENT_DONE_FILENAME, tb);
    hig_workarea_add_row_w(t, &row, tb, w, NULL);

    cache_size_mb = session_get_cache_size_mb(s);
    if (cache_size_mb >= 0) {
        w = trg_json_widget_spin_int_new(&priv->widgets, s, SGET_CACHE_SIZE_MB,
                                         NULL, 0, INT_MAX, 1);
        hig_workarea_add_row(t, &row, _("Cache size (MiB)"), w, w);
    }

    hig_workarea_add_section_title(t, &row, _("Behavior"));

    w = trg_json_widget_check_new(&priv->widgets, s,
                                  SGET_RENAME_PARTIAL_FILES,
                                  _("Rename partial files"), NULL);
    hig_workarea_add_wide_control(t, &row, w);

    w = trg_json_widget_check_new(&priv->widgets, s,
                                  SGET_TRASH_ORIGINAL_TORRENT_FILES, _
                                  ("Trash original torrent files"), NULL);
    hig_workarea_add_wide_control(t, &row, w);

    w = trg_json_widget_check_new(&priv->widgets, s,
                                  SGET_START_ADDED_TORRENTS,
                                  _("Start added torrents"), NULL);
    hig_workarea_add_wide_control(t, &row, w);

    return t;
}

static GObject *trg_remote_prefs_dialog_constructor(GType type,
                                                    guint
                                                    n_construct_properties,
                                                    GObjectConstructParam *
                                                    construct_params)
{
    GObject *object;
    TrgRemotePrefsDialogPrivate *priv;
    JsonObject *session;
    GtkWidget *notebook, *contentvbox;

    object = G_OBJECT_CLASS
        (trg_remote_prefs_dialog_parent_class)->constructor(type,
                                                            n_construct_properties,
                                                            construct_params);
    priv = TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(object);
    session = trg_client_get_session(priv->client);

    contentvbox = gtk_dialog_get_content_area(GTK_DIALOG(object));

    gtk_window_set_title(GTK_WINDOW(object), _("Remote Preferences"));
    gtk_window_set_transient_for(GTK_WINDOW(object),
                                 GTK_WINDOW(priv->parent));
    gtk_window_set_destroy_with_parent(GTK_WINDOW(object), TRUE);

    gtk_dialog_add_button(GTK_DIALOG(object), _("_Close"),
                          GTK_RESPONSE_CLOSE);
    gtk_dialog_add_button(GTK_DIALOG(object), _("_OK"),
                          GTK_RESPONSE_OK);

    gtk_container_set_border_width(GTK_CONTAINER(object), GUI_PAD);

    gtk_dialog_set_default_response(GTK_DIALOG(object), GTK_RESPONSE_OK);

    g_signal_connect(G_OBJECT(object), "response",
                     G_CALLBACK(trg_remote_prefs_response_cb), NULL);

    notebook = gtk_notebook_new();

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
                             trg_rprefs_generalPage(TRG_REMOTE_PREFS_DIALOG
                                                    (object), session),
                             gtk_label_new(_("General")));

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
                             trg_rprefs_connPage(TRG_REMOTE_PREFS_DIALOG
                                                 (object), session),
                             gtk_label_new(_("Connections")));

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
                             trg_rprefs_bandwidthPage
                             (TRG_REMOTE_PREFS_DIALOG(object), session),
                             gtk_label_new(_("Bandwidth")));

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
                             trg_rprefs_limitsPage(TRG_REMOTE_PREFS_DIALOG
                                                   (object), session),
                             gtk_label_new(_("Limits")));

    gtk_container_set_border_width(GTK_CONTAINER(notebook), GUI_PAD);

    gtk_box_pack_start(GTK_BOX(contentvbox), notebook, TRUE, TRUE, 0);

    return object;
}

static void
trg_remote_prefs_dialog_class_init(TrgRemotePrefsDialogClass * klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    g_type_class_add_private(klass, sizeof(TrgRemotePrefsDialogPrivate));

    object_class->constructor = trg_remote_prefs_dialog_constructor;
    object_class->get_property = trg_remote_prefs_dialog_get_property;
    object_class->set_property = trg_remote_prefs_dialog_set_property;

    g_object_class_install_property(object_class,
                                    PROP_CLIENT,
                                    g_param_spec_pointer("trg-client",
                                                         "TClient",
                                                         "Client",
                                                         G_PARAM_READWRITE
                                                         |
                                                         G_PARAM_CONSTRUCT_ONLY
                                                         |
                                                         G_PARAM_STATIC_NAME
                                                         |
                                                         G_PARAM_STATIC_NICK
                                                         |
                                                         G_PARAM_STATIC_BLURB));

    g_object_class_install_property(object_class,
                                    PROP_PARENT,
                                    g_param_spec_object("parent-window",
                                                        "Parent window",
                                                        "Parent window",
                                                        TRG_TYPE_MAIN_WINDOW,
                                                        G_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY
                                                        |
                                                        G_PARAM_STATIC_NAME
                                                        |
                                                        G_PARAM_STATIC_NICK
                                                        |
                                                        G_PARAM_STATIC_BLURB));
}

static void
trg_remote_prefs_dialog_init(TrgRemotePrefsDialog * self G_GNUC_UNUSED)
{
}

TrgRemotePrefsDialog *trg_remote_prefs_dialog_get_instance(TrgMainWindow *
                                                           parent,
                                                           TrgClient *
                                                           client)
{
    if (instance == NULL) {
        instance =
            g_object_new(TRG_TYPE_REMOTE_PREFS_DIALOG, "parent-window",
                         parent, "trg-client", client, NULL);
    }

    return TRG_REMOTE_PREFS_DIALOG(instance);
}
