/* Evolution calendar - Alarm notification engine
 *
 * Copyright (C) 2000 Helix Code, Inc.
 *
 * Authors: Federico Mena-Quintero <federico@helixcode.com>
 *
 * 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gtk/gtksignal.h>
#include <cal-util/timeutil.h>
#include "alarm.h"
#include "alarm-notify.h"



/* Whether the notification system has been initialized */
static gboolean alarm_notify_inited;

/* Clients we are monitoring for alarms */
static GHashTable *client_alarms_hash = NULL;

/* Structure that stores a client we are monitoring */
typedef struct {
	/* Monitored client */
	CalClient *client;

	/* Number of times this client has been registered */
	int refcount;

	/* Hash table of component UID -> CompQueuedAlarms.  If an element is
	 * present here, then it means its cqa->queued_alarms contains at least
	 * one queued alarm.  When all the alarms for a component have been
	 * dequeued, the CompQueuedAlarms structure is removed from the hash
	 * table.  Thus a CQA exists <=> it has queued alarms.
	 */
	GHashTable *uid_alarms_hash;
} ClientAlarms;

/* Pair of a CalComponentAlarms and the mapping from queued alarm IDs to the
 * actual alarm instance structures.
 */
typedef struct {
	/* The parent client alarms structure */
	ClientAlarms *parent_client;

	/* The actual component and its alarm instances */
	CalComponentAlarms *alarms;

	/* List of QueuedAlarm structures */
	GSList *queued_alarms;
} CompQueuedAlarms;

/* Pair of a queued alarm ID and the alarm trigger instance it refers to */
typedef struct {
	/* Alarm ID from alarm.h */
	gpointer alarm_id;

	/* Instance from our parent CompAlarms->alarms list */
	CalAlarmInstance *instance;
} QueuedAlarm;

/* Alarm ID for the midnight refresh function */
static gpointer midnight_refresh_id = NULL;



static void load_alarms (ClientAlarms *ca);
static void midnight_refresh_cb (gpointer alarm_id, time_t trigger, gpointer data);

/* Queues an alarm trigger for midnight so that we can load the next day's worth
 * of alarms.
 */
static void
queue_midnight_refresh (void)
{
	time_t midnight;

	g_assert (midnight_refresh_id == NULL);

	midnight = time_day_end (time (NULL));

	midnight_refresh_id = alarm_add (midnight, midnight_refresh_cb, NULL, NULL);
	if (!midnight_refresh_id) {
		g_message ("alarm_notify_init(): Could not set up the midnight refresh alarm!");
		/* FIXME: what to do? */
	}
}

/* Loads a client's alarms; called from g_hash_table_foreach() */
static void
add_client_alarms_cb (gpointer key, gpointer value, gpointer data)
{
	ClientAlarms *ca;

	ca = value;
	load_alarms (ca);
}

/* Loads the alarms for the new day every midnight */
static void
midnight_refresh_cb (gpointer alarm_id, time_t trigger, gpointer data)
{
	/* Re-load the alarms for all clients */

	g_hash_table_foreach (client_alarms_hash, add_client_alarms_cb, NULL);

	/* Re-schedule the midnight update */

	midnight_refresh_id = NULL;
	queue_midnight_refresh ();
}

/* Looks up a client in the client alarms hash table */
static ClientAlarms *
lookup_client (CalClient *client)
{
	return g_hash_table_lookup (client_alarms_hash, client);
}

/* Callback used when an alarm triggers */
static void
alarm_trigger_cb (gpointer alarm_id, time_t trigger, gpointer data)
{
	CompQueuedAlarms *cqa;

	cqa = data;

	/* FIXME */

	g_message ("alarm_trigger_cb(): Triggered!");
}

/* Callback used when an alarm must be destroyed */
static void
alarm_destroy_cb (gpointer alarm_id, gpointer data)
{
	CompQueuedAlarms *cqa;
	GSList *l;
	QueuedAlarm *qa;
	const char *uid;

	cqa = data;

	qa = NULL; /* Keep GCC happy */

	/* Find the alarm in the queued alarms */

	for (l = cqa->queued_alarms; l; l = l->next) {
		qa = l->data;
		if (qa->alarm_id == alarm_id)
			break;
	}

	g_assert (l != NULL);

	/* Remove it and free it */

	cqa->queued_alarms = g_slist_remove_link (cqa->queued_alarms, l);
	g_slist_free_1 (l);

	g_free (qa);

	/* If this was the last queued alarm for this component, remove the
	 * component itself.
	 */

	if (cqa->queued_alarms != NULL)
		return;

	cal_component_get_uid (cqa->alarms->comp, &uid);
	g_hash_table_remove (cqa->parent_client->uid_alarms_hash, uid);
	cqa->parent_client = NULL;

	cal_component_alarms_free (cqa->alarms);
	cqa->alarms = NULL;

	g_free (cqa);
}

/* Adds the alarms in a CalComponentAlarms structure to the alarms queued for a
 * particular client.  Also puts the triggers in the alarm timer queue.
 */
static void
add_component_alarms (ClientAlarms *ca, CalComponentAlarms *alarms)
{
	const char *uid;
	CompQueuedAlarms *cqa;
	GSList *l;

	/* No alarms? */
	if (alarms->alarms == NULL) {
		cal_component_alarms_free (alarms);
		return;
	}

	cqa = g_new (CompQueuedAlarms, 1);
	cqa->parent_client = ca;
	cqa->alarms = alarms;

	cqa->queued_alarms = NULL;

	for (l = alarms->alarms; l; l = l->next) {
		CalAlarmInstance *instance;
		gpointer alarm_id;
		QueuedAlarm *qa;

		instance = l->data;

		alarm_id = alarm_add (instance->trigger, alarm_trigger_cb, cqa, alarm_destroy_cb);
		if (!alarm_id) {
			g_message ("add_component_alarms(): Could not schedule a trigger for "
				   "%ld, discarding...", (long) instance->trigger);
			continue;
		}

		qa = g_new (QueuedAlarm, 1);
		qa->alarm_id = alarm_id;
		qa->instance = instance;

		cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa);
	}

	cal_component_get_uid (alarms->comp, &uid);

	/* If we failed to add all the alarms, then we should get rid of the cqa */
	if (cqa->queued_alarms == NULL) {
		g_message ("add_component_alarms(): Could not add any of the alarms "
			   "for the component `%s'; discarding it...", uid);

		cal_component_alarms_free (cqa->alarms);
		cqa->alarms = NULL;

		g_free (cqa);
		return;
	}

	cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms);
	g_hash_table_insert (ca->uid_alarms_hash, (char *) uid, cqa);
}

/* Loads today's remaining alarms for a client */
static void
load_alarms (ClientAlarms *ca)
{
	time_t now, day_end;
	GSList *comp_alarms;
	GSList *l;

	now = time (NULL);
	day_end = time_day_end (now);

	comp_alarms = cal_client_get_alarms_in_range (ca->client, now, day_end);

	/* All of the last day's alarms should have already triggered and should
	 * have been removed, so we should have no pending components.
	 */
	g_assert (g_hash_table_size (ca->uid_alarms_hash) == 0);

	for (l = comp_alarms; l; l = l->next) {
		CalComponentAlarms *alarms;

		alarms = l->data;
		add_component_alarms (ca, alarms);
	}

	g_slist_free (comp_alarms);
}

/* Called when a calendar client finished loading; we load its alarms */
static void
cal_loaded_cb (CalClient *client, CalClientLoadStatus status, gpointer data)
{
	ClientAlarms *ca;

	ca = data;

	if (status != CAL_CLIENT_LOAD_SUCCESS)
		return;

	load_alarms (ca);
}

/* Looks up a component's queued alarm structure in a client alarms structure */
static CompQueuedAlarms *
lookup_comp_queued_alarms (ClientAlarms *ca, const char *uid)
{
	return g_hash_table_lookup (ca->uid_alarms_hash, uid);
}

/* Removes a component an its alarms */
static void
remove_comp (ClientAlarms *ca, const char *uid)
{
	CompQueuedAlarms *cqa;
	GSList *l;

	cqa = lookup_comp_queued_alarms (ca, uid);
	if (!cqa)
		return;

	/* If a component is present, then it means we must have alarms queued
	 * for it.
	 */
	g_assert (cqa->queued_alarms != NULL);

	for (l = cqa->queued_alarms; l;) {
		QueuedAlarm *qa;

		qa = l->data;

		/* Get the next element here because the list element will go
		 * away.  Also, we do not free the qa here because it will be
		 * freed by the destroy notification function.
		 */
		l = l->next;

		alarm_remove (qa->alarm_id);
	}

	/* The list should be empty now, and thus the queued component alarms
	 * structure should have been freed and removed from the hash table.
	 */
	g_assert (lookup_comp_queued_alarms (ca, uid) == NULL);
}

/* Called when a calendar component changes; we must reload its corresponding
 * alarms.
 */
static void
obj_updated_cb (CalClient *client, const char *uid, gpointer data)
{
	ClientAlarms *ca;
	time_t now, day_end;
	CalComponentAlarms *alarms;
	gboolean found;

	ca = data;

	remove_comp (ca, uid);

	now = time (NULL);
	day_end = time_day_end (now);

	found = cal_client_get_alarms_for_object (ca->client, uid, now, day_end, &alarms);

	if (!found)
		return;

	add_component_alarms (ca, alarms);
}

/* Called when a calendar component is removed; we must delete its corresponding
 * alarms.
 */
static void
obj_removed_cb (CalClient *client, const char *uid, gpointer data)
{
	ClientAlarms *ca;

	ca = data;

	remove_comp (ca, uid);
}



/**
 * alarm_notify_init:
 *
 * Initializes the alarm notification system.  This should be called near the
 * beginning of the program, after calling alarm_init().
 **/
void
alarm_notify_init (void)
{
	g_return_if_fail (alarm_notify_inited == FALSE);

	client_alarms_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
	queue_midnight_refresh ();

	alarm_notify_inited = TRUE;
}

/**
 * alarm_notify_done:
 *
 * Shuts down the alarm notification system.  This should be called near the end
 * of the program.  All the monitored calendar clients should already have been
 * unregistered with alarm_notify_remove_client().
 **/
void
alarm_notify_done (void)
{
	g_return_if_fail (alarm_notify_inited);

	/* All clients must be unregistered by now */
	g_return_if_fail (g_hash_table_size (client_alarms_hash) == 0);

	g_hash_table_destroy (client_alarms_hash);
	client_alarms_hash = NULL;

	g_assert (midnight_refresh_id != NULL);
	alarm_remove (midnight_refresh_id);
	midnight_refresh_id = NULL;

	alarm_notify_inited = FALSE;
}

/**
 * alarm_notify_add_client:
 * @client: A calendar client.
 *
 * Adds a calendar client to the alarm notification system.  Alarm trigger
 * notifications will be presented at the appropriate times.  The client should
 * be removed with alarm_notify_remove_client() when receiving notifications
 * from it is no longer desired.
 *
 * A client can be added any number of times to the alarm notification system,
 * but any single alarm trigger will only be presented once for a particular
 * client.  The client must still be removed the same number of times from the
 * notification system when it is no longer wanted.
 **/
void
alarm_notify_add_client (CalClient *client)
{
	ClientAlarms *ca;

	g_return_if_fail (alarm_notify_inited);
	g_return_if_fail (client != NULL);
	g_return_if_fail (IS_CAL_CLIENT (client));

	ca = lookup_client (client);
	if (ca) {
		ca->refcount++;
		return;
	}

	ca = g_new (ClientAlarms, 1);

	ca->client = client;
	gtk_object_ref (GTK_OBJECT (ca->client));

	ca->refcount = 1;
	g_hash_table_insert (client_alarms_hash, client, ca);

	ca->uid_alarms_hash = g_hash_table_new (g_str_hash, g_str_equal);

	if (!cal_client_is_loaded (client))
		gtk_signal_connect (GTK_OBJECT (client), "cal_loaded",
				    GTK_SIGNAL_FUNC (cal_loaded_cb), ca);

	gtk_signal_connect (GTK_OBJECT (client), "obj_updated",
			    GTK_SIGNAL_FUNC (obj_updated_cb), ca);
	gtk_signal_connect (GTK_OBJECT (client), "obj_removed",
			    GTK_SIGNAL_FUNC (obj_removed_cb), ca);

	if (cal_client_is_loaded (client))
		load_alarms (ca);
}

/* Called from g_hash_table_foreach(); adds a component UID to a list */
static void
add_uid_cb (gpointer key, gpointer value, gpointer data)
{
	GSList **uids;
	const char *uid;

	uids = data;
	uid = key;

	*uids = g_slist_prepend (*uids, (char *) uid);
}

/* Removes all the alarms queued for a particular calendar client */
static void
remove_client_alarms (ClientAlarms *ca)
{
	GSList *uids;
	GSList *l;

	/* First we build a list of UIDs so that we can remove them one by one */

	uids = NULL;
	g_hash_table_foreach (ca->uid_alarms_hash, add_uid_cb, &uids);

	for (l = uids; l; l = l->next) {
		const char *uid;

		uid = l->data;

		remove_comp (ca, uid);
	}

	g_slist_free (uids);

	/* The hash table should be empty now */

	g_assert (g_hash_table_size (ca->uid_alarms_hash) == 0);
}

/**
 * alarm_notify_remove_client:
 * @client: A calendar client.
 *
 * Removes a calendar client from the alarm notification system.
 **/
void
alarm_notify_remove_client (CalClient *client)
{
	ClientAlarms *ca;

	g_return_if_fail (alarm_notify_inited);
	g_return_if_fail (client != NULL);
	g_return_if_fail (IS_CAL_CLIENT (client));

	ca = lookup_client (client);
	g_return_if_fail (ca != NULL);

	g_assert (ca->refcount > 0);
	ca->refcount--;

	if (ca->refcount > 0)
		return;

	remove_client_alarms (ca);

	/* Clean up */

	gtk_signal_disconnect_by_data (GTK_OBJECT (ca->client), ca);

	gtk_object_unref (GTK_OBJECT (ca->client));
	ca->client = NULL;

	g_hash_table_destroy (ca->uid_alarms_hash);
	ca->uid_alarms_hash = NULL;

	g_free (ca);

	g_hash_table_remove (client_alarms_hash, client);
}



#if 0

/* Sends a mail notification of an alarm trigger */
static void
mail_notification (char *mail_address, char *text, time_t app_time)
{
	pid_t pid;
	int   p [2];
	char *command;

	pipe (p);
	pid = fork ();
	if (pid == 0){
		int dev_null;

		dev_null = open ("/dev/null", O_RDWR);
		dup2 (p [0], 0);
		dup2 (dev_null, 1);
		dup2 (dev_null, 2);
		execl ("/usr/lib/sendmail", "/usr/lib/sendmail",
		       mail_address, NULL);
		_exit (127);
	}
	command = g_strconcat ("To: ", mail_address, "\n",
			       "Subject: ", _("Reminder of your appointment at "),
			       ctime (&app_time), "\n\n", text, "\n", NULL);
	write (p [1], command, strlen (command));
 	close (p [1]);
	close (p [0]);
	g_free (command);
}

static int
max_open_files (void)
{
        static int files;

        if (files)
                return files;

        files = sysconf (_SC_OPEN_MAX);
        if (files != -1)
                return files;
#ifdef OPEN_MAX
        return files = OPEN_MAX;
#else
        return files = 256;
#endif
}

/* Executes a program as a notification of an alarm trigger */
static void
program_notification (char *command, int close_standard)
{
	struct sigaction ignore, save_intr, save_quit;
	int status = 0, i;
	pid_t pid;

	ignore.sa_handler = SIG_IGN;
	sigemptyset (&ignore.sa_mask);
	ignore.sa_flags = 0;

	sigaction (SIGINT, &ignore, &save_intr);
	sigaction (SIGQUIT, &ignore, &save_quit);

	if ((pid = fork ()) < 0){
		fprintf (stderr, "\n\nfork () = -1\n");
		return;
	}
	if (pid == 0){
		pid = fork ();
		if (pid == 0){
			const int top = max_open_files ();
			sigaction (SIGINT,  &save_intr, NULL);
			sigaction (SIGQUIT, &save_quit, NULL);

			for (i = (close_standard ? 0 : 3); i < top; i++)
				close (i);

			/* FIXME: As an excercise to the reader, copy the
			 * code from mc to setup shell properly instead of
			 * /bin/sh.  Yes, this comment is larger than a cut and paste.
			 */
			execl ("/bin/sh", "/bin/sh", "-c", command, (char *) 0);

			_exit (127);
		} else {
			_exit (127);
		}
	}
	wait (&status);
	sigaction (SIGINT,  &save_intr, NULL);
	sigaction (SIGQUIT, &save_quit, NULL);
}

/* Queues a snooze alarm */
static void
snooze (GnomeCalendar *gcal, CalComponent *comp, time_t occur, int snooze_mins, gboolean audio)
{
	time_t now, trigger;
	struct tm tm;
	CalAlarmInstance ai;

	now = time (NULL);
	tm = *localtime (&now);
	tm.tm_min += snooze_mins;

	trigger = mktime (&tm);
	if (trigger == -1) {
		g_message ("snooze(): produced invalid time_t; not queueing alarm!");
		return;
	}

#if 0
	cal_component_get_uid (comp, &ai.uid);
	ai.type = audio ? ALARM_AUDIO : ALARM_DISPLAY;
#endif
	ai.trigger = trigger;
	ai.occur = occur;

	setup_alarm (gcal, &ai);
}

struct alarm_notify_closure {
	GnomeCalendar *gcal;
	CalComponent *comp;
	time_t occur;
};

/* Callback used for the result of the alarm notification dialog */
static void
display_notification_cb (AlarmNotifyResult result, int snooze_mins, gpointer data)
{
	struct alarm_notify_closure *c;

	c = data;

	switch (result) {
	case ALARM_NOTIFY_CLOSE:
		break;

	case ALARM_NOTIFY_SNOOZE:
		snooze (c->gcal, c->comp, c->occur, snooze_mins, FALSE);
		break;

	case ALARM_NOTIFY_EDIT:
		gnome_calendar_edit_object (c->gcal, c->comp);
		break;

	default:
		g_assert_not_reached ();
	}

	gtk_object_unref (GTK_OBJECT (c->comp));
	g_free (c);
}

/* Present a display notification of an alarm trigger */
static void
display_notification (time_t trigger, time_t occur, CalComponent *comp, GnomeCalendar *gcal)
{
	gboolean result;
	struct alarm_notify_closure *c;

	gtk_object_ref (GTK_OBJECT (comp));

	c = g_new (struct alarm_notify_closure, 1);
	c->gcal = gcal;
	c->comp = comp;
	c->occur = occur;

	result = alarm_notify_dialog (trigger, occur, comp, display_notification_cb, c);
	if (!result) {
		g_message ("display_notification(): could not display the alarm notification dialog");
		g_free (c);
		gtk_object_unref (GTK_OBJECT (comp));
	}
}

/* Present an audible notification of an alarm trigger */
static void
audio_notification (time_t trigger, time_t occur, CalComponent *comp, GnomeCalendar *gcal)
{
	g_message ("AUDIO NOTIFICATION!");
	/* FIXME */
}

/* Callback function used when an alarm is triggered */
static void
trigger_alarm_cb (gpointer alarm_id, time_t trigger, gpointer data)
{
	struct trigger_alarm_closure *c;
	GnomeCalendarPrivate *priv;
	CalComponent *comp;
	CalClientGetStatus status;
	const char *uid;
	ObjectAlarms *oa;
   	GList *l;

	c = data;
	priv = c->gcal->priv;

	/* Fetch the object */

	status = cal_client_get_object (priv->client, c->uid, &comp);

	switch (status) {
	case CAL_CLIENT_GET_SUCCESS:
		/* Go on */
		break;
	case CAL_CLIENT_GET_SYNTAX_ERROR:
	case CAL_CLIENT_GET_NOT_FOUND:
		g_message ("trigger_alarm_cb(): syntax error in fetched object");
		return;
	}

	g_assert (comp != NULL);

	/* Present notification */

	switch (c->type) {
	case CAL_COMPONENT_ALARM_EMAIL:
#if 0
		g_assert (ico->malarm.enabled);
		mail_notification (ico->malarm.data, ico->summary, c->occur);
#endif
		break;

	case CAL_COMPONENT_ALARM_PROCEDURE:
#if 0
		g_assert (ico->palarm.enabled);
		program_notification (ico->palarm.data, FALSE);
#endif
		break;

	case CAL_COMPONENT_ALARM_DISPLAY:
#if 0
		g_assert (ico->dalarm.enabled);
#endif
		display_notification (trigger, c->occur, comp, c->gcal);
		break;

	case CAL_COMPONENT_ALARM_AUDIO:
#if 0
		g_assert (ico->aalarm.enabled);
#endif
		audio_notification (trigger, c->occur, comp, c->gcal);
		break;

	default:
		break;
	}

	/* Remove the alarm from the hash table */
	cal_component_get_uid (comp, &uid);
	oa = g_hash_table_lookup (priv->alarms, uid);
	g_assert (oa != NULL);

	l = g_list_find (oa->alarm_ids, alarm_id);
	g_assert (l != NULL);

	oa->alarm_ids = g_list_remove_link (oa->alarm_ids, l);
	g_list_free_1 (l);

	if (!oa->alarm_ids) {
		g_hash_table_remove (priv->alarms, uid);
		g_free (oa->uid);
		g_free (oa);
	}

	gtk_object_unref (GTK_OBJECT (comp));
}

#endif

#if 0

static void
stop_beeping (GtkObject* object, gpointer data)
{
	guint timer_tag, beep_tag;
	timer_tag = GPOINTER_TO_INT (gtk_object_get_data (object, "timer_tag"));
	beep_tag  = GPOINTER_TO_INT (gtk_object_get_data (object, "beep_tag"));

	if (beep_tag > 0) {
		gtk_timeout_remove (beep_tag);
		gtk_object_set_data (object, "beep_tag", GINT_TO_POINTER (0));
	}
	if (timer_tag > 0) {
		gtk_timeout_remove (timer_tag);
		gtk_object_set_data (object, "timer_tag", GINT_TO_POINTER (0));
	}
}

static gint
start_beeping (gpointer data)
{
	gdk_beep ();

	return TRUE;
}

static gint
timeout_beep (gpointer data)
{
	stop_beeping (data, NULL);
	return FALSE;
}

void
calendar_notify (time_t activation_time, CalendarAlarm *which, void *data)
{
	iCalObject *ico = data;
	guint beep_tag, timer_tag;
	int ret;
	gchar* snooze_button = (enable_snooze ? _("Snooze") : NULL);
	time_t now, diff;

	if (&ico->aalarm == which){
		time_t app = ico->aalarm.trigger + ico->aalarm.offset;
		GtkWidget *w;
		char *msg;

		msg = g_strconcat (_("Reminder of your appointment at "),
					ctime (&app), "`",
					ico->summary, "'", NULL);

		/* Idea: we need Snooze option :-) */
		w = gnome_message_box_new (msg, GNOME_MESSAGE_BOX_INFO, _("Ok"), snooze_button, NULL);
		beep_tag = gtk_timeout_add (1000, start_beeping, NULL);
		if (enable_aalarm_timeout)
			timer_tag = gtk_timeout_add (audio_alarm_timeout*1000,
						     timeout_beep, w);
		else
			timer_tag = 0;
		gtk_object_set_data (GTK_OBJECT (w), "timer_tag",
				     GINT_TO_POINTER (timer_tag));
		gtk_object_set_data (GTK_OBJECT (w), "beep_tag",
				     GINT_TO_POINTER (beep_tag));
		gtk_widget_ref (w);
		gtk_window_set_modal (GTK_WINDOW (w), FALSE);
		ret = gnome_dialog_run (GNOME_DIALOG (w));
		switch (ret) {
		case 1:
			stop_beeping (GTK_OBJECT (w), NULL);
			now = time (NULL);
			diff = now - which->trigger;
			which->trigger = which->trigger + diff + snooze_secs;
			which->offset  = which->offset - diff - snooze_secs;
			alarm_add (which, &calendar_notify, data);
			break;
		default:
			stop_beeping (GTK_OBJECT (w), NULL);
			break;
		}

		gtk_widget_unref (w);
		return;
	}

        if (&ico->palarm == which){
		execute (ico->palarm.data, 0);
		return;
	}

	if (&ico->malarm == which){
		time_t app = ico->malarm.trigger + ico->malarm.offset;

		mail_notify (ico->malarm.data, ico->summary, app);
		return;
	}

	if (&ico->dalarm == which){
		time_t app = ico->dalarm.trigger + ico->dalarm.offset;
		GtkWidget *w;
		char *msg;

		if (beep_on_display)
			gdk_beep ();
		msg = g_strconcat (_("Reminder of your appointment at "),
					ctime (&app), "`",
					ico->summary, "'", NULL);
		w = gnome_message_box_new (msg, GNOME_MESSAGE_BOX_INFO,
					   _("Ok"), snooze_button, NULL);
		gtk_window_set_modal (GTK_WINDOW (w), FALSE);
		ret = gnome_dialog_run (GNOME_DIALOG (w));
		switch (ret) {
		case 1:
			now = time (NULL);
			diff = now - which->trigger;
			which->trigger = which->trigger + diff + snooze_secs;
			which->offset  = which->offset - diff - snooze_secs;
			alarm_add (which, &calendar_notify, data);
			break;
		default:
			break;
		}

		return;
	}
}

#endif