/*
 * Mail Notification
 * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be>
 *
 * 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

%headertop{
#include <dbus/dbus-glib.h>
%}

%{
#include <stdio.h>
#include <libintl.h>
#include <gobject/gvaluecollector.h>
#include <libedataserver/eds-version.h>
#include <camel/camel.h>
#if EDS_CHECK_VERSION(2,29,0)
  #include <shell/e-shell.h>
  #include <mail/e-mail-browser.h>
#else
  #include <mail/em-folder-view.h>
  #include <mail/em-format.h>
  #include <mail/em-message-browser.h>
#endif
#include <mail/em-utils.h>
#if EDS_CHECK_VERSION(3,3,5)
  #include <libemail-engine/e-mail-session.h>
  #include <libemail-engine/mail-tools.h>
#else
  #include <mail/mail-tools.h>
  #if EDS_CHECK_VERSION(2,91,0)
    #include <mail/e-mail-session.h>
  #else
    #include <mail/mail-session.h>
  #endif
#endif
#include "mn-evolution.h"
#include "mn-evolution-folder-tree-server.h"
#include "mn-evolution-plugin.h"

/* see https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=208774 */
static GHashTable *folders = NULL;

typedef struct
{
  char		*uri;
  CamelFolder	*folder;
} FolderInfo;

#define MN_EVOLUTION_SERVER_ERROR	(mn_evolution_server_error_quark())

typedef enum
{
  MN_EVOLUTION_SERVER_ERROR_FOLDER_NOT_FOUND,
  MN_EVOLUTION_SERVER_ERROR_MESSAGE_NOT_FOUND,
  MN_EVOLUTION_SERVER_ERROR_FOLDER_TREE_REGISTRATION_FAILED,
} MNEvolutionServerError;
%}

%afterdecls{
#include "mn-evolution-server-dbus.h"
%}

/*
 * We hold the GDK lock because Evolution is multi-threaded and D-Bus
 * interface methods are run from a main loop callback.
 */

class MN:Evolution:Server from G:Object
{
  signal NONE (STRING)
    void folder_changed (self, const char *uri (check null));

  signal NONE (STRING)
    void message_reading (self, const char *uri (check null));

  class_init (class)
  {
    dbus_g_object_type_install_info(TYPE_SELF, &dbus_glib_mn_evolution_server_object_info);
  }

  private GQuark
    error_quark (void)
  {
    return g_quark_from_static_string("mn-evolution-server-error");
  }

  private void
    cache_folder (const char *uri (check null),
		  CamelFolder *folder (check null))
  {
    FolderInfo *info;

    info = g_new0(FolderInfo, 1);
    info->uri = g_strdup(uri);
    info->folder = folder;
#if EDS_CHECK_VERSION(2,31,0)
    g_object_ref(folder);
#else
    camel_object_ref(folder);
#endif

    /* uncache the folder when it is deleted */
#if EDS_CHECK_VERSION(2,31,0)
    g_signal_connect(folder, "deleted", G_CALLBACK(self_folder_deleted_cb), info);
#else
    camel_object_hook_event(folder, "deleted", self_folder_deleted_cb, info);
#endif

    g_hash_table_replace(folders, info->uri, info);
  }

  private void
    folder_info_free (FolderInfo *info (check null))
  {
#if EDS_CHECK_VERSION(2,31,0)
    g_signal_handlers_disconnect_by_func(info->folder, self_folder_deleted_cb, info);
    g_object_unref(info->folder);
#else
    camel_object_unhook_event(info->folder, "deleted", self_folder_deleted_cb, info);
    camel_object_unref(info->folder);
#endif
    g_free(info->uri);
    g_free(info);
  }

  private void
    folder_deleted_cb (CamelObject *object,
		       gpointer event_data,
		       gpointer user_data)
  {
    FolderInfo *info = user_data;
    gboolean status;

    status = g_hash_table_remove(folders, info->uri);
    g_assert(status == TRUE);
  }

  private CamelFolder *
    lookup_folder (const char *uri (check null), GError **err)
  {
    CamelFolder *folder = NULL;

    if (folders)
      {
	FolderInfo *info;

	info = g_hash_table_lookup(folders, uri);
	if (info)
	  {
	    folder = info->folder;
#if EDS_CHECK_VERSION(2,31,0)
            g_object_ref(folder);
#else
	    camel_object_ref(folder);
#endif
	  }
      }
    else
      folders = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) self_folder_info_free);

    if (! folder)
      {
#if EDS_CHECK_VERSION(2,91,0)
	static EMailSession * session = NULL;
	if (! session)
	  {
#if EDS_CHECK_VERSION(3,1,0)
	    EShell *shell = e_shell_get_default();
	    EShellBackend *shell_backend = e_shell_get_backend_by_name(shell, "mail");
	    session = E_MAIL_SESSION(e_mail_backend_get_session(E_MAIL_BACKEND(shell_backend)));
#else
	    session = e_mail_session_new();
#endif
          }

	folder = e_mail_session_uri_to_folder_sync(session, uri, 0, NULL, NULL);
#endif
	if (folder)
	  self_cache_folder(uri, folder);
	else
	  g_set_error(err,
		      MN_EVOLUTION_SERVER_ERROR,
		      MN_EVOLUTION_SERVER_ERROR_FOLDER_NOT_FOUND,
		      dgettext(GETTEXT_PACKAGE, "folder not found"));
      }

    return folder;
  }

  private GValueArray *
    value_array_new (GType type, ...)
  {
    GValueArray *value_array;
    va_list args;

    value_array = g_value_array_new(0);

    va_start(args, type);

    while (type)
      {
	GValue value = { 0, };
	char *error = NULL;

	g_value_init(&value, type);
	G_VALUE_COLLECT(&value, args, 0, &error);
	g_assert(error == NULL);

	g_value_array_append(value_array, &value);
	g_value_unset(&value);

	type = va_arg(args, GType);
      }

    va_end(args);

    return value_array;
  }

  private GValueArray *
    camel_message_info_to_dbus_struct (CamelMessageInfo *info (check null))
  {
    const CamelSummaryMessageID *id;
    char hexhash[sizeof(CamelSummaryMessageID) * 2 + 1];
    int i;

    id = camel_message_info_message_id(info);
    for (i = 0; i < sizeof(id->id.hash); i++)
      sprintf(hexhash + i * 2, "%.2x", id->id.hash[i]);

    g_assert(camel_message_info_uid(info) != NULL);

    return self_value_array_new(/* char *uid */
				G_TYPE_STRING, camel_message_info_uid(info),
				/* unsigned int sent_time */
				G_TYPE_UINT, (unsigned int) camel_message_info_date_sent(info),
				/* unsigned int received_time */
				G_TYPE_UINT, (unsigned int) camel_message_info_date_received(info),
				/* char *id */
				G_TYPE_STRING, hexhash,
				/* char *from */
				G_TYPE_STRING, camel_message_info_from(info),
				/* char *subject */
				G_TYPE_STRING, camel_message_info_subject(info),
				(GType) 0);
  }

  private gboolean
    get_unseen_messages (self,
			 const char *folder_uri (check null),
			 GPtrArray **ret (check null),
			 GError **err)
  {
    CamelFolder *folder;

    GDK_THREADS_ENTER();

    folder = self_lookup_folder(folder_uri, err);
    if (folder)
      {
        int i;
#if EDS_CHECK_VERSION(3,6,0)
        GPtrArray *uids;
#else
        GPtrArray *summary;
#endif

        *ret = g_ptr_array_new();

#if EDS_CHECK_VERSION(3,6,0)
        uids = camel_folder_search_by_expression (folder, "(match-all (not (system-flag \"seen\")))", NULL, err);
        if (uids) {
          for (i = 0; i < uids->len; i++) {
            CamelMessageInfo *info = camel_folder_get_message_info(folder, uids->pdata[i]);
            if (info)
              g_ptr_array_add(*ret, self_camel_message_info_to_dbus_struct(info));
          }
          camel_folder_search_free (folder, uids);
        }
#else
	summary = camel_folder_get_summary(folder);

	for (i = 0; i < summary->len; i++)
	  {
#if EDS_CHECK_VERSION(2,23,5)
            char *uid = summary->pdata[i];
            CamelMessageInfo *info = camel_folder_get_message_info(folder, uid);
#else
	    CamelMessageInfo *info = summary->pdata[i];
#endif

	    if (info && (camel_message_info_flags(info) & CAMEL_MESSAGE_SEEN) == 0)
	      g_ptr_array_add(*ret, self_camel_message_info_to_dbus_struct(info));
	  }

	camel_folder_free_summary(folder, summary);
#endif

#if EDS_CHECK_VERSION(2,31,0)
        g_object_unref(folder);
#else
	camel_object_unref(folder);
#endif
      }

    GDK_THREADS_LEAVE();

    return folder != NULL;
  }

  private gboolean
    get_folder_name (self,
		     const char *folder_uri (check null),
		     char **ret (check null),
		     GError **err)
  {
    CamelFolder *folder;

    GDK_THREADS_ENTER();

    folder = self_lookup_folder(folder_uri, err);
    if (folder)
      {
#if EDS_CHECK_VERSION(3,1,0)
	*ret = g_strdup(camel_folder_get_display_name(folder));
#else
	*ret = g_strdup(camel_folder_get_name(folder));
#endif

#if EDS_CHECK_VERSION(2,31,0)
        g_object_unref(folder);
#else
	camel_object_unref(folder);
#endif
      }

    GDK_THREADS_LEAVE();

    return folder != NULL;
  }

  private gboolean
    open_message (self,
		  const char *folder_uri (check null),
		  const char *message_uid (check null),
		  GError **err)
  {
    CamelFolder *folder;

    GDK_THREADS_ENTER();

    folder = self_lookup_folder(folder_uri, err);
    if (folder)
      {
#if EDS_CHECK_VERSION(2,29,0)
	EShell *shell;
	EShellBackend *shell_backend;
	GtkWidget *browser;

	shell = e_shell_get_default ();
	shell_backend = e_shell_get_backend_by_name (shell, "mail");

#if EDS_CHECK_VERSION(3,5,1)
	browser = e_mail_browser_new (E_MAIL_BACKEND(shell_backend), NULL, NULL, E_MAIL_FORMATTER_MODE_NORMAL);
#else
	browser = e_mail_browser_new (E_MAIL_BACKEND(shell_backend));
#endif
#if EDS_CHECK_VERSION(3,1,0)
	e_mail_reader_set_folder (E_MAIL_READER (browser), folder);
#else
	e_mail_reader_set_folder (E_MAIL_READER (browser), folder, folder_uri);
#endif
	e_mail_reader_set_message (E_MAIL_READER (browser), message_uid);
	gtk_widget_show (browser);
#else
	GtkWidget *browser;

	/* modelled after Evolution's handleuri_got_folder() */

	browser = em_message_browser_window_new();

	em_format_set_session((EMFormat *) ((EMFolderView *) browser)->preview, session);
	em_folder_view_set_folder((EMFolderView *) browser, folder, folder_uri);
	em_folder_view_set_message((EMFolderView *) browser, message_uid, FALSE);
	gtk_widget_show(((EMMessageBrowser *) browser)->window);
#endif

#if EDS_CHECK_VERSION(2,31,0)
	g_object_unref(folder);
#else
	camel_object_unref(folder);
#endif
      }

    GDK_THREADS_LEAVE();

    return folder != NULL;
  }

  private gboolean
    set_message_flags (self,
		       const char *folder_uri (check null),
		       const char *message_uid (check null),
		       unsigned int flags,
		       GError **err)
  {
    CamelFolder *folder;
    gboolean status = FALSE;

    GDK_THREADS_ENTER();

    folder = self_lookup_folder(folder_uri, err);
    if (folder)
      {
	status = camel_folder_set_message_flags(folder, message_uid, flags, flags);
#if EDS_CHECK_VERSION(2,31,0)
        g_object_unref(folder);
#else
	camel_object_unref(folder);
#endif

	if (! status)
	  g_set_error(err,
		      MN_EVOLUTION_SERVER_ERROR,
		      MN_EVOLUTION_SERVER_ERROR_MESSAGE_NOT_FOUND,
		      dgettext(GETTEXT_PACKAGE, "message not found"));
      }

    GDK_THREADS_LEAVE();

    return status;
  }

  private gboolean
    folder_tree_new (self, guint32 id, GError **err)
  {
    MNEvolutionFolderTreeServer *tree;
    char *service;
    char *path;
    GError *tmp_err = NULL;
    gboolean status;

    GDK_THREADS_ENTER();

    tree = mn_evolution_folder_tree_server_new(id);

    service = g_strdup_printf(MN_EVOLUTION_FOLDER_TREE_SERVER_SERVICE, id);
    path = g_strdup_printf(MN_EVOLUTION_FOLDER_TREE_SERVER_PATH, id);

    status = mn_evolution_plugin_register_server(G_OBJECT(tree), service, path, &tmp_err);

    g_free(service);
    g_free(path);

    if (! status)
      {
	/* unlikely to ever happen, not worth a translation */
	g_set_error(err,
		    MN_EVOLUTION_SERVER_ERROR,
		    MN_EVOLUTION_SERVER_ERROR_FOLDER_TREE_REGISTRATION_FAILED,
		    "cannot register folder tree server: %s", tmp_err->message);
	g_error_free(tmp_err);
	g_object_unref(tree);
      }

    GDK_THREADS_LEAVE();

    return status;
  }

  public MNEvolutionServer *
    new (void)
  {
    return GET_NEW;
  }
}
