/* urc-gui.c
 *
 * Copyright 2009-2025 Daniele Napolitano <dnax88@gmail.com>
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include "config.h"

#include <glib.h>
#include <glib/gi18n-lib.h>

#include <gtk/gtk.h>

#include <arpa/inet.h>

#include "urc-graph.h"
#include "urc-upnp.h"
#include "urc-gui.h"
#include "urc-application.h"

enum Columns
{
    UPNP_COLUMN_DESC,
    UPNP_COLUMN_PROTOCOL,
    UPNP_COLUMN_INT_PORT,
    UPNP_COLUMN_EXT_PORT,
    UPNP_COLUMN_LOCAL_IP,
    UPNP_COLUMN_REM_IP
};

static const gchar *urc_notification_ids[URC_NOTIFY_COUNT] = {
    [URC_NOTIFY_ROUTER_NEW] = "router-available",
    [URC_NOTIFY_ROUTER_GONE] = "router-unavailable",
    [URC_NOTIFY_NEW_PORT] = "new-port",
};

typedef struct
{
    GtkWidget *window,
              *add_desc,
              *add_ext_port,
              *add_proto_tcp,
              *add_proto_udp,
              *add_local_ip,
              *add_local_port,
              *button_apply,
              *button_cancel,
              *expander;

} AddPortWindow;

typedef struct
{
    GtkBuilder* builder;

    UrcApplication *app;

    GtkWidget *main_window,
              *treeview,
              *router_name_label,
              *router_name_hbox,
              *router_url_label,
              *config_label,
              *wan_status_label,
              *ip_label,
              *net_graph_box,
              *down_rate_label,
              *up_rate_label,
              *total_received_label,
              *total_sent_label,
              *button_remove,
              *button_add,
              *headerbar,
              *refresh_button,
              *network_drawing_area,
              *receiving_color,
              *sending_color;

    GtkMenuButton *menu_button;

    AddPortWindow* add_port_window;

    GtkWidget* info_window;

    GMenuModel *headermenu;

    GActionGroup *actions;

    RouterInfo *router;

} GuiContext;

static GuiContext* gui;

static void
gui_add_port_window_destroy (GtkWidget *widget,
                           gpointer   user_data)
{

    gtk_widget_destroy (gui->add_port_window->window);
    g_free(gui->add_port_window);
}

static void
gui_add_port_window_apply (GtkWidget *button,
                           gpointer   user_data)
{
    GError* error = NULL;
    GtkWidget* spinner;
    gchar const *btn_label;

    // Will be freed after
    PortForwardInfo* port_info = g_new (PortForwardInfo, 1);
    port_info->description = g_strdup( gtk_entry_get_text(GTK_ENTRY(gui->add_port_window->add_desc)) );
    port_info->protocol = g_strdup( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gui->add_port_window->add_proto_tcp)) ? "TCP" : "UDP" );
    port_info->internal_port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON (gui->add_port_window->add_local_port) );
    port_info->external_port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON (gui->add_port_window->add_ext_port) );
    port_info->internal_host = g_strdup( gtk_entry_get_text(GTK_ENTRY(gui->add_port_window->add_local_ip)) );
    port_info->remote_host = "";
    port_info->lease_time = 0;
    port_info->enabled = TRUE;

    // Set spinner on on the apply button and remove temporarily the label
    spinner = gtk_spinner_new();
    btn_label = g_strdup(gtk_button_get_label (GTK_BUTTON(gui->add_port_window->button_apply)));
    gtk_button_set_label (GTK_BUTTON(gui->add_port_window->button_apply), NULL);
    gtk_button_set_image (GTK_BUTTON(gui->add_port_window->button_apply), spinner);
    gtk_button_set_always_show_image (GTK_BUTTON(gui->add_port_window->button_apply), TRUE);
    gtk_spinner_start (GTK_SPINNER(spinner));

    // Try to add the new port mapping.
    if(add_port_mapping(user_data, port_info, &error) != TRUE)
    {
        // We have errors.
        GtkWidget* dialog;
        dialog = gtk_message_dialog_new(GTK_WINDOW(gui->add_port_window->window),
                                        GTK_DIALOG_MODAL,
                                        GTK_MESSAGE_ERROR,
                                        GTK_BUTTONS_OK,
                                        _("Unable to set this port forward"));

        gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog),
                                                "%d: %s", error->code, error->message);
        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
    }

    // Stop the spinner and restore the button label
    gtk_spinner_stop(GTK_SPINNER(spinner));
    gtk_button_set_label(GTK_BUTTON(gui->add_port_window->button_apply), btn_label);
    gtk_button_set_image (GTK_BUTTON(gui->add_port_window->button_apply), NULL);

    if (error == NULL) {
        // No errors, close the dialog.
        gui_add_port_window_destroy(NULL, NULL);
    }
    else {
        g_error_free (error);
    }
}

gboolean
is_valid_ip(const char* ip_str) {
    struct sockaddr_in sa;
    return inet_pton(AF_INET, ip_str, &(sa.sin_addr)) != 0;
}

gboolean
urc_ip_address_entry_is_valid (GtkEditable *editable)
{
    const gchar *text;
    text = gtk_entry_get_text (GTK_ENTRY (editable));
    return text[0] == '\0' || is_valid_ip (text);
}

static void
gui_on_ip_address_entry_changed (GtkWidget *widget)
{
    GtkStyleContext *context;
    context = gtk_widget_get_style_context (widget);

    if (urc_ip_address_entry_is_valid (GTK_EDITABLE(widget))) {
        gtk_style_context_remove_class (context, "error");
    }
    else {
        gtk_style_context_add_class (context, "error");
    }
}

static void
gui_add_port_window_on_port_change (GtkSpinButton *spinbutton,
                                    gpointer       user_data)
{
    guint num = 0;

    // sync values only in "easy" mode
    if(gtk_expander_get_expanded(GTK_EXPANDER(gui->add_port_window->expander)) == FALSE)
    {
        num = atoi (gtk_entry_get_text(GTK_ENTRY(spinbutton)));
        gtk_spin_button_set_value (GTK_SPIN_BUTTON(gui->add_port_window->add_local_port), num);
    }
}

gboolean
utils_char_is_ascii_ip4_address (char character)
{
	return g_ascii_isdigit (character) || character == '.';
}

static void
urc_ip_address_filter_cb (GtkEditable *editable,
                       gchar *text,
                       gint length,
                       gint *position,
                       gpointer user_data)
{
    int i, count = 0;
	gchar *result = g_new (gchar, length+1);

    for (i = 0; i < length; i++) {
		if (utils_char_is_ascii_ip4_address (text[i]))
			result[count++] = text[i];
	}
	result[count] = 0;

    if (count > 0) {
        g_signal_handlers_block_by_func (G_OBJECT (editable),
			                                 G_CALLBACK (urc_ip_address_filter_cb),
			                                 user_data);
        gtk_editable_insert_text (editable, result, count, position);
        g_signal_handlers_unblock_by_func  (G_OBJECT (editable),
			                                 G_CALLBACK (urc_ip_address_filter_cb),
			                                 user_data);
    }

    g_signal_stop_emission_by_name (G_OBJECT (editable), "insert-text");

	g_free (result);
}

static void
gui_check_add_port_fields_valid(GtkEditable *editable)
{
    const gchar* text;
    gboolean form_valid = TRUE;

    // Set error state on the IP field.
    if (GTK_WIDGET(editable) == gui->add_port_window->add_local_ip)
        gui_on_ip_address_entry_changed(gui->add_port_window->add_local_ip);

    text = gtk_entry_get_text(GTK_ENTRY(gui->add_port_window->add_desc));
    if (strlen(text) == 0) form_valid = FALSE;

    if (!urc_ip_address_entry_is_valid (GTK_EDITABLE(gui->add_port_window->add_local_ip))) form_valid = FALSE;

    if (atoi (gtk_entry_get_text(GTK_ENTRY(gui->add_port_window->add_ext_port))) == 0) form_valid = FALSE;

    if (atoi (gtk_entry_get_text(GTK_ENTRY(gui->add_port_window->add_local_port))) == 0) form_valid = FALSE;

    if (form_valid)
        gtk_widget_set_sensitive(gui->add_port_window->button_apply, TRUE);
    else
        gtk_widget_set_sensitive(gui->add_port_window->button_apply, FALSE);

}

static void
gui_create_add_port_window (GtkBuilder* builder)
{
    GError* error = NULL;

    if (!gtk_builder_add_from_resource (builder, URC_RESOURCE_BASE "ui/add-port-window.ui", &error))
    {
        g_error ("Couldn't load builder file: %s", error->message);
        g_error_free (error);
    }

    AddPortWindow* add_port_window = g_new (AddPortWindow, 1);
    add_port_window->window = GTK_WIDGET (gtk_builder_get_object (builder, "add_port_window"));
    g_assert (add_port_window->window != NULL);

    gtk_window_set_transient_for(GTK_WINDOW(add_port_window->window), GTK_WINDOW(gui->main_window));

    add_port_window->add_desc = GTK_WIDGET (gtk_builder_get_object (builder, "add_desc"));
    add_port_window->add_ext_port = GTK_WIDGET (gtk_builder_get_object (builder, "add_ext_port"));
    add_port_window->add_proto_tcp = GTK_WIDGET (gtk_builder_get_object (builder, "add_proto_tcp"));
    add_port_window->add_proto_udp = GTK_WIDGET (gtk_builder_get_object (builder, "add_proto_udp"));
    add_port_window->add_local_ip = GTK_WIDGET (gtk_builder_get_object (builder, "add_local_ip"));
    add_port_window->add_local_port = GTK_WIDGET (gtk_builder_get_object (builder, "add_local_port"));
    add_port_window->button_apply = GTK_WIDGET (gtk_builder_get_object (builder, "button_apply"));
    add_port_window->button_cancel = GTK_WIDGET (gtk_builder_get_object (builder, "button_cancel"));
    add_port_window->expander = GTK_WIDGET (gtk_builder_get_object (builder, "expander1"));

    g_signal_connect(add_port_window->add_ext_port, "changed",
                         G_CALLBACK(gui_add_port_window_on_port_change), NULL);

    g_signal_connect(add_port_window->button_cancel, "clicked",
                         G_CALLBACK(gui_add_port_window_destroy), NULL);

    g_signal_connect(add_port_window->window, "delete-event",
                         G_CALLBACK(gui_add_port_window_destroy), NULL);

    gui->add_port_window = add_port_window;
}

static void
gui_run_add_port_window (GuiContext *gui) {
    gui_create_add_port_window(gui->builder);
    
    gtk_entry_set_text (GTK_ENTRY(gui->add_port_window->add_local_ip), urc_get_local_ip(gui->router));

    gtk_widget_set_sensitive(gui->add_port_window->button_apply, FALSE);

    /* Connect new signal with data */
    g_signal_connect(gui->add_port_window->button_apply, "clicked",
                         G_CALLBACK(gui_add_port_window_apply), gui->router->wan_conn_service);


    // Form validation callbacks
    g_signal_connect_after(gui->add_port_window->add_desc, "changed",
                         G_CALLBACK(gui_check_add_port_fields_valid), NULL);

    g_signal_connect_after(gui->add_port_window->add_ext_port, "value-changed",
                         G_CALLBACK(gui_check_add_port_fields_valid), NULL);

    g_signal_connect_after(gui->add_port_window->add_local_port, "changed",
                         G_CALLBACK(gui_check_add_port_fields_valid), NULL);

    g_signal_connect_after(gui->add_port_window->add_local_ip, "changed",
                         G_CALLBACK(gui_check_add_port_fields_valid), NULL);

    g_signal_connect(gui->add_port_window->add_local_ip, "insert-text",
                         G_CALLBACK(urc_ip_address_filter_cb), NULL);

    gtk_widget_show_all (gui->add_port_window->window);
}

/* Clean the Treeview */
void
gui_clear_ports_list_treeview (void)
{
    GtkTreeModel *model;
    GtkTreeIter   iter;
    gboolean      more;

    if(gui->treeview == NULL)
        return;

    model = gtk_tree_view_get_model (GTK_TREE_VIEW (gui->treeview));
    more = gtk_tree_model_get_iter_first (model, &iter);

    while (more)
        more = gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
}

/* Add a port mapped in the treeview list */
void
gui_add_mapped_ports (const GSList *port_list)
{
    GtkTreeModel *model;
    GtkTreeIter   iter;
    const GSList *port_iter = port_list;
    PortForwardInfo *port_info;

    if(gui->treeview == NULL)
        return;

    model = gtk_tree_view_get_model (GTK_TREE_VIEW (gui->treeview));

    while(port_iter)
    {
        port_info = URC_PORT (port_iter->data);

        gtk_list_store_prepend (GTK_LIST_STORE (model), &iter);
        gtk_list_store_set (GTK_LIST_STORE (model),
                            &iter,
                            UPNP_COLUMN_DESC, port_info->description,
                            UPNP_COLUMN_PROTOCOL, port_info->protocol,
                            UPNP_COLUMN_INT_PORT, port_info->internal_port,
                            UPNP_COLUMN_EXT_PORT, port_info->external_port,
                            UPNP_COLUMN_LOCAL_IP, port_info->internal_host,
                            UPNP_COLUMN_REM_IP, port_info->remote_host,
                            -1);

        port_iter = g_slist_next (port_iter);
    }

}

/* Button remove callback */
static void
on_button_remove_clicked (GuiContext *gui) {
    GtkTreeModel *model;
    GtkTreeIter   iter;
    GtkTreeSelection *selection;
    GError *error;

    model = gtk_tree_view_get_model (GTK_TREE_VIEW (gui->treeview));
    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (gui->treeview));

    if(!gtk_tree_selection_get_selected(selection, NULL, &iter))
    {
        /* no selection */
        gtk_widget_set_sensitive(gui->button_remove, FALSE);
        return;
    }

    PortForwardInfo* port_info = g_new (PortForwardInfo, 1);

    gtk_tree_model_get(model, &iter,
                       UPNP_COLUMN_PROTOCOL, &port_info->protocol,
                       UPNP_COLUMN_EXT_PORT, &port_info->external_port,
                       UPNP_COLUMN_REM_IP, &port_info->remote_host,
                       -1);

    if (delete_port_mapped (gui->router->wan_conn_service, port_info, &error) != TRUE)
    {
        // We have errors.
        GtkWidget* dialog;

        dialog = gtk_message_dialog_new(GTK_WINDOW(gui->main_window),
                                        GTK_DIALOG_MODAL,
                                        GTK_MESSAGE_ERROR,
                                        GTK_BUTTONS_OK,
                                        _("Unable to remove this port forward"));

        gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog),
                                                "%d: %s", error->code, error->message);
        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
        g_error_free (error);
    }
    else {
        // delete row from the local list
        gtk_list_store_remove(GTK_LIST_STORE(model), &iter);

        // Deselect any remaining items.
        gtk_tree_selection_unselect_all (gtk_tree_view_get_selection(GTK_TREE_VIEW (gui->treeview)));
    }

    // Data owned by GtkTreeModel, not free all the strings inside.
    g_free (port_info);
}

static gboolean
gui_on_treeview_selection (GtkTreeSelection *selection,
                           GtkTreeModel     *model,
                           GtkTreePath      *path,
                           gboolean          path_currently_selected,
                           gpointer          userdata)
{

    if (!path_currently_selected)
        gtk_widget_set_sensitive(gui->button_remove, TRUE);

    else
        gtk_widget_set_sensitive(gui->button_remove, FALSE);


    return TRUE; /* allow selection state to change */
}

void
urc_copy_selected_row_to_clipboard (GtkTreeView *tree_view) {
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    GtkTreeIter iter;

    selection = gtk_tree_view_get_selection(tree_view);

    if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
        PortForwardInfo* port_info = g_new (PortForwardInfo, 1);
        gchar *clipboard_text;

        gtk_tree_model_get(model, &iter,
                           UPNP_COLUMN_DESC, &port_info->description,
                           UPNP_COLUMN_PROTOCOL, &port_info->protocol,
                           UPNP_COLUMN_INT_PORT, &port_info->internal_port,
                           UPNP_COLUMN_EXT_PORT, &port_info->external_port,
                           UPNP_COLUMN_LOCAL_IP, &port_info->internal_host,
                           UPNP_COLUMN_REM_IP, &port_info->remote_host,
                           -1);

        clipboard_text = g_strdup_printf ("%s\n%s %s:%d -> %s:%d",
                                          port_info->description,
                                          port_info->protocol,
                                          strlen(port_info->remote_host) > 0 ? port_info->remote_host : "0.0.0.0",
                                          port_info->external_port,
                                          port_info->internal_host,
                                          port_info->internal_port);

        GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
        gtk_clipboard_set_text(clipboard, clipboard_text, -1);

        g_free(port_info);
        g_free(clipboard_text);
    }
}


gboolean
on_tree_view_button_press(GtkWidget *treeview, GdkEventButton *event, gpointer user_data) {
    if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
        GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
        GtkTreePath *path;

        if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview),
                                          (gint) event->x, (gint) event->y,
                                          &path, NULL, NULL, NULL)) {
            gtk_tree_selection_unselect_all(selection);
            gtk_tree_selection_select_path(selection, path);
            gtk_tree_path_free(path);
        }

        // Create the menu one time only
        GtkWidget *menu = g_object_get_data(G_OBJECT(treeview), "context-menu");
        if (!menu) {
            menu = gtk_menu_new();
            GtkWidget *copy_item = gtk_menu_item_new_with_label(_("Copy text"));

            g_signal_connect_swapped(copy_item, "activate",
                                     G_CALLBACK(urc_copy_selected_row_to_clipboard),
                                     treeview);

            gtk_menu_shell_append(GTK_MENU_SHELL(menu), copy_item);
            gtk_widget_show_all(menu);
            g_object_set_data(G_OBJECT(treeview), "context-menu", menu);
        }

        gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent*)event);
        return TRUE;
    }
    return FALSE;
}


/* Initialize model for Treeview */
static void
gui_init_treeview()
{
    GtkTreeModel *model;
    GtkTreeSelection *selection;
    int i;
    char *headers[6] = {_("Description"),
                        _("Protocol"),
                        _("Int. Port"),
                        _("Ext. Port"),
                        _("Local IP"),
                        /*_("Remote IP"),*/
                        NULL };

    GtkListStore *store = gtk_list_store_new (6,
                                G_TYPE_STRING,  /* Description       */
                                G_TYPE_STRING,  /* Protocol          */
                                G_TYPE_UINT,    /* Internal port     */
                                G_TYPE_UINT,    /* External port     */
                                G_TYPE_STRING,  /* Internal host     */
                                G_TYPE_STRING); /* Remove host Value */

    model = GTK_TREE_MODEL (store);

    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (gui->treeview));
    g_assert (selection != NULL);

    for (i = 0; headers[i] != NULL; i++) {
        GtkCellRenderer   *renderer;
        GtkTreeViewColumn *column;

        column = gtk_tree_view_column_new ();

        renderer = gtk_cell_renderer_text_new ();
        gtk_tree_view_column_pack_end (column, renderer, TRUE);
        gtk_tree_view_column_set_title (column, headers[i]);

        gtk_tree_view_column_add_attribute (column,
                                        renderer,
                                        "text", i);


        gtk_tree_view_column_set_sort_column_id(column, i);

        gtk_tree_view_column_set_sizing(column,
                                        GTK_TREE_VIEW_COLUMN_FIXED);

        gtk_tree_view_insert_column (GTK_TREE_VIEW (gui->treeview),
                                     column, -1);

        gtk_tree_view_column_set_sizing(column,
                                        GTK_TREE_VIEW_COLUMN_AUTOSIZE);

        gtk_tree_view_column_set_resizable (column, TRUE);
    }

    gtk_tree_view_set_model (GTK_TREE_VIEW (gui->treeview),
                             model);
    gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);

    gtk_tree_selection_set_select_function(selection, gui_on_treeview_selection, NULL, NULL);

    g_signal_connect(gui->treeview, "button-press-event", G_CALLBACK(on_tree_view_button_press), NULL);
}

void
gui_disable_total_received()
{
   if(gui->total_received_label == NULL)
        return;

    gtk_widget_set_sensitive(gui->total_received_label, FALSE);
}

void
gui_set_total_received (const unsigned int total_received)
{
    gchar *str;
    str = g_format_size_full(total_received, G_FORMAT_SIZE_IEC_UNITS);

    if(gui->total_received_label == NULL)
        return;

    gtk_label_set_text (GTK_LABEL(gui->total_received_label), str);
    g_free(str);

    gtk_widget_set_sensitive(gui->total_received_label, TRUE);
}

void
gui_disable_total_sent()
{
   if(gui->total_sent_label == NULL)
        return;

    gtk_widget_set_sensitive(gui->total_sent_label, FALSE);
}

void
gui_set_total_sent (const unsigned int total_sent)
{
    gchar *str;
    str = g_format_size_full(total_sent, G_FORMAT_SIZE_IEC_UNITS);

    if(gui->total_sent_label == NULL)
        return;

    gtk_label_set_text (GTK_LABEL(gui->total_sent_label), str);
    g_free(str);

    gtk_widget_set_sensitive(gui->net_graph_box, TRUE);
}

void
gui_disable_download_speed()
{
    if(gui->down_rate_label == NULL)
        return;

    gtk_widget_set_sensitive(gui->down_rate_label, FALSE);
    gtk_label_set_text (GTK_LABEL(gui->down_rate_label), "—" );
}

/* Update router speeds, values in KiB/sec */
void
gui_set_download_speed(const gdouble down_speed)
{
    gchar *bytes, *str;
    SpeedValue *speed = g_new (SpeedValue, 1);
    speed->speed = down_speed;
    speed->valid = TRUE;

    update_download_graph_data(speed);

    bytes = g_format_size_full(down_speed * 1024, G_FORMAT_SIZE_IEC_UNITS);
    str = g_strdup_printf("%s/s", bytes);
    g_free(bytes);

    if(gui->down_rate_label == NULL)
        return;

    gtk_label_set_text (GTK_LABEL(gui->down_rate_label), str);
    g_free(str);

    gtk_widget_set_sensitive(gui->net_graph_box, TRUE);

}

void
gui_disable_upload_speed()
{
    if(gui->up_rate_label == NULL)
        return;

    gtk_widget_set_sensitive(gui->up_rate_label, FALSE);
    gtk_label_set_text (GTK_LABEL(gui->up_rate_label), "—" );
}

/* Update router speeds, values in KiB/sec */
void
gui_set_upload_speed(const gdouble up_speed)
{
    gchar *bytes, *str;
    SpeedValue *speed = g_new(SpeedValue, 1);

    speed->speed = up_speed;
    speed->valid = TRUE;

    update_upload_graph_data(speed);

    bytes = g_format_size_full(up_speed * 1024, G_FORMAT_SIZE_IEC_UNITS);
    str = g_strdup_printf("%s/s", bytes);
    g_free(bytes);

    if(gui->up_rate_label == NULL)
        return;

    gtk_label_set_text (GTK_LABEL(gui->up_rate_label), str);
    g_free(str);

    gtk_widget_set_sensitive(gui->up_rate_label, TRUE);

}

void
gui_update_graph()
{
    if(gui->network_drawing_area == NULL)
        return;

    gtk_widget_queue_draw(gui->network_drawing_area);
}

/* Set WAN state label */
void
gui_set_conn_status(const gchar *state)
{
    gchar* str = NULL;

    if (gui->wan_status_label == NULL)
        return;

    if (g_strcmp0("Connected", state) == 0)
        str = g_strdup_printf( "<b>%s</b> <span color=\"#009000\"><b>%s</b></span>", _("WAN status:"), _("Connected") );
    else if (g_strcmp0("Disconnected", state) == 0)
        str = g_strdup_printf( "<b>%s</b> <span color=\"#900000\"><b>%s</b></span>", _("WAN status:"), _("Disconnected"));
    else if (g_strcmp0("Connecting", state) == 0)
        str = g_strdup_printf( "<b>%s</b> <span color=\"#0000a0\"><b>%s</b></span>", _("WAN status:"), _("Connecting"));
    else if (g_strcmp0("Disconnecting", state) == 0)
        str = g_strdup_printf( "<b>%s</b> <span color=\"#c04000\"><b>%s</b></span>", _("WAN status:"), _("Disconnecting"));
    else
        str = g_strdup_printf( "<b>%s</b> %s", _("WAN status:"), state);

    gtk_label_set_markup (GTK_LABEL(gui->wan_status_label), str);

    gtk_widget_set_sensitive(gui->wan_status_label, TRUE);

}

/* Set WAN state label unknown */
void
gui_disable_conn_status()
{
    gchar* str = NULL;

    if(gui->wan_status_label == NULL)
        return;

    str = g_strdup_printf("<b>%s</b> %s", _("WAN status:"), _("unknown"));

    gtk_label_set_markup (GTK_LABEL(gui->wan_status_label), str);

    g_free(str);

}

/* Set external IP address */
void
gui_set_ext_ip(const gchar *ip)
{
    gchar* str;

    if(gui->ip_label == NULL)
        return;

    if (ip == NULL || strlen(ip) == 0)
        str = g_strdup_printf( "<b>%s</b> %s", _("IP:"), _("none"));
    else
        str = g_strdup_printf( "<b>%s</b> %s", _("IP:"), ip);

    gtk_label_set_markup (GTK_LABEL(gui->ip_label), str);
    g_free(str);

    gtk_widget_set_sensitive(gui->ip_label, TRUE);

}

/* Set external IP address */
void
gui_disable_ext_ip()
{
    gchar* str = NULL;

    if(gui->ip_label == NULL)
        return;

    str = g_strdup_printf( "<b>%s</b> %s", _("IP:"), _("unknown"));

    gtk_label_set_markup (GTK_LABEL(gui->ip_label), str);

    g_free(str);
}

/* Set router informations */
void
gui_set_router_info (RouterInfo *router)
{
    gchar* str;
    GAction* action;

    if(gui == NULL || gui->main_window == NULL)
        return;

    gui->router = router;

    str = g_strdup_printf (_("Connected to %s"), router->device_ip);
    gtk_header_bar_set_subtitle(GTK_HEADER_BAR(gui->headerbar), str);
    g_free(str);

    gtk_label_set_text (GTK_LABEL(gui->router_name_label), router->friendly_name);

    gtk_widget_set_sensitive(gui->router_name_hbox, TRUE);

    gtk_label_set_text (GTK_LABEL(LOOKUP_WIDGET("friendly_name_label")), router->friendly_name);
    gtk_label_set_text (GTK_LABEL(LOOKUP_WIDGET("manufacturer_label")), router->manufacturer);

    str = g_strdup_printf( "<a href=\"%s\">%s</a>", router->manufacturer_website, router->manufacturer_website);
    gtk_label_set_markup (GTK_LABEL(LOOKUP_WIDGET("manufacturer_website_label")), str);
    g_free(str);

    gtk_label_set_text (GTK_LABEL(LOOKUP_WIDGET("model_description_label")), router->model_description);
    gtk_label_set_text (GTK_LABEL(LOOKUP_WIDGET("model_name_label")), router->model_name);
    gtk_label_set_text (GTK_LABEL(LOOKUP_WIDGET("model_number_label")), router->model_number);

    str = g_strdup_printf( "<a href=\"%s\">%s</a>", router->model_url, router->model_url);
    gtk_label_set_markup (GTK_LABEL(LOOKUP_WIDGET("model_url_label")), str);
    g_free(str);

    gtk_label_set_text (GTK_LABEL(LOOKUP_WIDGET("upc_label")), router->upc);
    gtk_label_set_text (GTK_LABEL(LOOKUP_WIDGET("serial_number_label")), router->serial_number);

    str = g_strdup_printf( "<a href=\"%s\">%s</a>", router->device_descriptor, router->device_descriptor);
    gtk_label_set_markup (GTK_LABEL(LOOKUP_WIDGET("xml_descriptor_label")), str);
    g_free(str);

    if(router->http_address != NULL) {

        str = g_strdup_printf( "<a href=\"%s\">%s</a>", router->http_address, router->http_address);
        gtk_label_set_markup (GTK_LABEL(gui->router_url_label), str);
        g_free(str);

        gtk_widget_set_sensitive(gui->router_url_label, TRUE);
        gtk_widget_set_sensitive(gui->config_label, TRUE);

        gtk_widget_set_tooltip_text(gui->router_url_label, _("Open the router config in the default browser"));

    } else {

        gtk_label_set_label(GTK_LABEL(gui->router_url_label), _("not available"));
        gtk_widget_set_sensitive(gui->config_label, FALSE);
    }

    // Enable the menu item to open the XML descriptor.
    action = g_action_map_lookup_action (G_ACTION_MAP (gui->actions), "open-router-info");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);

}

void
gui_send_notification (URCNotificationType n_type, gpointer user_data) {
    GApplication *app = g_application_get_default ();
    GSettings *settings = urc_application_get_settings (URC_APPLICATION(app));

    // Skip if notifications are disabled
    if (!g_settings_get_boolean (settings, "notifications"))
        return;

    const gchar *n_title;
    gchar *n_body;

    switch (n_type) {
        case URC_NOTIFY_ROUTER_NEW:
            n_title = _("New router detected!");
            n_body = g_strdup_printf ("%s on %s",
                                      URC_ROUTER_INFO(user_data)->friendly_name,
                                      URC_ROUTER_INFO(user_data)->device_ip);
            break;

        case URC_NOTIFY_ROUTER_GONE:
            n_title = _("Router gone away");
            n_body = g_strdup_printf ("%s on %s",
                                      URC_ROUTER_INFO(user_data)->friendly_name,
                                      URC_ROUTER_INFO(user_data)->device_ip);
            break;

        case URC_NOTIFY_NEW_PORT:
            n_title = _("New port forwarding added");
            n_body = g_strdup_printf ("%s\n%s %s:%d -> %s:%d",
                                      URC_PORT(user_data)->description,
                                      URC_PORT(user_data)->protocol,
                                      strlen(URC_PORT(user_data)->remote_host) > 0 ? URC_PORT(user_data)->remote_host : "0.0.0.0",
                                      URC_PORT(user_data)->external_port,
                                      URC_PORT(user_data)->internal_host,
                                      URC_PORT(user_data)->internal_port);
            break;

        default:
            // Do nothing

    }

    GNotification* notify = g_notification_new (n_title);
    g_notification_set_body (notify, n_body);
    g_application_send_notification (app, urc_notification_ids[n_type], notify);
    g_object_unref (notify);
    g_free(n_body);
}

/* updates all values */
static void
on_refresh_activate_cb (GuiContext *gui) {
    urc_upnp_refresh_data (URC_ROUTER_INFO(gui->router));
    gtk_widget_set_sensitive(gui->button_remove, FALSE);
}


void
gui_activate_buttons() {
    gtk_widget_set_sensitive(gui->refresh_button, TRUE);
    gtk_widget_set_sensitive(gui->button_add, TRUE);
}

void
gui_disable()
{
    if(gui == NULL || gui->main_window == NULL)
        return;

    gchar *str;
    GAction *action;

    gui_clear_ports_list_treeview();

    gtk_label_set_text (GTK_LABEL(gui->router_name_label), _("not available"));
    gtk_widget_set_sensitive(gui->router_name_hbox, FALSE);

    str = g_strdup_printf("<b>%s</b> %s", _("WAN status:"), _("not available") );
    gtk_label_set_markup (GTK_LABEL(gui->wan_status_label), str);
    gtk_widget_set_sensitive(gui->wan_status_label, FALSE);
    g_free(str);

    str = g_strdup_printf("<b>%s</b> %s", _("IP:"), _("not available") );
    gtk_label_set_markup (GTK_LABEL(gui->ip_label), str);
    gtk_widget_set_sensitive(gui->ip_label, FALSE);
    g_free(str);

    gtk_label_set_text (GTK_LABEL(gui->down_rate_label), "—");
    gtk_label_set_text (GTK_LABEL(gui->up_rate_label), "—");
    gtk_label_set_text (GTK_LABEL(gui->total_received_label), "—");
    gtk_label_set_text (GTK_LABEL(gui->total_sent_label), "—");
    gtk_widget_set_sensitive(gui->net_graph_box, FALSE);

    gtk_label_set_text (GTK_LABEL(gui->router_url_label), _("not available"));
    gtk_widget_set_sensitive(gui->router_url_label, FALSE);

    gtk_widget_set_sensitive(gui->button_add, FALSE);
    gtk_widget_set_sensitive(gui->button_remove, FALSE);

    gtk_widget_set_sensitive(gui->config_label, FALSE);

    gtk_widget_set_sensitive(gui->refresh_button, FALSE);

    action = g_action_map_lookup_action (G_ACTION_MAP (gui->actions), "open-router-info");
    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);

    gtk_header_bar_set_subtitle(GTK_HEADER_BAR(gui->headerbar), _("Devices discovery started…"));

    urc_disable_graph();
    gui_update_graph();
}

/* Menu router_info activate callback */
static void
on_router_info_activate_cb (GSimpleAction *simple, GVariant *parameter, gpointer user_data)
{
  gtk_widget_show_all(gui->info_window);
}

static void
gui_create_info_window (GtkBuilder* builder)
{
    GError* error = NULL;

    if (!gtk_builder_add_from_resource (gui->builder, URC_RESOURCE_BASE "ui/info-window.ui", &error))
    {
        g_error ("Couldn't load builder file: %s", error->message);
        g_error_free (error);
    }

    gui->info_window = GTK_WIDGET (gtk_builder_get_object (builder, "info_window"));
    g_assert (gui->info_window != NULL);

    gtk_window_set_transient_for(GTK_WINDOW(gui->info_window), GTK_WINDOW(gui->main_window));

    g_signal_connect(gui->info_window, "delete-event",
                         G_CALLBACK(gtk_widget_hide_on_delete), NULL);
}


/* Menu About activate callback */
static void
on_about_activate_cb (GSimpleAction *simple, GVariant *parameter, gpointer user_data)
{
    gchar* authors[] = {
        "Daniele Napolitano <dnax88@gmail.com>",
        "Giuseppe Cicalini <cicone@gmail.com> (basic cURL code)",
        NULL
    };

    gchar* artists[] = {
        "Frédéric Bellaiche http://www.quantum-bits.org",
        NULL
    };

    /* Feel free to put your names here translators :-) */
    gchar* translators = _("translator-credits");

    gtk_show_about_dialog (GTK_WINDOW(gui->main_window),
        "authors", authors,
        "artists", artists,
        "translator-credits", strcmp("translator-credits", translators) ? translators : NULL,
        "comments", _("A simple program that manages UPnP compliant routers"),
        "copyright", "Copyright © 2009-2021 Daniele Napolitano \"DnaX\"",
        "version", VERSION,
        "license-type", GTK_LICENSE_GPL_3_0,
        "website", "https://gitlab.gnome.org/DnaX/upnp-router-control",
        "logo-icon-name", "org.upnproutercontrol.UPnPRouterControl",
        NULL);
}


static void
gui_destroy(gpointer app)
{
    g_print("* Destroying GUI...\n");

    gui_disable();

    // Remove all the notifications
    for (int i = 0; i < URC_NOTIFY_COUNT; ++i) {
        g_application_withdraw_notification(G_APPLICATION(app), urc_notification_ids[i]);
    }

    gtk_widget_destroy(gui->info_window);
    gtk_widget_destroy(gui->main_window);

    gui->main_window = NULL;

    g_free(gui);
}

void
gui_on_colors_change (GuiContext *gui) {
    GdkRGBA color;
    gchar *color_string;
    GSettings *settings = urc_application_get_settings(gui->app);

    // Download speed color
    gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (gui->receiving_color),
                                &color);
    urc_graph_set_receiving_color(color);
    
    // Saving setting
    color_string = gdk_rgba_to_string(&color);
    g_settings_set_string(settings, "graph-receiving-color", color_string);
    g_free (color_string);

    // Upload speed color
    gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (gui->sending_color),
                                &color);
    urc_graph_set_sending_color(color);

    // Saving setting
    color_string = gdk_rgba_to_string(&color);
    g_settings_set_string(settings, "graph-sending-color", color_string);
    g_free (color_string);

    gui_update_graph();
}

GtkWidget*
urc_gui_init (UrcApplication *app)
{
    GtkBuilder* builder;
    GError* error = NULL;
    GSettings *settings = urc_application_get_settings(app);

    g_print("* Initializing GUI...\n");

    gui = g_new (GuiContext, 1);

    gui->app = app;

    gui->builder = gtk_builder_new ();
    if (!gtk_builder_add_from_resource (gui->builder, URC_RESOURCE_BASE "ui/main.ui", &error))
    {
        g_error ("Couldn't load builder file: %s", error->message);
        g_error_free (error);
    }

    gui->main_window = GTK_WIDGET (gtk_builder_get_object (gui->builder, "main_window"));
    g_assert (gui->main_window != NULL);

    gui->treeview = GTK_WIDGET (gtk_builder_get_object (gui->builder, "treeview1"));

    gui->router_name_label = GTK_WIDGET (gtk_builder_get_object (gui->builder, "router_name_label"));
    gui->router_name_hbox = GTK_WIDGET (gtk_builder_get_object (gui->builder, "hbox_name"));

    gui->router_url_label = GTK_WIDGET (gtk_builder_get_object (gui->builder, "router_url_label"));
    gui->config_label = GTK_WIDGET (gtk_builder_get_object (gui->builder, "label_config"));
    gui->wan_status_label = GTK_WIDGET (gtk_builder_get_object (gui->builder, "wan_status_label"));
    gui->ip_label = GTK_WIDGET (gtk_builder_get_object (gui->builder, "ip_label"));
    gui->network_drawing_area = GTK_WIDGET (gtk_builder_get_object (gui->builder, "network_graph"));

    // Network data labels.
    gui->net_graph_box = GTK_WIDGET (gtk_builder_get_object (gui->builder, "net_graph_box"));
    gui->down_rate_label = GTK_WIDGET (gtk_builder_get_object (gui->builder, "down_rate_label"));
    gui->up_rate_label = GTK_WIDGET (gtk_builder_get_object (gui->builder, "up_rate_label"));
    gui->total_received_label = GTK_WIDGET (gtk_builder_get_object (gui->builder, "total_received_value"));
    gui->total_sent_label = GTK_WIDGET (gtk_builder_get_object (gui->builder, "total_sent_value"));

    // Color buttons.
    gui->receiving_color = GTK_WIDGET (gtk_builder_get_object (gui->builder, "receiving_color"));
    gui->sending_color = GTK_WIDGET (gtk_builder_get_object (gui->builder, "sending_color"));

    // Port management buttons.
    gui->button_add = GTK_WIDGET (gtk_builder_get_object (gui->builder, "button_add"));
    gui->button_remove = GTK_WIDGET (gtk_builder_get_object (gui->builder, "button_remove"));

    gui->headerbar = GTK_WIDGET (gtk_builder_get_object (gui->builder, "headerbar"));
    gui->refresh_button = GTK_WIDGET (gtk_builder_get_object (gui->builder, "refresh_button"));
    gui->menu_button = GTK_MENU_BUTTON (gtk_builder_get_object (gui->builder, "menu_button"));

    gui_create_info_window(gui->builder);

    // Loads graph color settings
    gchar *color_str = g_settings_get_string (settings, "graph-receiving-color");
    GdkRGBA color;
    gdk_rgba_parse (&color, color_str);
    gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (gui->receiving_color),
                                &color);
    urc_graph_set_receiving_color(color);
    g_free (color_str);

    color_str = g_settings_get_string (settings, "graph-sending-color");
    gdk_rgba_parse (&color, color_str);
    gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (gui->sending_color),
                                &color);
    urc_graph_set_sending_color(color);
    g_free (color_str);

    // Refresh button
    g_signal_connect_swapped (gui->refresh_button, "clicked",
                     G_CALLBACK(on_refresh_activate_cb), gui);

    // Add/remove buttons
    g_signal_connect_swapped (gui->button_add, "clicked",
                     G_CALLBACK(gui_run_add_port_window), gui);
    g_signal_connect_swapped (gui->button_remove, "clicked",
                     G_CALLBACK(on_button_remove_clicked), gui);

    // Color buttons
    g_signal_connect_swapped (G_OBJECT(gui->receiving_color), "color-set",
                     G_CALLBACK(gui_on_colors_change), gui);

    g_signal_connect_swapped (G_OBJECT(gui->sending_color), "color-set",
                     G_CALLBACK(gui_on_colors_change), gui);

    g_signal_connect_swapped (G_OBJECT(gui->main_window), "delete-event",
                     G_CALLBACK(gui_destroy), app);

    gtk_icon_theme_add_resource_path (gtk_icon_theme_get_default (),
                                    URC_RESOURCE_BASE"/icons");

    builder = gtk_builder_new ();
    if (!gtk_builder_add_from_resource (builder, URC_RESOURCE_BASE "ui/headerbar-menu.ui", &error))
    {
        g_error ("Couldn't load builder file: %s", error->message);
        g_error_free (error);
    }

    gui->headermenu =  G_MENU_MODEL(gtk_builder_get_object (builder, "headermenu"));

    // Menu actions.
    const GActionEntry entries[] = {
        { "about", on_about_activate_cb },
        { "open-router-info", on_router_info_activate_cb }
    };

    gui->actions = G_ACTION_GROUP( g_simple_action_group_new () );
    g_action_map_add_action_entries (G_ACTION_MAP (gui->actions), entries, G_N_ELEMENTS (entries), gui->main_window);
    gtk_widget_insert_action_group (gui->main_window, "app", gui->actions);

    GActionGroup *win_actions = G_ACTION_GROUP( g_simple_action_group_new () );
    GAction *action = g_settings_create_action(settings, "notifications");
    g_action_map_add_action (G_ACTION_MAP (win_actions), action);
    gtk_widget_insert_action_group (gui->main_window, "win", win_actions);

    gtk_menu_button_set_menu_model(gui->menu_button, gui->headermenu);

    g_object_unref (G_OBJECT (builder));

    gui_init_treeview();

    gui_disable();

    urc_init_network_graph(gui->network_drawing_area);

    g_print("* Showing GUI...\n");

    gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(gui->main_window));
    gtk_widget_show_all(gui->main_window);

    return gui->main_window;
}

