/*
 * Notifier program
 *
 * Author:
 *   Miguel de Icaza (miguel@ximian.com)
 * Modifications by Ian Wienand (ianw@ieee.org)
 */
#include "gnome-notifier.h"


/* Customizable settings */
#define NHEIGHT 200
#define NWIDTH  200
#define MAX_SLOTS 30
#define STEPS 20
#define SPEED 50

/* after you have moved the mouse over the window, it will disappear
   in this many microseconds. */
#define WAIT_PERIOD 5000

#define GCONF_DIR "/apps/gnome-notifier/notifications"
#define GCONF_KEY "Message"

GError *gconf_error = NULL;
GConfClient *gconf_client = NULL;

int width, height, max_slots;
int slots[MAX_SLOTS] = {[0 ... (MAX_SLOTS - 1)] = -1 };

struct sWin {
	GtkWidget *win, *html;
	int slot;
	int size;
	gulong handlerid;
};

typedef struct sWin Win;

static char *encode_html(const char *text)
{
	return
	    g_strdup_printf
	    ("<html><body text=\"black\" background=\"miggy:background\">%s</body></html>",
	     text);
}

static int get_slot(GtkWidget * win)
{
	int i;
	for (i = 0; i < max_slots; i++)
		if (slots[i] == -1) {
			slots[i] = 1;
			return i;
		}
	return 0;
}

static int slow_hide_win(gpointer data)
{
	Win *win = (Win *) data;
	int x, y;

	if (win->size == 0) {
		gtk_widget_destroy(win->win);
		slots[win->slot] = -1;
		g_free(win);
		return FALSE;
	}

	gtk_window_get_position(GTK_WINDOW(win->win), &x, &y);
	y += STEPS;
	win->size -= STEPS;
	gtk_window_move(GTK_WINDOW(win->win), width - NWIDTH, y);
	return TRUE;
}

static int wait_win(gpointer data)
{
	gtk_timeout_add(SPEED, slow_hide_win, data);
	return FALSE;
}

static int mouseout_win(GtkWidget *widget,
			GdkEventButton *event, gpointer data)
{
	Win *win = (Win *)data;
	g_signal_handler_disconnect(G_OBJECT(win->win), win->handlerid);
	gtk_container_set_border_width(GTK_CONTAINER(win->win), 5);
	gtk_timeout_add(WAIT_PERIOD, wait_win, data);
	return TRUE;
}

static int mouseover_win(GtkWidget * widget,
			 GdkEventButton * event, gpointer data)
{
	Win *win = (Win *) data;
	g_signal_handler_disconnect(G_OBJECT(win->win), win->handlerid);
	win->handlerid =
		g_signal_connect(G_OBJECT(win->win),
				 "leave-notify-event",
				 G_CALLBACK(mouseout_win), data);
	return TRUE;
}

static int slow_show_win(gpointer data)
{
	Win *win = (Win *) data;
	int x, y;

	if (win->size == NHEIGHT) {
		win->handlerid =
		    g_signal_connect(G_OBJECT(win->win),
				     "enter-notify-event",
				     G_CALLBACK(mouseover_win), data);
		return FALSE;
	}

	gtk_window_get_position(GTK_WINDOW(win->win), &x, &y);
	y -= STEPS;
	win->size += STEPS;
	gtk_window_move(GTK_WINDOW(win->win), width - NWIDTH, y);
	return TRUE;
}

static void link_clicked(GtkHTML * html, const gchar * url, Win * w)
{
	GError *error = NULL;

	if (strncmp(url, "run:", 4) == 0) {
		GError *error = NULL;
		g_spawn_command_line_async(url + 4, &error);
	} else
		gnome_url_show(url, &error);
}

static void begin_animation(GtkWidget * win, GtkWidget * html)
{
	int slot = get_slot(win);
	int begin = height - slot * NHEIGHT;
	Win *w = g_new0(Win, 1);

	w->win = win;
	w->html = html;
	w->slot = slot;
	w->size = 0;

	g_signal_connect(html, "link_clicked", G_CALLBACK(link_clicked),
			 w);
	gtk_window_move(GTK_WINDOW(win), width - NWIDTH, begin);
	gtk_widget_show_all(win);

	gtk_timeout_add(SPEED, slow_show_win, w);
}

static void
url_requested(GtkHTML * html, const char *url, GtkHTMLStream * stream,
	      gpointer data)
{
	if (strcmp(url, "miggy:background") == 0) {
		gtk_html_stream_write(stream, image, sizeof(image));
		gtk_html_stream_close(stream, GTK_HTML_STREAM_OK);
	}
}

static void notify_new(const char *text)
{
	GtkWidget *win = gtk_window_new(GTK_WINDOW_POPUP);
	GtkWidget *html, *frame;
	char *html_text = encode_html(text);
	GtkHTMLStream *stream;

	html = gtk_html_new();
	g_signal_connect(html, "url_requested", G_CALLBACK(url_requested),
			 NULL);

	/*
	 * load the content
	 */
	stream = gtk_html_begin(GTK_HTML(html));
	gtk_html_stream_write(stream, html_text, strlen(html_text));
	gtk_html_end(GTK_HTML(html), stream, GTK_HTML_STREAM_OK);

	g_free(html_text);

	frame = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
	gtk_container_add(GTK_CONTAINER(win), frame);

	gtk_window_set_default_size(GTK_WINDOW(win), NWIDTH, NHEIGHT);
	gtk_container_add(GTK_CONTAINER(frame), html);
	begin_animation(win, html);
}

void maybe_notify(const char *text)
{
	if (text && *text)
		notify_new(text);
}

void
new_msg(GConfClient * client, guint cnxn_id, GConfEntry * entry,
	gpointer user_data)
{
	if (gconf_entry_get_value(entry) == NULL)
		return;

	if (gconf_entry_get_value(entry)->type == GCONF_VALUE_STRING) {
		maybe_notify(gconf_value_get_string
			     (gconf_entry_get_value(entry)));
		gconf_client_set_string(gconf_client,
					GCONF_DIR "/" GCONF_KEY, "", NULL);
	}
}

int main(int argc, char *argv[])
{
	gnome_program_init("Notifier", "0.1", LIBGNOMEUI_MODULE, argc,
			   argv, NULL);

	/* boot */
	height = gdk_screen_height();
	width = gdk_screen_width();
	max_slots = MAX((height - NHEIGHT) / NHEIGHT, MAX_SLOTS);

	/* init gconf, watch for notifications */
	if (!gconf_init(argc, argv, &gconf_error))
		g_error("GConf init failed:\n  %s", gconf_error->message);
	gconf_client = gconf_client_get_default();
	gconf_client_get_string(gconf_client, GCONF_DIR "/" GCONF_KEY,
				NULL);
	gconf_client_set_string(gconf_client, GCONF_DIR "/" GCONF_KEY, "",
				NULL);
	gconf_client_add_dir(gconf_client, GCONF_DIR,
			     GCONF_CLIENT_PRELOAD_ONELEVEL, &gconf_error);
	gconf_client_notify_add(gconf_client, GCONF_DIR "/" GCONF_KEY,
				new_msg, NULL, NULL, NULL);
	gtk_main();
	return 0;
}

