/*
 * 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.
 */

%{
#include <libnotify/notify.h>
#include "mn-message.h"
#include "mn-shell.h"
#include "mn-util.h"
#include "mn-conf.h"
#include "mn-locked-callback.h"
#include "mn-popup.h"
#include "mn-properties-dialog.h"
%}

class MN:Popups from G:Object
{
  /*
   * Maps message IDs to popups. If a message is known but has no
   * popup (because popups were disabled when that message was first
   * encountered), the value is NULL.
   */
  private GHashTable *popups = {g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) mn_g_object_null_unref)} destroywith g_hash_table_destroy;

  init (self)
  {
    if (! notify_is_initted())
      return;			/* libnotify init failed */

    mn_g_object_gconf_notifications_add_gdk_locked(self,
						   MN_CONF_POPUPS_NAMESPACE, self_notify_cb, self,
						   NULL);

    g_signal_connect(mn_shell->mailboxes, "messages-changed", G_CALLBACK(self_messages_changed_h), self);
  }

  private void
    close_cb (const char *id, MNPopup *popup, gpointer user_data)
  {
    if (popup)
      mn_popup_close(popup);
  }

  private void
    close_popups (self)
  {
    g_hash_table_foreach(selfp->popups, (GHFunc) self_close_cb, NULL);
  }

  finalize (self)
  {
    /* close popups on exit */
    self_close_popups(self);
  }

  private void
    messages_changed_h (MNMailboxes *mailboxes,
			gboolean has_new,
			gpointer user_data)
  {
    Self *self = user_data;

    self_update(self, FALSE);
  }

  private void
    notify_cb (GConfClient *client,
	       unsigned int cnxn_id,
	       GConfEntry *entry,
	       gpointer user_data)
  {
    Self *self = user_data;

    self_update(self, TRUE);
  }

  private int
    popups_show_timestamp_compare_cb (MNPopup *a, MNPopup *b)
  {
    int cmp;

    cmp = a->show_timestamp.tv_sec - b->show_timestamp.tv_sec;
    if (cmp != 0)
      return cmp;

    return a->show_timestamp.tv_usec - b->show_timestamp.tv_usec;
  }

  private void
    get_visible_popups_cb (const char *id, MNPopup *popup, GSList **list)
  {
    if (popup && popup->visible)
      *list = g_slist_insert_sorted(*list, popup, (GCompareFunc) self_popups_show_timestamp_compare_cb);
  }

  /* returns the visible popups sorted by show timestamp, oldest first */
  private GSList *
    get_visible_popups (self)
  {
    GSList *popups = NULL;

    g_hash_table_foreach(selfp->popups, (GHFunc) self_get_visible_popups_cb, &popups);

    return popups;
  }

  private gboolean
    close_and_remove_stale_cb (const char *id, MNPopup *popup, GHashTable *messages)
  {
    if (! g_hash_table_lookup(mn_shell->mailboxes->messages_hash_table, id))
      {
	if (popup)
	  mn_popup_close(popup);

	return TRUE;		/* remove */
      }
    else
      return FALSE;		/* keep */
  }

  private gboolean
    close_and_remove_visible_cb (const char *id, MNPopup *popup, gpointer user_data)
  {
    if (popup && popup->visible)
      {
	mn_popup_close(popup);

	return TRUE;		/* remove */
      }
    else
      return FALSE;		/* keep */
  }

  private void
    update (self, gboolean reshow_current_popups)
  {
    int i;

    /* close and remove stale popups */
    g_hash_table_foreach_remove(selfp->popups, (GHRFunc) self_close_and_remove_stale_cb, NULL);

    if (reshow_current_popups)
      /*
       * We have to reshow the currently visible popups, so close and
       * remove them.
       */
      g_hash_table_foreach_remove(selfp->popups, (GHRFunc) self_close_and_remove_visible_cb, NULL);

    if (mn_conf_get_bool(MN_CONF_POPUPS_ENABLED))
      {
	GPtrArray *messages;	/* for O(1) length retrieval and indexing */
	GSList *visible_popups;
	int num_visible_popups;
	int popup_limit;
	int new_popup_count;
	int messages_to_popup;	/* the number of messages to popup */

	if (mn_conf_get_enum_value(MN_TYPE_POPUP_POSITION, MN_CONF_POPUPS_POSITION) == MN_POPUP_POSITION_ATTACHED)
	  /* the popup is attached to the icon: allow at most one popup */
	  popup_limit = 1;
	else
	  popup_limit = mn_conf_get_int(MN_CONF_POPUPS_LIMIT);

	/* build a list of messages which are not already known */

	messages = g_ptr_array_new();

	MN_ARRAY_FOREACH(i, mn_shell->mailboxes->messages)
	  {
	    MNMessage *message = g_ptr_array_index(mn_shell->mailboxes->messages, i);
	    gpointer ptr;

	    if (! g_hash_table_lookup_extended(selfp->popups, message->id, NULL, &ptr))
	      g_ptr_array_add(messages, message);
	  }

	visible_popups = self_get_visible_popups(self);
	num_visible_popups = g_slist_length(visible_popups);

	new_popup_count = num_visible_popups + messages->len;
	if (new_popup_count > popup_limit)
	  {
	    int to_remove;

	    /* make room for n new popups by closing the n oldest popups */

	    to_remove = new_popup_count - popup_limit;
	    to_remove = MIN(num_visible_popups, to_remove);

	    while (to_remove > 0)
	      {
		MNPopup *oldest;

		g_assert(visible_popups != NULL);

		oldest = visible_popups->data;
		mn_popup_close(oldest);

		visible_popups = g_slist_delete_link(visible_popups, visible_popups);
		to_remove--;
	      }
	  }

	g_slist_free(visible_popups);

	messages_to_popup = MIN(messages->len, popup_limit);

	/*
	 * Below we iterate over the messages array backwards because
	 * it is sorted by sent time most recent first, while we want
	 * to popup the oldest messages first.
	 */

	/* register the messages which we will not popup */

	for (i = messages->len - 1; i >= messages_to_popup; i--)
	  {
	    MNMessage *message = g_ptr_array_index(messages, i);

	    g_hash_table_replace(selfp->popups, g_strdup(message->id), NULL);
	  }

	/* popup the first messages_to_popup messages */

	for (i = messages_to_popup - 1; i >= 0; i--)
	  {
	    MNMessage *message = g_ptr_array_index(messages, i);
	    MNPopup *popup;

	    popup = mn_popup_new(message);
	    mn_popup_show(popup);

	    g_hash_table_replace(selfp->popups, g_strdup(message->id), popup);
	  }

	g_ptr_array_free(messages, TRUE);
      }
    else
      {
	/* register the new messages */

	MN_ARRAY_FOREACH(i, mn_shell->mailboxes->messages)
	  {
	    MNMessage *message = g_ptr_array_index(mn_shell->mailboxes->messages, i);
	    g_hash_table_replace(selfp->popups, g_strdup(message->id), NULL);
	  }
      }
  }

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