#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include "guiutils.h"
#include "animicon.h"


/*
 *	Flags:
 */
typedef enum {
	ANIM_ICON_REPEATING		= (1 << 0)
} AnimIconFlags;


#define AnimIconIntervalDefault		150


typedef struct _AnimIcon		AnimIcon;


/*
 *	Animated Icon:
 */
struct _AnimIcon {

	AnimIconFlags	flags;

	GdkColormap	*colormap;
	GdkGC		*gc;

	GdkPixmap	**pixmaps_list;
	GdkBitmap	**masks_list;
	GdkColor	*color_bg;

	GtkWidget	*drawing_area;
	GdkPixmap	*pixmap;	/* Back buffer */

	gint		width,
			height;

	gint		nframes,	/* Total frames */
			frame;		/* Current frame */

	gulong		interval;	/* In milliseconds */

	guint		toid;
};
#define ANIM_ICON(p)		((AnimIcon *)(p))
#define ANIM_ICON_DATA_KEY	"anim_icon"


static void AnimIconDataDestroyCB(gpointer data);
static gint AnimIconEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void AnimIconRealizeCB(GtkWidget *widget, gpointer data);
static gint AnimIconDrawTOCB(gpointer data);

static void AnimIconDraw(AnimIcon *a);

GtkWidget *AnimIconNew(gint width, gint height);
void AnimIconSetFromXPMDataList(
	GtkWidget *w, GList *data_list, gulong interval
);
void AnimIconSetColorBG(GtkWidget *w, const GdkColor *c);
void AnimIconSetRepeating(GtkWidget *w, gboolean repeating);

gint AnimIconFrames(GtkWidget *w);
gint AnimIconFrame(GtkWidget *w);
gboolean AnimIconIsPlaying(GtkWidget *w);
void AnimIconSeek(GtkWidget *w, gint frame);
void AnimIconPause(GtkWidget *w);
void AnimIconPlay(GtkWidget *w);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)


/*
 *	Animated Icon data destroy callback.
 */
static void AnimIconDataDestroyCB(gpointer data)
{
	gint i;
	AnimIcon *a = ANIM_ICON(data);
	if(a == NULL)
	    return;

	GTK_TIMEOUT_REMOVE(a->toid);
	a->toid = 0;

	for(i = 0; i < a->nframes; i++)
	{
	    GDK_PIXMAP_UNREF(a->pixmaps_list[i]);
	    GDK_BITMAP_UNREF(a->masks_list[i]);
	}
	g_free(a->pixmaps_list);
	g_free(a->masks_list);

	if(a->color_bg != NULL)
	{
	    GDK_COLORMAP_FREE_COLOR(a->colormap, a->color_bg);
	    g_free(a->color_bg);
	}

	GDK_GC_UNREF(a->gc);
	GDK_PIXMAP_UNREF(a->pixmap);
	GDK_COLORMAP_UNREF(a->colormap);


	g_free(a);
}

/*
 *	Animated Icon event signal callback.
 */
static gint AnimIconEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data 
)
{
	gint status = FALSE;
	GdkEventConfigure *configure;
	GdkEventExpose *expose;
	AnimIcon *a = ANIM_ICON(data);
	if((widget == NULL) || (event == NULL) || (a == NULL))
	    return(status);

	switch((gint)event->type)
	{
	  case GDK_CONFIGURE:
	    configure = (GdkEventConfigure *)event;
	    GDK_PIXMAP_UNREF(a->pixmap);
	    a->pixmap = gdk_pixmap_new(
		configure->window,
		configure->width, configure->height, -1
	    );
	    break;

	  case GDK_EXPOSE:
	    expose = (GdkEventExpose *)event;
	    AnimIconDraw(a);
	    break;
	}

	return(status);
}

/*
 *	GtkDrawingArea "realize" signal callback.
 */
static void AnimIconRealizeCB(GtkWidget *widget, gpointer data)
{
	gboolean need_alloc_colors = FALSE;
	GdkColormap *colormap;
	GdkGC *gc;
	GdkWindow *window;
	AnimIcon *a = ANIM_ICON(data);
	if((widget == NULL) || (a == NULL))
	    return;

	window = widget->window;
	if(window == NULL)
	    return;

	colormap = a->colormap;
	if(colormap == NULL)
	{
	    a->colormap = colormap = gdk_window_get_colormap(window);
	    if(colormap != NULL)
	    {
		gdk_colormap_ref(colormap);
		need_alloc_colors = TRUE;
	    }
	}

	gc = a->gc;
	if(gc == NULL)
	    a->gc = gc = gdk_gc_new(window);

	if(need_alloc_colors)
	    GDK_COLORMAP_ALLOC_COLOR(colormap, a->color_bg);
}

/*
 *	Animated Icon draw timeout callback.
 */
static gint AnimIconDrawTOCB(gpointer data)
{
	AnimIcon *a = ANIM_ICON(data);
	if(a == NULL)
	    return(FALSE);

	gdk_threads_enter();

	/* Go to next frame */
	a->frame++;
	if(a->frame >= a->nframes)
	{
	    if(a->flags & ANIM_ICON_REPEATING)
	    {
		a->frame = 0;
	    }
	    else
	    {
		a->frame = MAX(a->nframes - 1, 0);
		a->toid = 0;
		gdk_threads_leave();
		return(FALSE);
	    }
	}

	/* Queue draw */
	if(a->drawing_area != NULL)
	    gtk_widget_queue_draw(a->drawing_area);

	gdk_threads_leave();

	return(TRUE);
}


/*
 *	Draws the Animated Icon.
 */
static void AnimIconDraw(AnimIcon *a)
{
	gint width, height;
	GdkWindow *window;
	GdkDrawable *drawable;
	GdkGC *gc;
	GtkStateType state;
	GtkStyle *style;
	GtkWidget *w;

	if(a == NULL)
	    return;

	w = a->drawing_area;
	if(w == NULL)
	    return;

	if(!GTK_WIDGET_VISIBLE(w))
	    return;

	window = w->window;
	gc = a->gc;
	state = GTK_WIDGET_STATE(w);
	style = gtk_widget_get_style(w);
	if((window == NULL) || (gc == NULL) || (style == NULL))
	    return;

	gdk_window_get_size(window, &width, &height);
	if((width <= 0) || (height <= 0))
	    return;

	drawable = (GdkDrawable *)a->pixmap;
	if(drawable == NULL)
	    drawable = (GdkDrawable *)window;

	/* Draw background */
	if(a->color_bg != NULL)
	{
	    gdk_gc_set_foreground(gc, a->color_bg);
	    gdk_draw_rectangle(
		drawable, gc, TRUE,
		0, 0, width, height
	    );
	}
	else
	{
	    gtk_style_apply_default_background(
		style, drawable, FALSE, state,
		NULL,
		0, 0, width, height
	    );
	}

	/* Draw animated icon */
	if(a->nframes > 0)
	{
	    const gint	frame = a->frame,
			nframes = a->nframes;
	    if((frame >= 0) && (frame < nframes))
	    {
		GdkPixmap *pixmap = a->pixmaps_list[frame];
		GdkBitmap *mask = a->masks_list[frame];
		gdk_gc_set_clip_mask(gc, mask);
		gdk_gc_set_clip_origin(gc, 0, 0);
		gdk_draw_pixmap(
		    drawable, gc, pixmap,
		    0, 0, 0, 0, width, height
		);
		gdk_gc_set_clip_mask(gc, NULL);
	    }
	}

	/* Draw focus rectangle if widget is in focus */
	if(GTK_WIDGET_HAS_FOCUS(w) && GTK_WIDGET_SENSITIVE(w))
	    gtk_draw_focus( 
		style, drawable,
		0, 0, width - 1, height - 1
	    );

	/* Send drawable to window if drawable is not the window */
	if(drawable != window)
	    gdk_draw_pixmap(
		window, gc, drawable,
		0, 0, 0, 0, width, height
	    );
}


/*
 *	Creates a new Animated Icon.
 */
GtkWidget *AnimIconNew(gint width, gint height)
{
	GtkWidget *w = gtk_drawing_area_new();
	AnimIcon *a = ANIM_ICON(g_malloc0(sizeof(AnimIcon)));

	gtk_object_set_data_full(
	    GTK_OBJECT(w), ANIM_ICON_DATA_KEY,
	    a, AnimIconDataDestroyCB
	);

	gtk_widget_set_usize(w, width, height);
	gtk_widget_add_events(
	    w,
	    GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(AnimIconEventCB), a
	);
	gtk_signal_connect( 
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(AnimIconEventCB), a 
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "realize",
	    GTK_SIGNAL_FUNC(AnimIconRealizeCB), a
	);

	a->flags = 0;
	a->colormap = NULL;
	a->gc = NULL;
	a->pixmaps_list = NULL;
	a->masks_list = NULL;
	a->color_bg = NULL;
	a->drawing_area = w;
	a->pixmap = NULL;
	a->frame = 0;
	a->nframes = 0;
	a->width = width;
	a->height = height;
	a->toid = 0;

	return(w);
}

/*
 *	Sets the Animated Icon data from the specified XPM data.
 *
 *	If there is a previous animation playing then it will be stopped
 *	and unloaded.
 */
void AnimIconSetFromXPMDataList(
	GtkWidget *w, GList *data_list, gulong interval
)
{
	gint i;
	AnimIcon *a = ANIM_ICON((w != NULL) ?
	    gtk_object_get_data(GTK_OBJECT(w), ANIM_ICON_DATA_KEY) : NULL
	);
	if(a == NULL)
	    return;

	/* Remove current draw timeout if any */
	if(a->toid > 0)
	{
	    gtk_timeout_remove(a->toid);
	    a->toid = 0;
	}

	/* Delete existing pixmaps and masks lists */
	for(i = 0; i < a->nframes; i++)
	{
	    GDK_PIXMAP_UNREF(a->pixmaps_list[i]);
	    GDK_BITMAP_UNREF(a->masks_list[i]);
	}
	g_free(a->pixmaps_list);
	a->pixmaps_list = NULL;
	g_free(a->masks_list);
	a->masks_list = NULL;
	a->nframes = 0;
	a->frame = 0;
	a->interval = (gulong)AnimIconIntervalDefault;


	/* Load XPM data */
	if(data_list != NULL)
	{
	    guint8 **data;
	    GList *glist;

	    /* Get number of frames */
	    a->nframes = g_list_length(data_list);

	    /* Allocate pixmaps and masks lists */
	    a->pixmaps_list = (GdkPixmap **)g_malloc0(
		a->nframes * sizeof(GdkPixmap *)
	    );
	    a->masks_list = (GdkBitmap **)g_malloc0(
		a->nframes * sizeof(GdkBitmap *)
	    );
	    if((a->pixmaps_list == NULL) || (a->masks_list == NULL))
	    {
		g_free(a->pixmaps_list);
		a->pixmaps_list = NULL;
		g_free(a->masks_list);
		a->masks_list = NULL;
		a->nframes = 0;
		return;
	    }

	    /* Load each pixmap and mask */
	    for(glist = data_list, i = 0;
		glist != NULL;
		glist = g_list_next(glist), i++
	    )
	    {
		data = (guint8 **)glist->data;
		if(data == NULL)
		    continue;

		a->pixmaps_list[i] = GDK_PIXMAP_NEW_FROM_XPM_DATA(
		    &a->masks_list[i], (guint8 **)data
		);
	    }

	    /* Set interval */
	    a->interval = interval;
	}
}

/*
 *	Sets the Animated Icon's background color.
 */
void AnimIconSetColorBG(GtkWidget *w, const GdkColor *c)
{
	AnimIcon *a = ANIM_ICON((w != NULL) ?
	    gtk_object_get_data(GTK_OBJECT(w), ANIM_ICON_DATA_KEY) : NULL
	);
	if(a == NULL)
	    return;

	GDK_COLORMAP_FREE_COLOR(a->colormap, a->color_bg);
	g_free(a->color_bg);
	a->color_bg = NULL;

	if(c == NULL)
	    return;

	a->color_bg = (GdkColor *)g_malloc(sizeof(GdkColor));
	memcpy(a->color_bg, c, sizeof(GdkColor));
	GDK_COLORMAP_ALLOC_COLOR(a->colormap, a->color_bg);
}

/*
 *	Sets the Animated Icon to be repeating or not.
 */
void AnimIconSetRepeating(GtkWidget *w, gboolean repeating)
{
	AnimIcon *a = ANIM_ICON((w != NULL) ?
	    gtk_object_get_data(GTK_OBJECT(w), ANIM_ICON_DATA_KEY) : NULL
	);
	if(a == NULL)
	    return;

	if(repeating)
	    a->flags |= ANIM_ICON_REPEATING;
	else
	    a->flags &= ~ANIM_ICON_REPEATING;
}


/*
 *	Returns the number of frames on the Animated Icon.
 */
gint AnimIconFrames(GtkWidget *w)
{
	AnimIcon *a = ANIM_ICON((w != NULL) ?
	    gtk_object_get_data(GTK_OBJECT(w), ANIM_ICON_DATA_KEY) : NULL
	);
	return((a != NULL) ? a->nframes : 0);
}

/*
 *	Returns the Animated Icon's current frame.
 */
gint AnimIconFrame(GtkWidget *w)
{
	AnimIcon *a = ANIM_ICON((w != NULL) ?
	    gtk_object_get_data(GTK_OBJECT(w), ANIM_ICON_DATA_KEY) : NULL
	);
	return((a != NULL) ? a->frame : 0);
}

/*
 *	Checks if the Animated Icon is playing.
 */
gboolean AnimIconIsPlaying(GtkWidget *w)
{
	AnimIcon *a = ANIM_ICON((w != NULL) ?
	    gtk_object_get_data(GTK_OBJECT(w), ANIM_ICON_DATA_KEY) : NULL
	);
	return((a != NULL) ? (a->toid > 0) : FALSE);
}

/*
 *	Seek to the Animated Icon's specified frame.
 */
void AnimIconSeek(GtkWidget *w, gint frame)
{
	AnimIcon *a = ANIM_ICON((w != NULL) ?
	    gtk_object_get_data(GTK_OBJECT(w), ANIM_ICON_DATA_KEY) : NULL
	);
	if(a == NULL)
	    return;

	if((frame >= 0) && (frame < a->nframes))
	{
	    if(a->frame != frame)
	    {
		a->frame = frame;
		gtk_widget_queue_draw(w);
	    }
	}
	else
	{
	    frame = 0;
	    if(a->frame != frame)
	    {
		a->frame = frame;
		gtk_widget_queue_draw(w);
	    }
	}
}

/*
 *	Pauses the Animated Icon.
 */
void AnimIconPause(GtkWidget *w)
{
	AnimIcon *a = ANIM_ICON((w != NULL) ?
	    gtk_object_get_data(GTK_OBJECT(w), ANIM_ICON_DATA_KEY) : NULL
	);
	if(a == NULL)
	    return;

	if(a->toid > 0)
	{
	    gtk_timeout_remove(a->toid);
	    a->toid = 0;
	}
}


/*
 *	Starts playing the Animated Icon.
 */
void AnimIconPlay(GtkWidget *w)
{
	AnimIcon *a = ANIM_ICON((w != NULL) ?
	    gtk_object_get_data(GTK_OBJECT(w), ANIM_ICON_DATA_KEY) : NULL
	);
	if(a == NULL)
	    return;

	/* Already playing? */
	if(a->toid > 0)
	    return;

	gtk_widget_queue_draw(w);

	/* Set redraw timeout callback */
	if(a->interval > 0l)
	    a->toid = gtk_timeout_add(
		a->interval,
		AnimIconDrawTOCB, a
	    );
}

