/*
 * Copyright (C) 2012 Canonical Ltd.
 *
 * 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; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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
 * Lesser 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Authors: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
 */

#include <config.h>
#include "log-store-ufa-internal.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <telepathy-glib/telepathy-glib.h>

#include "event-internal.h"
#include "text-event.h"
#include "call-event-internal.h"
#include "entity-internal.h"
#include "log-manager-internal.h"

#define DEBUG_FLAG TPL_DEBUG_LOG_STORE
#include "debug-internal.h"
#include "util-internal.h"

#define TPL_LOG_STORE_UFA_NAME                          "Ufa"
#define UBUNTU_ANDROID_SERVICE_NAME                     "com.canonical.Android"
#define UBUNTU_ANDROID_CALLLOG_OBJECT_PATH              "/com/canonical/android/telephony/Telephony"
#define UBUNTU_ANDROID_CALLLOG_INTERFACE_NAME           "com.canonical.android.telephony.CallLog"
#define UBUNTU_ANDROID_CALLLOG_CHANGED_SIGNAL           "CallListChanged"

#define UBUNTU_ANDROID_MESSAGES_OBJECT_PATH              "/com/canonical/android/messages/Messages"
#define UBUNTU_ANDROID_MESSAGES_INTERFACE_NAME           "com.canonical.android.messages.Messages"
#define UBUNTU_ANDROID_MESSAGES_CHANGED_SIGNAL           "MessagesChanged"
#define UBUNTU_ANDROID_MESSAGES_ADDED_SIGNAL             "MessageAdded"
#define UBUNTU_ANDROID_MESSAGES_REMOVED_SIGNAL           "MessageRemoved"

#define UBUNTU_ANDROID_CONTACTS_OBJECT_PATH              "/com/canonical/android/contacts/Contacts"
#define UBUNTU_ANDROID_CONTACTS_INTERFACE_NAME           "com.canonical.android.contacts.Contacts"


typedef gboolean (*TplLogStoreTranslateFunc) (TplLogStoreUfa *self, GVariant *result, GList **cache);

static void log_store_iface_init (gpointer g_iface, gpointer iface_data);
static void tpl_log_store_ufa_get_property (GObject *object, guint param_id, GValue *value,
    GParamSpec *pspec);
static void tpl_log_store_ufa_set_property (GObject *object, guint param_id, const GValue *value,
    GParamSpec *pspec);

G_DEFINE_TYPE_WITH_CODE (TplLogStoreUfa, tpl_log_store_ufa,
    G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE (TPL_TYPE_LOG_STORE, log_store_iface_init));

enum /* properties */
{
  PROP_0,
  PROP_NAME,
  PROP_READABLE,
  PROP_WRITABLE
};


typedef struct _UfaCallLog UfaCallLog;
struct _UfaCallLog
{
    gchar *number;
    gchar *contact_name;
    gchar *contact_id;
    gchar *number_type;
    GDateTime *timestamp;
    glong duration;
    gboolean is_outgoing;
    gboolean is_missed;
    gboolean is_new;
    gchar *avatar_token;
};

typedef struct _UfaConversationLog UfaConversationLog;
struct _UfaConversationLog
{
    gchar *number;
    gchar *contact_id;
    gchar *avatar_token;
    GSList *messages;
};

typedef struct _UfaMessageLog UfaMessageLog;
struct _UfaMessageLog
{
    UfaConversationLog *conversation;
    gchar *number;
    gchar *text;
    GDateTime *timestamp;
    gboolean is_read;
    gboolean is_outgoing;
    gboolean is_mms;
    gchar *message_id;
    gchar *subject;
};


typedef struct _UfaGetAvatarData UfaGetAvatarData;
struct _UfaGetAvatarData
{
    GFile *file;
    TplLogStoreUfa *self;
};

struct _TplLogStoreUfaPriv
{
    GList *call_logs;
    GList *conversation_logs;
    GDBusProxy *telephony_proxy;
    GDBusProxy *messages_proxy;
    GDBusProxy *contacts_proxy;
    guint watch_id;
    gboolean calls_loaded;
    gboolean messages_loaded;
    GMutex *message_cache_mutex;
    GMutex *call_cache_mutex;
    UfaConversationLog *loading_conversation;
    gboolean calls_cache_dirty;
    gboolean messages_cache_dirty;
};

static void
tpl_log_store_ufa_get_property (GObject *self,
    guint id,
    GValue *value,
    GParamSpec *pspec)
{
  switch (id)
    {
      case PROP_NAME:
        g_value_set_string (value, TPL_LOG_STORE_UFA_NAME);
        break;

      case PROP_READABLE:
        g_value_set_boolean (value, TRUE);
        break;

      case PROP_WRITABLE:
        g_value_set_boolean (value, FALSE);
        break;

      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (self, id, pspec);
        break;
    }
}

static void
tpl_log_store_ufa_set_property (GObject *self,
    guint id,
    const GValue *value,
    GParamSpec *pspec)
{
  switch (id)
    {
      case PROP_NAME:
      case PROP_READABLE:
      case PROP_WRITABLE:
        break;

      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (self, id, pspec);
        break;
    }
}

static void
tpl_log_store_ufa_clear_call_log_cache (TplLogStoreUfa *self,
                                        gboolean free_memory,
                                        gboolean exclusive)
{
    if (!free_memory) {
        self->priv->calls_cache_dirty = TRUE;
    } else {
        if (exclusive) {
            g_mutex_lock(self->priv->call_cache_mutex);
        }

        if (self->priv->call_logs) {
            GList *cache = self->priv->call_logs;
            for (; cache; cache = g_list_delete_link (cache, cache)) {
                UfaCallLog *log = cache->data;
                g_free (log->avatar_token);
                g_free (log->number);
                g_free (log->contact_id);
                g_free (log->contact_name);
                g_free (log->number_type);
                g_date_time_unref (log->timestamp);
                g_free (log);
            }
            self->priv->call_logs = NULL;
        }
        self->priv->calls_loaded = FALSE;

        if (exclusive) {
            g_mutex_unlock(self->priv->call_cache_mutex);
        }
    }
}

static void
tpl_log_store_ufa_clear_message_log_cache (TplLogStoreUfa *self,
                                           gboolean free_memory,
                                           gboolean exclusive)
{
    if (!free_memory) {
        self->priv->messages_cache_dirty = TRUE;
    } else {
        if (exclusive) {
            g_mutex_lock(self->priv->message_cache_mutex);
        }

        if (self->priv->conversation_logs) {
            GList *cache = self->priv->conversation_logs;
            for (; cache; cache = g_list_delete_link (cache, cache)) {
                UfaConversationLog *log = cache->data;
                g_free (log->number);
                g_free (log->contact_id);
                g_free (log->avatar_token);
                GSList *messages = log->messages;
                for (; messages; messages = g_slist_delete_link (messages, messages)) {
                    UfaMessageLog *mlog = messages->data;
                    g_free (mlog->number);
                    g_free (mlog->text);
                    g_date_time_unref (mlog->timestamp);
                    g_free (mlog->message_id);
                    g_free (mlog->subject);
                    g_free (mlog);
                }
                g_free (log);
            }
            self->priv->conversation_logs = NULL;
        }
        self->priv->messages_loaded = FALSE;

        if (exclusive) {
            g_mutex_unlock(self->priv->message_cache_mutex);
        }
    }
}


static void
tpl_log_store_ufa_clear_cache (TplLogStoreUfa *self, gboolean free_memory)
{
    tpl_log_store_ufa_clear_call_log_cache (self, free_memory, TRUE);
    tpl_log_store_ufa_clear_message_log_cache (self, free_memory, TRUE);
}

static GError*
tpl_log_store_ufa_get_page (TplLogStoreUfa *self,
                            const gchar *token,
                            GDBusProxy *proxy,
                            const char *getPageMethod,
                            TplLogStoreTranslateFunc translate_func,
                            GList **cache)
{
    GError *error = 0;

    // Request page
    GVariant *result = g_dbus_proxy_call_sync (proxy,
                                               getPageMethod,
                                               g_variant_new ("(s)", token),
                                               G_DBUS_CALL_FLAGS_NONE,
                                               -1,
                                               NULL,
                                               &error);
    if (!result) {
        return error;
    }

    if (!translate_func (self, result, cache)) {
        g_variant_unref (result);
        return NULL;
    }

    g_variant_unref (result);
    return tpl_log_store_ufa_get_page (self, token, proxy, getPageMethod, translate_func, cache);
}

static void
tpl_log_store_ufa_get_log_impl (TplLogStoreUfa *self,
                                GDBusProxy *proxy,
                                const gchar *getTokenMethod,
                                const gchar *getTokenMethodArg,
                                const gchar *getPageMethod,
                                TplLogStoreTranslateFunc traslate_func,
                                GList **results)
{
    if (!proxy) {
        return;
    }

    GError *error = NULL;
    // Request token
    GVariant *arg = NULL;
    if (getTokenMethodArg) {
        arg = g_variant_new ("(s)", getTokenMethodArg);
    }
    GVariant *result = g_dbus_proxy_call_sync (proxy,
                                               getTokenMethod,
                                               arg,
                                               G_DBUS_CALL_FLAGS_NONE,
                                               -1,
                                               NULL,
                                               &error);
    if (!result) {
        DEBUG ("%s erorr: %s in %s",
                 getTokenMethod,
                 error->message,
                 g_dbus_proxy_get_object_path (proxy));
        g_error_free (error);
        return;
    }

    gchar *token = NULL;
    g_variant_get (result, "(s)", &token);

    if (token == NULL) {
        g_warning ("Invalid method signature : %s", getTokenMethod);
        return;
    }

    error = tpl_log_store_ufa_get_page (self, token, proxy, getPageMethod, traslate_func, results);
    if (error) {
        DEBUG ("Get Log error: %s", error->message);
        g_error_free (error);
    }
}

static gboolean
tpl_log_store_ufa_translate_telephony (TplLogStoreUfa *self,
                                       GVariant *result,
                                       GList **cache)
{
    GVariantIter *iter = NULL;

    g_variant_get (result, "(a(ssssxxbbb))", &iter);

    if (iter == NULL) {
        g_warning ("Invalid call log struct signature.");
        return FALSE;
    }

    if (g_variant_iter_n_children (iter) == 0) {
        // End of the pages
        return FALSE;
    }

    gchar *number;
    gchar *contact_id;
    gchar *contact_name;
    gchar *number_type;
    gint64 timestamp;
    gint64 duration;
    gboolean is_outgoing;
    gboolean is_missed;
    gboolean is_new;

    UfaCallLog *log = NULL;

    while (g_variant_iter_loop (iter,
                                "(ssssxxbbb)",
                                &number,
                                &contact_id,
                                &contact_name,
                                &number_type,
                                &timestamp,
                                &duration,
                                &is_outgoing,
                                &is_missed,
                                &is_new)) {
        log = g_new0(UfaCallLog, 1);
        log->number = g_strdup (number);
        log->contact_id = g_strdup (contact_id);
        log->contact_name = g_strdup (contact_name);
        log->number_type = g_strdup (number_type);
        log->timestamp = g_date_time_new_from_unix_utc ((timestamp / 1000));
        log->duration = duration;
        log->is_outgoing = is_outgoing;
        log->is_missed = is_missed;
        log->is_new = is_new;
        *cache = g_list_append (*cache, log);
    }

    return TRUE;
}

static gboolean
tpl_log_store_ufa_translate_message (TplLogStoreUfa *self,
                                     GVariant *result,
                                     GList **cache)
{
    GVariantIter *iter = NULL;

    g_variant_get (result, "(a(ssxbbbsss))", &iter);

    if (iter == NULL) {
        g_warning ("Invalid message log struct signature");
        return FALSE;
    }

    if (g_variant_iter_n_children (iter) == 0) {
        // End of the pages
        return FALSE;
    }

    gchar *number;
    gchar *text;
    gint64 timestamp;
    gboolean is_new;
    gboolean is_outgoing;
    gboolean is_mms;
    gchar *message_id;
    gchar *subject;
    gchar *conversation_id;

    UfaConversationLog *conversation = self->priv->loading_conversation;
    UfaMessageLog *log = NULL;

    while (g_variant_iter_loop (iter,
                                "(ssxbbbsss)",
                                &number,
                                &text,
                                &timestamp,
                                &is_new,
                                &is_outgoing,
                                &is_mms,
                                &message_id,
                                &subject,
                                &conversation_id)) {
        log = g_new0(UfaMessageLog, 1);

        // for incoming messages, use the number from the conversation log
        // to make sure all the numbers are equal
        if (is_outgoing) {
            log->number = g_strdup (number);
        } else {
            log->number = g_strdup (conversation->number);
        }
        log->text = g_strdup (text);
        log->timestamp = g_date_time_new_from_unix_utc (timestamp / 1000);
        log->is_read = is_new;
        log->is_outgoing = is_outgoing;
        log->is_mms = is_mms;
        log->message_id = g_strdup (message_id);
        log->subject = g_strdup (subject);
        log->conversation = conversation;
        conversation->messages = g_slist_append (conversation->messages, log);
    }

    return TRUE;
}

static gboolean
tpl_log_store_ufa_translate_conversation (TplLogStoreUfa *self,
                                          GVariant *result,
                                          GList **cache)
{
    GVariantIter *iter = NULL;

    g_variant_get (result, "(a(ssssxb))", &iter);

    if (iter == NULL) {
        g_warning ("Invalid conversation struct signature");
        return FALSE;
    }

    if (g_variant_iter_n_children (iter) == 0) {
        // End of the pages
        return FALSE;
    }

    gchar *id;
    gchar *number;
    gchar *last_message;
    gchar *contact_id;
    gint64 last_timestamp;
    gboolean is_new;
    UfaConversationLog *log;

    while (g_variant_iter_loop (iter,
                                "(ssssxb)",
                                &id,
                                &number,
                                &contact_id,
                                &last_message,
                                &last_timestamp,
                                &is_new)) {
        log = g_new0(UfaConversationLog, 1);
        log->number = g_strdup (number);
        log->contact_id = g_strdup (contact_id);
        *cache = g_list_append (*cache, log);

        self->priv->loading_conversation = log;
        tpl_log_store_ufa_get_log_impl (self,
                                         self->priv->messages_proxy,
                                         "listMessagesBegin",
                                         id,
                                         "listMessagesNextPage",
                                         tpl_log_store_ufa_translate_message,
                                         cache);

    }
    self->priv->loading_conversation = NULL;

    return TRUE;
}

static void
tpl_log_store_ufa_get_call_log (TplLogStoreUfa *self)
{
    g_mutex_lock(self->priv->call_cache_mutex);

    if (self->priv->calls_loaded &&
        !self->priv->calls_cache_dirty) {
        g_mutex_unlock(self->priv->call_cache_mutex);
        return;
    }

    if (self->priv->calls_cache_dirty) {
        tpl_log_store_ufa_clear_call_log_cache (self, TRUE, FALSE);
    }

    tpl_log_store_ufa_get_log_impl (self,
                                    self->priv->telephony_proxy,
                                    "listCallsBegin",
                                    NULL,
                                    "listCallsNextPage",
                                    tpl_log_store_ufa_translate_telephony,
                                    &(self->priv->call_logs));
    self->priv->calls_loaded = TRUE;
    self->priv->calls_cache_dirty = FALSE;
    g_mutex_unlock(self->priv->call_cache_mutex);
}

static void
tpl_log_store_ufa_get_message_log (TplLogStoreUfa *self)
{
    g_mutex_lock(self->priv->message_cache_mutex);

    if (self->priv->messages_loaded &&
        !self->priv->messages_cache_dirty) {
        g_mutex_unlock(self->priv->message_cache_mutex);
        return;
    }

    if (self->priv->messages_cache_dirty) {
        tpl_log_store_ufa_clear_message_log_cache (self, TRUE, FALSE);
    }

    tpl_log_store_ufa_get_log_impl (self,
                                    self->priv->messages_proxy,
                                    "listConversationsBegin",
                                    NULL,
                                    "listConversationsNextPage",
                                    tpl_log_store_ufa_translate_conversation,
                                    &(self->priv->conversation_logs));

    self->priv->messages_loaded = TRUE;
    self->priv->messages_cache_dirty = FALSE;
    g_mutex_unlock(self->priv->message_cache_mutex);
}

static void
tpl_log_store_ufa_get_log (TplLogStoreUfa *self)
{
    tpl_log_store_ufa_get_call_log (self);
    tpl_log_store_ufa_get_message_log (self);

    DEBUG ("LOG UPDATED CALLS: %d MESSAGES: %d", g_list_length (self->priv->call_logs),
                                                 g_list_length (self->priv->conversation_logs));

}


static void
tpl_log_store_ufa_dbus_signal (GDBusProxy *proxy,
                               gchar *sender_name,
                               gchar *signal_name,
                               GVariant *parameters,
                               TplLogStoreUfa *self)
{
    g_print ("DEBUG: %s\n", signal_name);
    // If the data has changed, just discard the cache, but don't fetch the messages here
    // because then the load is going to be forced synchronously, blocking the applications.
    // The log will be loaded again when necessary by the applications using this store.
    if (g_str_equal (signal_name, UBUNTU_ANDROID_CALLLOG_CHANGED_SIGNAL)) {
        tpl_log_store_ufa_clear_call_log_cache (self, FALSE, FALSE);
    } else if (g_str_equal (signal_name, UBUNTU_ANDROID_MESSAGES_CHANGED_SIGNAL) ||
               g_str_equal (signal_name, UBUNTU_ANDROID_MESSAGES_ADDED_SIGNAL)  ||
               g_str_equal (signal_name, UBUNTU_ANDROID_MESSAGES_REMOVED_SIGNAL)) {
        tpl_log_store_ufa_clear_message_log_cache (self, FALSE, FALSE);
    }
}

static void
tpl_log_store_ufa_connect_dbus (TplLogStoreUfa *self)
{
    // Service connected already
    if (self->priv->telephony_proxy) {
        return;
    }

    GError *error = NULL;
    self->priv->telephony_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
                                                                 G_BUS_NAME_WATCHER_FLAGS_NONE,
                                                                 NULL,
                                                                 UBUNTU_ANDROID_SERVICE_NAME,
                                                                 UBUNTU_ANDROID_CALLLOG_OBJECT_PATH,
                                                                 UBUNTU_ANDROID_CALLLOG_INTERFACE_NAME,
                                                                 NULL,
                                                                 &error);
    if (error) {
        DEBUG ("connection error: %s", error->message);
        g_error_free (error);
        self->priv->telephony_proxy = NULL;
    } else {
        g_signal_connect (self->priv->telephony_proxy, "g-signal",
                          G_CALLBACK (tpl_log_store_ufa_dbus_signal), self);
    }

    error = NULL;
    self->priv->messages_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
                                                                 G_BUS_NAME_WATCHER_FLAGS_NONE,
                                                                 NULL,
                                                                 UBUNTU_ANDROID_SERVICE_NAME,
                                                                 UBUNTU_ANDROID_MESSAGES_OBJECT_PATH,
                                                                 UBUNTU_ANDROID_MESSAGES_INTERFACE_NAME,
                                                                 NULL,
                                                                 &error);
    if (error) {
        DEBUG ("connection error: %s", error->message);
        g_error_free (error);
        self->priv->messages_proxy = NULL;
    } else {
        g_signal_connect (self->priv->messages_proxy, "g-signal",
                          G_CALLBACK (tpl_log_store_ufa_dbus_signal), self);
    }

    self->priv->contacts_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
                                                                G_BUS_NAME_WATCHER_FLAGS_NONE,
                                                                NULL,
                                                                UBUNTU_ANDROID_SERVICE_NAME,
                                                                UBUNTU_ANDROID_CONTACTS_OBJECT_PATH,
                                                                UBUNTU_ANDROID_CONTACTS_INTERFACE_NAME,
                                                                NULL,
                                                                &error);
    if (error) {
        DEBUG ("connection error: %s", error->message);
        g_error_free (error);
        self->priv->contacts_proxy = NULL;
    }
}

static void
tpl_log_store_ufa_dbus_service_appear (GDBusConnection *connection,
                                       const gchar *name,
                                       const gchar *name_owner,
                                       TplLogStoreUfa *self)
{
    if (!self->priv->telephony_proxy) {
        tpl_log_store_ufa_connect_dbus (self);
        tpl_log_store_ufa_get_log (self);
    }
}

static void
tpl_log_store_ufa_dbus_service_disapear (GDBusConnection *connection,
                                         const gchar *name,
                                         TplLogStoreUfa *self)
{
    g_clear_object (&self->priv->telephony_proxy);
    g_clear_object (&self->priv->contacts_proxy);
    tpl_log_store_ufa_clear_cache (self, FALSE);
}

static void
tpl_log_store_ufa_init (TplLogStoreUfa *self)
{
    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
                                              TPL_TYPE_LOG_STORE_UFA,
                                              TplLogStoreUfaPriv);

    self->priv->call_cache_mutex = g_mutex_new();
    self->priv->message_cache_mutex = g_mutex_new();

    tpl_log_store_ufa_connect_dbus (self);

    // keep dbus update about disconnection
    self->priv->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
                                             UBUNTU_ANDROID_SERVICE_NAME,
                                             G_BUS_NAME_WATCHER_FLAGS_NONE,
                                             (GBusNameAppearedCallback) tpl_log_store_ufa_dbus_service_appear,
                                             (GBusNameVanishedCallback) tpl_log_store_ufa_dbus_service_disapear,
                                             self,
                                             NULL);
}

static void
tpl_log_store_ufa_dispose (GObject *self)
{
    TplLogStoreUfaPriv *priv = TPL_LOG_STORE_UFA (self)->priv;

    if (priv->watch_id > 0) {
        g_bus_unwatch_name (priv->watch_id);
        priv->watch_id = 0;
    }

    if (priv->telephony_proxy) {
        g_clear_object (&priv->telephony_proxy);
    }

    if (priv->messages_proxy) {
        g_clear_object (&priv->messages_proxy);
    }

    if (priv->contacts_proxy) {
        g_clear_object (&priv->contacts_proxy);
    }

    tpl_log_store_ufa_clear_cache (TPL_LOG_STORE_UFA (self), TRUE);

    g_mutex_free(priv->call_cache_mutex);
    g_mutex_free(priv->message_cache_mutex);

    G_OBJECT_CLASS (tpl_log_store_ufa_parent_class)->dispose (self);
}


static void
tpl_log_store_ufa_class_init (TplLogStoreUfaClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = tpl_log_store_ufa_get_property;
  gobject_class->set_property = tpl_log_store_ufa_set_property;
  gobject_class->dispose = tpl_log_store_ufa_dispose;

  g_object_class_override_property (gobject_class, PROP_NAME, "name");
  g_object_class_override_property (gobject_class, PROP_READABLE, "readable");
  g_object_class_override_property (gobject_class, PROP_WRITABLE, "writable");

  g_type_class_add_private (gobject_class, sizeof (TplLogStoreUfaPriv));
}


static gboolean
string_contains (const gchar* self, const gchar* needle)
{
    g_return_val_if_fail (self != NULL, FALSE);
    g_return_val_if_fail (needle != NULL, FALSE);

    return strstr (self, needle) != NULL;
}

static gboolean
log_store_ufa_is_ufa_account (TpAccount *acc)
{
    const gchar *connection_manager = tp_account_get_connection_manager (acc);
    const gchar *protocol = tp_account_get_protocol (acc);

    return (g_str_equal (connection_manager, "ufa") && g_str_equal(protocol, "ufa"));
}

static TpAccount *
log_store_ufa_dup_account (const gchar *id)
{
    GList *accounts, *l;
    TpAccount *account = NULL;
    TpAccountManager *account_manager;

    account_manager = tp_account_manager_dup ();
    accounts = tp_account_manager_get_valid_accounts (account_manager);

    for (l = accounts; l != NULL; l = l->next)
    {
        TpAccount *acc = (TpAccount *) l->data;
        if (tp_account_is_valid (acc)) {
            if (log_store_ufa_is_ufa_account (acc)) {
                account = g_object_ref (acc);
                break;
            }
        }
    }

    g_list_free (accounts);
    g_object_unref (account_manager);
    return account;
}

static void
log_store_ufa_get_avatar_token_finish (GDBusProxy  *proxy,
                                       GAsyncResult *res,
                                       UfaGetAvatarData *data)
{
    GError *error = NULL;
    GVariant *result = g_dbus_proxy_call_finish (proxy, res, &error);
    if (error) {
        g_warning ("Fail to get contact avatar: %s", error->message);
        g_error_free (error);
        g_object_unref (data->file);
        g_free (data);
        return;
    }

    gchar *photo64 = NULL;
    gsize len;
    g_variant_get (result, "(s)", &photo64);
    g_variant_unref (result);

    if ((photo64 == NULL) || (strlen(photo64) == 0)) {
        // Contact does not have avatar, return empty string to avoid other search
        return;
    }

    guchar *photo = g_base64_decode (photo64, &len);
    if (photo64) {
       GFileIOStream *stream = g_file_replace_readwrite (data->file,
                                                          NULL,
                                                          FALSE,
                                                          G_FILE_CREATE_NONE,
                                                          NULL,
                                                          &error);
        if (error) {
            DEBUG ("Fail to create avatar file: %s", error->message);
            g_error_free (error);
        } else {
            GOutputStream *output;
            output = g_io_stream_get_output_stream ((GIOStream*) stream);
            if (output) {
                gsize bytes_writter = 0;
                g_output_stream_write_all (output,
                                           photo,
                                           len,
                                           &bytes_writter,
                                           NULL,
                                           &error);
                if (error) {
                    DEBUG ("Fail to create avatar file: %s", error->message);
                    g_error_free (error);
                }
            }
            g_object_unref (stream);
        }
    }
    g_object_unref (data->file);
    g_free (data);
    g_free (photo64);
    g_free (photo);
}

static gchar*
log_store_ufa_get_avatar_token (TplLogStoreUfa *self, const gchar *id)
{
    if (!self->priv->contacts_proxy) {
        // Not connected
        return NULL;
    }

    if (!id) {
        // Unknow id
        return NULL;
    }

    // Check if file already exists
    gchar *file_path = g_build_filename (g_get_user_cache_dir (),
                                         "ufa",
                                         "avatars",
                                         NULL);
    if (g_mkdir_with_parents (file_path, 0700) != 0) {
            DEBUG ("Fail to create avatar cache dir: %s.", file_path);
            g_free (file_path);
            return NULL;
    }

    gchar *file_name = g_build_filename (file_path, id, NULL);
    GFile *file = g_file_new_for_path (file_name);
    if (!g_file_query_exists (file, NULL)) {
        UfaGetAvatarData *data = g_new0(UfaGetAvatarData, 1);
        data->file = file;
        data->self = self;
        g_dbus_proxy_call (self->priv->contacts_proxy,
                           "getAvatar",
                            g_variant_new ("(s)", id),
                            G_DBUS_CALL_FLAGS_NONE,
                            -1,
                            NULL,
                            (GAsyncReadyCallback) log_store_ufa_get_avatar_token_finish,
                            data);
    } else {
        g_object_unref (file);
    }
    g_free (file_path);
    return file_name;
}

static TplEntity*
log_store_ufa_entity_new_from_call_log (TplLogStoreUfa *self, UfaCallLog *log)
{
    if (!log->avatar_token && log->contact_id && strlen(log->contact_id)) {
        log->avatar_token = log_store_ufa_get_avatar_token (self, log->contact_id);
    }

    gchar *alias = (log->contact_name && strlen (log->contact_name) ? log->contact_name : log->number);
    TplEntity *target = tpl_entity_new (log->number,
                                        TPL_ENTITY_CONTACT,
                                        alias,
                                        log->avatar_token);
    return target;
}

static TplEntity*
log_store_ufa_entity_new_from_conversation_log (TplLogStoreUfa *self, UfaConversationLog *log)
{
    if (!log->avatar_token && log->contact_id && strlen(log->contact_id)) {
        log->avatar_token = log_store_ufa_get_avatar_token (self, log->contact_id);
    }

    TplEntity *target = tpl_entity_new (log->number,
                                        TPL_ENTITY_CONTACT,
                                        log->number,
                                        log->avatar_token);
    return target;
}

static TplLogSearchHit *
log_store_ufa_search_hit_new_from_call_log (TplLogStore *iface, UfaCallLog *log)
{
    TplLogStoreUfa *self = TPL_LOG_STORE_UFA (iface);
    TplLogSearchHit *hit;

    hit = g_slice_new0 (TplLogSearchHit);
    hit->target = log_store_ufa_entity_new_from_call_log (self, log);
    int d, m, y;
    g_date_time_get_ymd (log->timestamp, &y, &m, &d);
    hit->date = g_date_new_dmy (d, m, y);
    hit->account = log_store_ufa_dup_account (log->number);

    return hit;
}

static TplLogSearchHit *
log_store_ufa_search_hit_new_from_conversation_log (TplLogStore *iface,
                                                    UfaConversationLog *log,
                                                    GDateTime *date)
{
    TplLogStoreUfa *self = TPL_LOG_STORE_UFA (iface);
    TplLogSearchHit *hit;

    hit = g_slice_new0 (TplLogSearchHit);
    hit->target = log_store_ufa_entity_new_from_conversation_log (self, log);
    int d, m, y;
    g_date_time_get_ymd (date, &y, &m, &d);
    hit->date = g_date_new_dmy (d, m, y);
    hit->account = log_store_ufa_dup_account (log->number);

    return hit;
}

static const char *
tpl_log_store_ufa_get_name (TplLogStore *self)
{
    return TPL_LOG_STORE_UFA_NAME;
}

/* public: returns whether some data for @id exist in @account */
static gboolean
log_store_ufa_exists (TplLogStore *self,
    TpAccount *account,
    TplEntity *target,
    gint type_mask)
{
    DEBUG ("FUNCTION: %s", __FUNCTION__);

    if (!log_store_ufa_is_ufa_account (account)) {
        return FALSE;
    }

    return TRUE;
}

static void
log_store_ufa_get_call_dates (TplLogStore *iface, TplEntity *target, GList **dates)
{
    TplLogStoreUfa *self = TPL_LOG_STORE_UFA (iface);

    GList *logs = self->priv->call_logs;

    for(; logs; logs = logs->next) {
        UfaCallLog *log = (UfaCallLog*) logs->data;

        if (!target || g_str_equal (tpl_entity_get_identifier(target), log->number)) {
            gint d, m, y;

            g_date_time_get_ymd (log->timestamp, &y, &m, &d);
            GDate *date = g_date_new_dmy (d, m, y);
            if (date) {
                if (g_list_find_custom (*dates, date, (GCompareFunc) g_date_compare) == NULL) {
                    *dates = g_list_insert_sorted (*dates, date, (GCompareFunc) g_date_compare);
                } else {
                    g_date_free (date);
                }
            } else {
                DEBUG ("Fail to create date element");
            }
        }
    }
}

static void
log_store_ufa_get_message_dates (TplLogStore *iface, TplEntity *target, GList **dates)
{
    TplLogStoreUfa *self = TPL_LOG_STORE_UFA (iface);
    GList *logs = self->priv->conversation_logs;

    for(; logs; logs = logs->next) {
        UfaConversationLog *log = (UfaConversationLog*) logs->data;

        if (!target || g_str_equal (tpl_entity_get_identifier(target), log->number)) {
            gint d, m, y;

            GSList *messages = log->messages;
            for(; messages; messages = messages->next) {
                UfaMessageLog *mlog = (UfaMessageLog *) messages->data;
                g_date_time_get_ymd (mlog->timestamp, &y, &m, &d);
                GDate *date = g_date_new_dmy (d, m, y);
                if (date) {
                    if (g_list_find_custom (*dates, date, (GCompareFunc) g_date_compare) == NULL) {
                        *dates = g_list_insert_sorted (*dates, date, (GCompareFunc) g_date_compare);
                    } else {
                        g_date_free (date);
                    }
                }
            }
        }
    }
}

static GList *
log_store_ufa_get_dates (TplLogStore *iface,
    TpAccount *account,
    TplEntity *target,
    gint type_mask)
{
    DEBUG ("FUNCTION: %s", __FUNCTION__);

    if (!log_store_ufa_is_ufa_account (account)) {
        return NULL;
    }

    tpl_log_store_ufa_get_log (TPL_LOG_STORE_UFA (iface));

    GList *dates = NULL;

    if (type_mask & TPL_EVENT_MASK_CALL) {
        log_store_ufa_get_call_dates (iface, target, &dates);
    }

    if (type_mask & TPL_EVENT_MASK_TEXT) {
        log_store_ufa_get_message_dates (iface, target, &dates);
    }

    return dates;
}

static void
log_store_ufa_get_call_events_for_date (TplLogStore *iface,
    TpAccount *account,
    TplEntity *myself,
    TplEntity *target,
    const GDate *date,
    GList **events)
{
    TplLogStoreUfa *self = TPL_LOG_STORE_UFA (iface);
    GList *walk = self->priv->call_logs;

    for (; walk; walk = walk->next) {
        UfaCallLog *log = (UfaCallLog *) walk->data;

        if (!target || g_str_equal (tpl_entity_get_identifier(target), log->number)) {
            int d, m, y;
            g_date_time_get_ymd (log->timestamp, &y, &m, &d);
            GDate *log_date = g_date_new_dmy (d, m, y);

            if (g_date_compare (log_date, date) != 0) {
                gchar d1[256];
                gchar d2[256];

                g_date_strftime(d1, 255, "%a, %d %b %Y %T %z", log_date);
                g_date_strftime(d2, 255, "%a, %d %b %Y %T %z", date);
                DEBUG ("Date is not the same: %s != %s", d1, d2);
                continue;
            }

            TpCallStateChangeReason end_reason = TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE;
            if (log->is_missed) {
                end_reason = TP_CALL_STATE_CHANGE_REASON_NO_ANSWER;
            }

            GObject *event = g_object_new (TPL_TYPE_CALL_EVENT,
                                           "account", account,
                                           "timestamp", g_date_time_to_unix (log->timestamp),
                                           "sender", (log->is_outgoing ? myself : target),
                                           "receiver", (log->is_outgoing ? target : myself),
                                           "duration", (GTimeSpan) log->duration,
                                           "end-actor", myself,
                                           "end-reason", end_reason,
                                           "detailed-end-reason", "",
                                           NULL);

            *events = g_list_append (*events, event);
        }
    }
}

static void
log_store_ufa_get_message_events_for_date (TplLogStore *iface,
    TpAccount *account,
    TplEntity *myself,
    TplEntity *target,
    const GDate *date,
    GList **events)
{
    TplLogStoreUfa *self = TPL_LOG_STORE_UFA (iface);
    GList *walk = self->priv->conversation_logs;

    for (; walk; walk = walk->next) {
        UfaConversationLog *log = (UfaConversationLog *) walk->data;

        if (!target || g_str_equal (tpl_entity_get_identifier(target), log->number)) {
            int d, m, y;

            GSList *messages = log->messages;
            for(; messages; messages = messages->next) {
                UfaMessageLog *mlog = (UfaMessageLog*) messages->data;

                g_date_time_get_ymd (mlog->timestamp, &y, &m, &d);
                GDate *log_date = g_date_new_dmy (d, m, y);

                if (g_date_compare (log_date, date) != 0) {
                    gchar d1[256];
                    gchar d2[256];

                    g_date_strftime(d1, 255, "%a, %d %b %Y %T %z", log_date);
                    g_date_strftime(d2, 255, "%a, %d %b %Y %T %z", date);
                    continue;
                }

                gint64 timestamp = g_date_time_to_unix (mlog->timestamp);
                GObject *event = g_object_new (TPL_TYPE_TEXT_EVENT,
                                                "account", account,
                                                "timestamp", timestamp,
                                                // If message was read use the same value as timestamp
                                                // since the service does not provide the read timestamp
                                                "edit-timestamp", (mlog->is_read ? timestamp : 0),
                                                "sender", (mlog->is_outgoing ? myself : target),
                                                "receiver", (mlog->is_outgoing ? target : myself),
                                                "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
                                                "message", mlog->text,
                                                "message-token", mlog->message_id,
                                                NULL);

                *events = g_list_append (*events, event);
            }
        }
    }
}

static GList *
log_store_ufa_get_events_for_date (TplLogStore *iface,
    TpAccount *account,
    TplEntity *target,
    gint type_mask,
    const GDate *date)
{
    DEBUG ("FUNCTION: %s", __FUNCTION__);

    if (!target || !account || !date) {
        return NULL;
    }

    if (!log_store_ufa_is_ufa_account (account)) {
        return NULL;
    }

    tpl_log_store_ufa_get_log (TPL_LOG_STORE_UFA (iface));

    // Get self account
    TplEntity *myself = tpl_entity_new ("account0",
                                        TPL_ENTITY_SELF,
                                        tp_account_get_display_name (account),
                                        NULL);
    g_assert (myself);
    GList *events = NULL;

    if (type_mask & TPL_EVENT_MASK_CALL) {
        log_store_ufa_get_call_events_for_date (iface, account, myself, target, date, &events);
    }

    if (type_mask & TPL_EVENT_MASK_TEXT) {
        log_store_ufa_get_message_events_for_date (iface, account, myself, target, date, &events);
    }

    g_object_unref (myself);

    return events;
}

static void
log_store_ufa_get_call_entities (TplLogStore *iface,
                                 GList **unique,
                                 GList **entities)
{
    TplLogStoreUfa *self = TPL_LOG_STORE_UFA (iface);
    GList *logs = self->priv->call_logs;

    for(; logs; logs = logs->next) {
        UfaCallLog *log = (UfaCallLog *) logs->data;
        if (g_list_index (*unique, log->number) == -1) {
            TplEntity *entity = log_store_ufa_entity_new_from_call_log (self, log);
            *entities = g_list_append (*entities, entity);
            *unique = g_list_append (*unique, log->number);
        }
    }
}

static void
log_store_ufa_get_message_entities (TplLogStore *iface,
                                    GList **unique,
                                    GList **entities)
{
    TplLogStoreUfa *self = TPL_LOG_STORE_UFA (iface);
    GList *logs = self->priv->conversation_logs;

    for(; logs; logs = logs->next) {
        UfaConversationLog *log = (UfaConversationLog *) logs->data;
        if (g_list_index (*unique, log->number) == -1) {
            TplEntity *entity = log_store_ufa_entity_new_from_conversation_log (self, log);
            *entities = g_list_append (*entities, entity);
            *unique = g_list_append (*unique, log->number);
        }
    }
}

static GList *
log_store_ufa_get_entities (TplLogStore *iface,
    TpAccount *account)
{
    DEBUG ("FUNCTION: %s", __FUNCTION__);

    if (!log_store_ufa_is_ufa_account (account)) {
        return NULL;
    }

    tpl_log_store_ufa_get_log (TPL_LOG_STORE_UFA (iface));

    GList *unique = NULL;
    GList *entities = NULL;

    log_store_ufa_get_call_entities (iface, &unique, &entities);
    log_store_ufa_get_message_entities(iface, &unique, &entities);

    if (unique) {
        g_list_free (unique);
    }

    return entities;
}

static void
log_store_ufa_search_call_new (TplLogStore *iface,
                               const gchar *text,
                               GList **hits)
{
    TplLogStoreUfa *self = TPL_LOG_STORE_UFA (iface);
    GList *logs = self->priv->call_logs;

    for(; logs; logs = logs->next) {
        UfaCallLog *log = (UfaCallLog *) logs->data;
        if (string_contains (log->number, text)  ||
            string_contains (log->contact_name, text) ||
            string_contains (log->number_type, text)) {
            TplLogSearchHit *hit = log_store_ufa_search_hit_new_from_call_log (iface, log);
            *hits = g_list_append (*hits, hit);
        }
    }
}

static void
log_store_ufa_search_message_new (TplLogStore *iface,
                                  const gchar *text,
                                  GList **hits)
{
    TplLogStoreUfa *self = TPL_LOG_STORE_UFA (iface);
    GList *logs = self->priv->conversation_logs;

    for(; logs; logs = logs->next) {
        UfaConversationLog *log = (UfaConversationLog *) logs->data;
        GSList *messages = log->messages;
        for(; messages; messages = messages->next) {
            UfaMessageLog *mlog = (UfaMessageLog *) messages->data;

            if (string_contains (mlog->text, text)  ||
                string_contains (mlog->number, text)) {
                TplLogSearchHit *hit;
                hit = log_store_ufa_search_hit_new_from_conversation_log (iface,
                                                                          log,
                                                                          mlog->timestamp);
                *hits = g_list_append (*hits, hit);
                break;
            }
        }
    }
}

static GList*
log_store_ufa_search_new (TplLogStore *iface,
    const gchar *text,
    gint type_mask)
{
    DEBUG ("FUNCTION: %s:%s", __FUNCTION__, text);
    GList *hits = NULL;

    tpl_log_store_ufa_get_log (TPL_LOG_STORE_UFA (iface));

    if (type_mask & TPL_EVENT_MASK_CALL) {
        log_store_ufa_search_call_new (iface, text, &hits);
    }

    if (type_mask & TPL_EVENT_MASK_TEXT) {
        log_store_ufa_search_message_new (iface, text, &hits);
    }

    return hits;
}

static GList *
log_store_ufa_get_filtered_events (TplLogStore *iface,
                                   TpAccount *account,
                                   TplEntity *target,
                                   gint type_mask,
                                   guint num_events,
                                   TplLogEventFilter filter,
                                   gpointer user_data)
{
    DEBUG ("FUNCTION: %s", __FUNCTION__);

    GList *dates, *l, *events = NULL;
    guint i = 0;

    dates = log_store_ufa_get_dates (iface, account, target, type_mask);

    for (l = g_list_last (dates); l != NULL && i < num_events; l = l->prev) {
        GList *new_events, *n, *next;

        new_events = log_store_ufa_get_events_for_date (iface, account, target, type_mask, l->data);

        n = new_events;
        while (n != NULL) {
            next = n->next;
            if (filter != NULL && !filter (n->data, user_data)) {
                g_object_unref (n->data);
                new_events = g_list_delete_link (new_events, n);
            } else {
                i++;
            }
            n = next;
        }
        events = g_list_concat (events, new_events);
    }

    g_list_foreach (dates, (GFunc) g_date_free, NULL);
    g_list_free (dates);

    return events;
}

static void
log_store_iface_init (gpointer g_iface,
    gpointer iface_data)
{
    TplLogStoreInterface *iface = (TplLogStoreInterface *) g_iface;

    iface->get_name = tpl_log_store_ufa_get_name;
    iface->exists = log_store_ufa_exists;
    iface->add_event = NULL;
    iface->get_dates = log_store_ufa_get_dates;
    iface->get_events_for_date = log_store_ufa_get_events_for_date;
    iface->get_entities = log_store_ufa_get_entities;
    iface->search_new = log_store_ufa_search_new;
    iface->get_filtered_events = log_store_ufa_get_filtered_events;
}
