/*
 * Copyright (C) 2015 Canonical Ltd.
 * Copyright (C) 2024 UBports Foundation
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; version 3.
 *
 * This library 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 Lesser General Public License
 * along with this library; if not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
 *
 */

#include "e-source-lomiri.h"

#include <libebackend/libebackend.h>
#include <libaccounts-glib/libaccounts-glib.h>

/* Standard GObject macros */
#define E_TYPE_LOMIRI_SOURCES \
    (e_lomiri_sources_get_type ())
#define E_LOMIRI_SOURCES(obj) \
    (G_TYPE_CHECK_INSTANCE_CAST \
    ((obj), E_TYPE_LOMIRI_SOURCES, ELomiriSources))


typedef struct _ELomiriSources ELomiriSources;
typedef struct _ELomiriSourcesClass ELomiriSourcesClass;

struct _ELomiriSources {
    EExtension parent;

    AgManager *ag_manager;
    /* AgAccountId -> ESource UID */
    GHashTable *loa_to_eds;
};

struct _ELomiriSourcesClass {
    EExtensionClass parent_class;
};


/* Module Entry Points */
void e_module_load (GTypeModule *type_module);
void e_module_unload (GTypeModule *type_module);

/* Forward Declarations */
GType e_lomiri_sources_get_type (void);

G_DEFINE_TYPE (
    ELomiriSources,
    e_lomiri_sources,
    E_TYPE_EXTENSION)

static ESourceRegistryServer *
lomiri_sources_get_server (ELomiriSources *extension)
{
    EExtensible *extensible;

    extensible = e_extension_get_extensible (E_EXTENSION (extension));

    return E_SOURCE_REGISTRY_SERVER (extensible);
}

static void
lomiri_sources_remove_collection (ELomiriSources *extension,
                                  ESource *source)
{
    GError *local_error = NULL;
    ESourceLomiri *lomiri_ext;

    g_debug("lomiri_sources_remove_collection: %s", e_source_get_display_name(source));
    lomiri_ext = e_source_get_extension (source, E_SOURCE_EXTENSION_LOMIRI);
    if (e_source_lomiri_get_autoremove (lomiri_ext)) {
        /* This removes the entire subtree rooted at source.
         * Deletes the corresponding on-disk key files too. */
        e_source_remove_sync (source, NULL, &local_error);

        if (local_error != NULL) {
            g_warning ("%s: %s", G_STRFUNC, local_error->message);
            g_error_free (local_error);
        }
    } else {
        g_debug("Source not marked to auto-remove");
    }
}

static void
lomiri_sources_config_source (ELomiriSources *extension,
                              ESource *source,
                              AgAccount *ag_account)
{
    ESourceExtension *source_extension;
    const gchar *account_name, *source_name;

    g_debug("CONFIGURE SOURCE: %s,%s", e_source_get_display_name(source),
            e_source_get_uid(source));

    account_name = ag_account_get_display_name (ag_account);
    source_name = e_source_get_display_name (source);
    if (g_strcmp0 (account_name, source_name) == 0) {
        g_object_bind_property (
            ag_account, "display-name",
            source, "display-name",
            G_BINDING_DEFAULT);
    }

    g_object_bind_property (
        ag_account, "enabled",
        source, "enabled",
        G_BINDING_SYNC_CREATE);

    source_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_LOMIRI);

    g_object_bind_property (
        ag_account, "id",
        source_extension, "account-id",
        G_BINDING_SYNC_CREATE);
}

static void
lomiri_sources_account_deleted_cb (AgManager *ag_manager,
                                   AgAccountId ag_account_id,
                                   ELomiriSources *extension)
{
    ESourceRegistryServer *server;
    GSList *eds_id_list;
    GSList *link;
    GQueue trash = G_QUEUE_INIT;

    if (ag_account_id == 0) {
        return;
    }

    server = lomiri_sources_get_server (extension);

    eds_id_list = g_hash_table_lookup (extension->loa_to_eds,
                                       GUINT_TO_POINTER (ag_account_id));

    g_debug("Sources registered for account: %d", g_slist_length (eds_id_list));

    for (link = eds_id_list; link != NULL; link = g_slist_next (link)) {
        const gchar *source_uid = link->data;

        ESource *source = e_source_registry_server_ref_source (server, source_uid);
        if (source != NULL) {
            g_debug ("Source selected to remove: %s", e_source_get_display_name (source));
            g_queue_push_tail (&trash, source);
        }
    }

    // destroy source id list
    if (eds_id_list) {
        g_slist_free_full (eds_id_list, g_free);
        g_hash_table_remove (extension->loa_to_eds,
                             GUINT_TO_POINTER (ag_account_id));
    }

    /* Empty the trash. */
    while (!g_queue_is_empty (&trash)) {
        ESource *source = g_queue_pop_head (&trash);
        lomiri_sources_remove_collection (extension, source);
        g_object_unref (source);
    }
}

static gboolean
lomiri_sources_register_source (ELomiriSources *extension,
                                ESource *source)
{
    ESourceLomiri *lomiri_ext;
    AgAccountId ag_account_id;
    AgAccount *ag_account = NULL;

    g_debug("Register new source: %s/%s", e_source_get_display_name(source),
            e_source_get_uid(source));

    if (!e_source_has_extension (source, E_SOURCE_EXTENSION_LOMIRI)) {
        g_debug("\tSource does not have lomiri extension!");
        return FALSE;
    }

    lomiri_ext = e_source_get_extension (source, E_SOURCE_EXTENSION_LOMIRI);
    ag_account_id = e_source_lomiri_get_account_id (lomiri_ext);
    if (ag_account_id > 0) {
        ag_account = ag_manager_get_account (extension->ag_manager,
                                             ag_account_id);
    } else {
        // accept sources with empty account
        return TRUE;
    }

    if (ag_account) {
        GSList *eds_id_list;
        GSList *match;
        const gchar *source_uid;

        eds_id_list = g_hash_table_lookup (extension->loa_to_eds,
                                           GUINT_TO_POINTER (ag_account_id));

        source_uid = e_source_get_uid (source);
        match = g_slist_find(eds_id_list, source_uid);
        if (match) {
            g_object_unref (ag_account);
            g_debug ("Source Already registered");
            return FALSE;
        }

        eds_id_list = g_slist_append (eds_id_list, g_strdup (source_uid));
        g_hash_table_insert (extension->loa_to_eds,
                             GUINT_TO_POINTER (ag_account_id),
                             eds_id_list);

        lomiri_sources_config_source (extension, source, ag_account);

        g_object_unref (ag_account);

        g_debug("Source %s, linked with account %d", source_uid, ag_account_id);
        return TRUE;
    }

    return FALSE;
}

static void
lomiri_sources_populate_accounts_table (ELomiriSources *extension)
{
    ESourceRegistryServer *server;
    GQueue trash = G_QUEUE_INIT;
    GList *list, *link;

    server = lomiri_sources_get_server (extension);
    list = e_source_registry_server_list_sources (server,
                                                  E_SOURCE_EXTENSION_LOMIRI);

    g_debug ("Found %d lomiri accounts.", g_list_length(list));
    for (link = list; link != NULL; link = g_list_next (link)) {
        ESource *source;
        source = E_SOURCE (link->data);

        /* If a matching AgAccountId was found, add it
         * to our accounts hash table.  Otherwise remove
         * the ESource after we finish looping. */
        if (!lomiri_sources_register_source (extension, source)) {
            g_debug ("Account not found we will remove the source: %s",
                     e_source_get_display_name (source));

            g_queue_push_tail (&trash, source);
        }
    }

    /* Empty the trash. */
    while (!g_queue_is_empty (&trash)) {
        ESource *source = g_queue_pop_head (&trash);
        lomiri_sources_remove_collection (extension, source);
    }

    g_list_free_full (list, (GDestroyNotify) g_object_unref);
    g_debug("lomiri_sources_populate_accounts_table:END");
}

static void
lomiri_source_source_added_cb (ESourceRegistryServer *server,
                               ESource *source,
                               ELomiriSources *extension)
{
    lomiri_sources_register_source(extension, source);
}

static void
lomiri_source_source_removed_cb (ESourceRegistryServer *server,
                                 ESource *source,
                                 ELomiriSources *extension)
{
    GHashTableIter iter;
    gpointer key, value;

    const gchar *source_uid = e_source_get_uid(source);
    g_debug("Source removed: %s", source_uid);

    GSList *source_link = 0;
    guint account_id = 0;

    g_hash_table_iter_init (&iter, extension->loa_to_eds);
    while (g_hash_table_iter_next (&iter, &key, &value)) {
        GSList *sources = (GSList*) value;
        source_link = g_slist_find_custom (sources, source_uid, (GCompareFunc) g_strcmp0);
        if (source_link) {
            account_id = (gulong) key;
            break;
        }
    }

    if (account_id != 0) {
        GSList *eds_id_list = g_hash_table_lookup (extension->loa_to_eds,
                                                   GUINT_TO_POINTER (account_id));
        eds_id_list = g_slist_remove_link (eds_id_list, source_link);
        g_free(source_link->data);
        g_hash_table_insert (extension->loa_to_eds,
                             GUINT_TO_POINTER (account_id),
                             eds_id_list);
        g_debug ("Remove source :%s for account %lu", source_uid, (gulong) value);
    }
}

static void
lomiri_sources_bus_acquired_cb (EDBusServer *server,
                                GDBusConnection *connection,
                                ELomiriSources *extension)
{
    g_debug("loading lomiri sources");

    if (extension->ag_manager != NULL)
        return;

    extension->ag_manager = ag_manager_new ();

    /* This populates a hash table of UOA ID -> ESource UID strings by
     * searching through available data sources for ones with a "Lomiri
     * Source" extension.  If such an extension is found, but
     * no corresponding AgAccount (presumably meaning the UOA account
     * was somehow deleted between E-D-S sessions) then the ESource in
     * which the extension was found gets deleted. */
    lomiri_sources_populate_accounts_table (extension);

    /* Listen for Online Account changes. */
    g_signal_connect (extension->ag_manager, "account-deleted",
                      G_CALLBACK (lomiri_sources_account_deleted_cb),
                      extension);

    ESourceRegistryServer *registry_server;
    registry_server = lomiri_sources_get_server (extension);
    g_signal_connect (registry_server, "source_added",
                      G_CALLBACK (lomiri_source_source_added_cb),
                      extension);
    g_signal_connect (registry_server, "source_removed",
                      G_CALLBACK (lomiri_source_source_removed_cb),
                      extension);
}

static void
lomiri_sources_dispose (GObject *object)
{
    ELomiriSources *extension;

    extension = E_LOMIRI_SOURCES (object);

    if (extension->ag_manager != NULL) {
        g_signal_handlers_disconnect_matched (
            extension->ag_manager,
            G_SIGNAL_MATCH_DATA,
            0, 0, NULL, NULL, object);
        g_object_unref (extension->ag_manager);
        extension->ag_manager = NULL;
    }

    /* Chain up to parent's dispose() method. */
    G_OBJECT_CLASS (e_lomiri_sources_parent_class)->dispose (object);
}

static void
lomiri_sources_finalize (GObject *object)
{
    ELomiriSources *extension;

    extension = E_LOMIRI_SOURCES (object);

    GHashTableIter iter;
    gpointer key, value;
    g_hash_table_iter_init (&iter, extension->loa_to_eds);
    while (g_hash_table_iter_next (&iter, &key, &value)) {
        g_slist_free_full ((GSList*) value, g_free);
    }
    g_hash_table_destroy (extension->loa_to_eds);

    /* Chain up to parent's finalize() method. */
    G_OBJECT_CLASS (e_lomiri_sources_parent_class)->finalize (object);
}

static void
lomiri_sources_constructed (GObject *object)
{
    EExtension *extension;
    EExtensible *extensible;

    extension = E_EXTENSION (object);
    extensible = e_extension_get_extensible (extension);

    /* Wait for the registry service to acquire its well-known
     * bus name so we don't do anything destructive beforehand.
     * Run last so that all the sources get loaded first. */

    g_signal_connect_after (
        extensible, "bus-acquired",
        G_CALLBACK (lomiri_sources_bus_acquired_cb),
        extension);

    /* Chain up to parent's constructed() method. */
    G_OBJECT_CLASS (e_lomiri_sources_parent_class)->constructed (object);
}

static void
e_lomiri_sources_class_init (ELomiriSourcesClass *klass)
{
    GObjectClass *object_class;
    EExtensionClass *extension_class;

    object_class = G_OBJECT_CLASS (klass);
    object_class->dispose = lomiri_sources_dispose;
    object_class->finalize = lomiri_sources_finalize;
    object_class->constructed = lomiri_sources_constructed;

    extension_class = E_EXTENSION_CLASS (klass);
    extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
}

static void
e_lomiri_sources_destroy_eds_id_slist (GSList *eds_id_list)
{
    g_slist_free_full (eds_id_list, g_free);
}

static void
e_lomiri_sources_init (ELomiriSources *extension)
{
    extension->ag_manager = NULL;
    extension->loa_to_eds = g_hash_table_new_full (
        (GHashFunc) g_direct_hash,
        (GEqualFunc) g_direct_equal,
        (GDestroyNotify) NULL,
        (GDestroyNotify) NULL);
}

G_MODULE_EXPORT void
e_module_load (GTypeModule *type_module)
{
    g_type_ensure (E_TYPE_SOURCE_LOMIRI);
    g_type_ensure (E_TYPE_LOMIRI_SOURCES);
}

G_MODULE_EXPORT void
e_module_unload (GTypeModule *type_module)
{
}
