/*
 * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "config.h"
#include <glib/gi18n.h>

#include <gio/gio.h>

#include "nautilus-recent-servers.h"

struct _NautilusRecentServers
{
    GObject parent_instance;

    GHashTable *server_infos;

    GFileMonitor *server_list_monitor;

    guint idle_reload_id;

    guint loading : 1;
};

static void        nautilus_recent_servers_set_loading (NautilusRecentServers *self,
                                                        gboolean               loading);

G_DEFINE_FINAL_TYPE (NautilusRecentServers, nautilus_recent_servers, G_TYPE_OBJECT)

enum
{
    PROP_0,
    PROP_LOADING,
    LAST_PROP
};

enum
{
    ADDED,
    CHANGED,
    REMOVED,
    LAST_SIGNAL
};

static GParamSpec *properties[LAST_PROP];
static guint signals[LAST_SIGNAL];

static void
ensure_monitor (NautilusRecentServers *self)
{
    if (self->server_list_monitor != NULL)
    {
        return;
    }

    g_autofree char *filename = g_build_filename (g_get_user_config_dir (), "gtk-4.0", "servers", NULL);
    g_autoptr (GFile) server_list_file = g_file_new_for_path (filename);
    g_autoptr (GError) error = NULL;

    self->server_list_monitor = g_file_monitor_file (server_list_file,
                                                     G_FILE_MONITOR_NONE,
                                                     NULL,
                                                     &error);

    if (error != NULL)
    {
        g_warning ("Cannot monitor server file: %s", error->message);
    }
    else
    {
        g_signal_connect_object (self->server_list_monitor, "changed",
                                 G_CALLBACK (nautilus_recent_servers_force_reload), self,
                                 G_CONNECT_SWAPPED);
    }
}

static GBookmarkFile *
server_list_load (void)
{
    GBookmarkFile *bookmarks;
    GError *error = NULL;
    char *datadir;
    char *filename;

    bookmarks = g_bookmark_file_new ();
    datadir = g_build_filename (g_get_user_config_dir (), "gtk-4.0", NULL);
    filename = g_build_filename (datadir, "servers", NULL);

    g_mkdir_with_parents (datadir, 0700);
    g_bookmark_file_load_from_file (bookmarks, filename, &error);

    if (error)
    {
        if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
        {
            /* only warn if the file exists */
            g_warning ("Unable to open server bookmarks: %s", error->message);
            g_clear_pointer (&bookmarks, g_bookmark_file_free);
        }

        g_clear_error (&error);
    }

    g_free (datadir);
    g_free (filename);

    return bookmarks;
}

static void
server_list_save (GBookmarkFile *bookmarks)
{
    char *filename;

    filename = g_build_filename (g_get_user_config_dir (), "gtk-4.0", "servers", NULL);
    g_bookmark_file_to_file (bookmarks, filename, NULL);
    g_free (filename);
}

void
nautilus_add_recent_server (GFile *file)
{
    GBookmarkFile *bookmarks;
    GFileInfo *info;
    GError *error;
    char *title;
    char *uri;

    GDateTime *now;

    error = NULL;
    bookmarks = server_list_load ();

    if (!bookmarks)
    {
        return;
    }

    uri = g_file_get_uri (file);

    info = g_file_query_info (file,
                              G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
                              G_FILE_QUERY_INFO_NONE,
                              NULL,
                              &error);
    title = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);

    g_bookmark_file_set_title (bookmarks, uri, title);
    now = g_date_time_new_now_utc ();
    g_bookmark_file_set_visited_date_time (bookmarks, uri, now);
    g_date_time_unref (now);
    g_bookmark_file_add_application (bookmarks, uri, NULL, NULL);

    server_list_save (bookmarks);

    g_bookmark_file_free (bookmarks);
    g_clear_object (&info);
    g_free (title);
    g_free (uri);
}

void
nautilus_remove_recent_server (const char *uri)
{
    GBookmarkFile *bookmarks;

    bookmarks = server_list_load ();

    if (!bookmarks)
    {
        return;
    }

    g_bookmark_file_remove_item (bookmarks, uri, NULL);
    server_list_save (bookmarks);

    g_bookmark_file_free (bookmarks);
}

static void
nautilus_recent_servers_finalize (GObject *object)
{
    NautilusRecentServers *self = (NautilusRecentServers *) object;

    g_clear_object (&self->server_list_monitor);
    g_clear_pointer (&self->server_infos, g_hash_table_destroy);

    G_OBJECT_CLASS (nautilus_recent_servers_parent_class)->finalize (object);
}

static void
nautilus_recent_servers_dispose (GObject *object)
{
    NautilusRecentServers *self = (NautilusRecentServers *) object;

    g_clear_handle_id (&self->idle_reload_id, g_source_remove);

    G_OBJECT_CLASS (nautilus_recent_servers_parent_class)->dispose (object);
}

static void
nautilus_recent_servers_get_property (GObject    *object,
                                      guint       prop_id,
                                      GValue     *value,
                                      GParamSpec *pspec)
{
    NautilusRecentServers *self = NAUTILUS_RECENT_SERVERS (object);

    switch (prop_id)
    {
        case PROP_LOADING:
        {
            g_value_set_boolean (value, nautilus_recent_servers_get_loading (self));
            break;
        }

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

static GFileInfo *
server_file_info_new (const char *uri)
{
    GFileInfo *info = g_file_info_new ();
    g_autofree char *random_name = g_dbus_generate_guid ();
    g_autoptr (GIcon) icon = g_themed_icon_new ("folder-remote");
    g_autoptr (GIcon) symbolic_icon = g_themed_icon_new ("folder-remote-symbolic");

    g_file_info_set_name (info, random_name);
    g_file_info_set_icon (info, icon);
    g_file_info_set_symbolic_icon (info, symbolic_icon);
    g_file_info_set_content_type (info, "inode/directory");
    g_file_info_set_file_type (info, G_FILE_TYPE_SHORTCUT);
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL, TRUE);
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE);
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE);
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE);
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);

    g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, uri);

    return info;
}

static int
_date_time_equal_steal_1st (GDateTime *date_time1__to_be_stolen,
                            GDateTime *date_time2)
{
    g_autoptr (GDateTime) date_time1 = date_time1__to_be_stolen;

    return ((date_time1 == NULL && date_time2 == NULL) ||
            g_date_time_equal (date_time1, date_time2));
}

static void
populate_servers_on_idle (gpointer user_data)
{
    NautilusRecentServers *self = NAUTILUS_RECENT_SERVERS (user_data);
    GBookmarkFile *server_list;
    char **uris;
    gsize num_uris;

    self->idle_reload_id = 0;

    server_list = server_list_load ();

    if (!server_list)
    {
        return;
    }

    /* Monitor the file in case it's modified outside this code */
    ensure_monitor (self);

    uris = g_bookmark_file_get_uris (server_list, &num_uris);

    if (!uris)
    {
        g_bookmark_file_free (server_list);
        nautilus_recent_servers_set_loading (self, FALSE);
        return;
    }

    g_autoptr (GList) old_infos = g_hash_table_get_values (self->server_infos);
    g_autoptr (GList) new_infos = NULL;
    g_autoptr (GList) changed_infos = NULL;

    for (gsize i = 0; i < num_uris; i++)
    {
        const gchar *uri = uris[i];
        GFileInfo *info = g_hash_table_lookup (self->server_infos, uri);
        gboolean new = FALSE;

        if (info != NULL)
        {
            old_infos = g_list_remove (old_infos, info);
        }
        else
        {
            new = TRUE;
            info = server_file_info_new (uri);
            g_hash_table_insert (self->server_infos, g_strdup (uri), info);
            new_infos = g_list_prepend (new_infos, info);
        }

        g_autofree char *name = g_bookmark_file_get_title (server_list, uri, NULL);
        GDateTime *added = g_bookmark_file_get_added_date_time (server_list, uri, NULL);
        GDateTime *visited = g_bookmark_file_get_visited_date_time (server_list, uri, NULL);
        GDateTime *modified = g_bookmark_file_get_modified_date_time (server_list, uri, NULL);

        if (new ||
            g_strcmp0 (g_file_info_get_display_name (info), name) != 0 ||
            !_date_time_equal_steal_1st (g_file_info_get_creation_date_time (info), added) ||
            !_date_time_equal_steal_1st (g_file_info_get_access_date_time (info), visited) ||
            !_date_time_equal_steal_1st (g_file_info_get_modification_date_time (info), modified))
        {
            g_file_info_set_display_name (info, name);
            g_file_info_set_creation_date_time (info, added);
            g_file_info_set_access_date_time (info, visited);
            g_file_info_set_modification_date_time (info, modified);

            if (!new)
            {
                changed_infos = g_list_prepend (changed_infos, info);
            }
        }
    }

    if (old_infos != NULL)
    {
        g_signal_emit (self, signals[REMOVED], 0, old_infos);
    }
    if (changed_infos != NULL)
    {
        g_signal_emit (self, signals[CHANGED], 0, changed_infos);
    }
    if (new_infos != NULL)
    {
        g_signal_emit (self, signals[ADDED], 0, new_infos);
    }

    for (GList *l = old_infos; l != NULL; l = l->next)
    {
        const char *old_uri = g_file_info_get_attribute_string (l->data, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);

        g_hash_table_remove (self->server_infos, old_uri);
    }

    nautilus_recent_servers_set_loading (self, FALSE);

    g_strfreev (uris);
    g_bookmark_file_free (server_list);
}

static void
nautilus_recent_servers_class_init (NautilusRecentServersClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->finalize = nautilus_recent_servers_finalize;
    object_class->dispose = nautilus_recent_servers_dispose;
    object_class->get_property = nautilus_recent_servers_get_property;

    properties[PROP_LOADING] =
        g_param_spec_boolean ("loading",
                              "Loading",
                              "Whether the view is loading locations",
                              FALSE,
                              G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);

    g_object_class_install_properties (object_class, LAST_PROP, properties);

    signals[ADDED] = g_signal_new ("added",
                                   G_TYPE_FROM_CLASS (object_class),
                                   G_SIGNAL_RUN_LAST, 0, NULL, NULL,
                                   g_cclosure_marshal_VOID__POINTER,
                                   G_TYPE_NONE, 1, G_TYPE_POINTER);
    signals[CHANGED] = g_signal_new ("changed",
                                     G_TYPE_FROM_CLASS (object_class),
                                     G_SIGNAL_RUN_LAST, 0, NULL, NULL,
                                     g_cclosure_marshal_VOID__POINTER,
                                     G_TYPE_NONE, 1, G_TYPE_POINTER);
    signals[REMOVED] = g_signal_new ("removed",
                                     G_TYPE_FROM_CLASS (object_class),
                                     G_SIGNAL_RUN_LAST, 0, NULL, NULL,
                                     g_cclosure_marshal_VOID__POINTER,
                                     G_TYPE_NONE, 1, G_TYPE_POINTER);
}

static void
nautilus_recent_servers_init (NautilusRecentServers *self)
{
    self->server_infos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
}

NautilusRecentServers *
nautilus_recent_servers_new (void)
{
    return g_object_new (NAUTILUS_TYPE_RECENT_SERVERS, NULL);
}

void
nautilus_recent_servers_force_reload (NautilusRecentServers *self)
{
    if (self->idle_reload_id == 0)
    {
        self->idle_reload_id = g_idle_add_once (populate_servers_on_idle, self);
        nautilus_recent_servers_set_loading (self, TRUE);
    }
}

/* Returns: (transfer full): List of server infos. */
GList *
nautilus_recent_servers_get_infos (NautilusRecentServers *self)
{
    g_return_val_if_fail (NAUTILUS_IS_RECENT_SERVERS (self), FALSE);

    GList *server_infos = g_hash_table_get_values (self->server_infos);

    g_list_foreach (server_infos, (GFunc) g_object_ref, NULL);
    return server_infos;
}

gboolean
nautilus_recent_servers_get_loading (NautilusRecentServers *self)
{
    g_return_val_if_fail (NAUTILUS_IS_RECENT_SERVERS (self), FALSE);

    return self->loading;
}

static void
nautilus_recent_servers_set_loading (NautilusRecentServers *self,
                                     gboolean               loading)
{
    g_return_if_fail (NAUTILUS_IS_RECENT_SERVERS (self));

    if (self->loading != loading)
    {
        self->loading = loading;
        g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LOADING]);
    }
}
