#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>

#include "guiutils.h"
#include "guirgbimg.h"

#include "edv_types.h"
#include "edv_mime_type.h"


static void EDVResizePixmap(
	GdkPixmap *pixmap, GdkBitmap *mask,
	gint width, gint height,
	GdkPixmap **pixmap_rtn, GdkBitmap **mask_rtn
);


/* MIME Type Commands */
const gchar *EDVMimeTypeGetCommandValueByName(
	edv_mime_type_struct *m, const gchar *name
);
edv_mime_type_command_struct *EDVMimeTypeGetCommandByName(
	edv_mime_type_struct *m, const gchar *name
);

edv_mime_type_command_struct *EDVMimeTypeCommandNew(void);
void EDVMimeTypeCommandDelete(edv_mime_type_command_struct *cmd);


/* MIME Types */
edv_mime_type_struct *EDVMimeTypeNew(
	edv_mime_type_class mt_class,
	const gchar *value,
	const gchar *type,              /* MIME Type name */
	const gchar *description	/* Short verbose description */
);
static void EDVMimeTypeLoadIconsNexus(
	GdkPixmap **pixmap, GdkBitmap **mask,
	guint8 ***data, const gchar **file,
	gint req_width, gint req_height
);
void EDVMimeTypeLoadSmallIconsData(
	edv_mime_type_struct *m, guint8 ***data
);
void EDVMimeTypeLoadMediumIconsData(
	edv_mime_type_struct *m, guint8 ***data
);
void EDVMimeTypeLoadLargeIconsData(
	edv_mime_type_struct *m, guint8 ***data
);

void EDVMimeTypeRealize(
	edv_mime_type_struct *m, gboolean force_rerealize
);

void EDVMimeTypeDelete(edv_mime_type_struct *m);


#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)


/*
 *      Resizes the pixmap and mask to the specified new size.
 */
static void EDVResizePixmap(
	GdkPixmap *pixmap, GdkBitmap *mask,
	gint width, gint height,
	GdkPixmap **pixmap_rtn, GdkBitmap **mask_rtn
)
{
	gint old_width, old_height;

	if(pixmap_rtn != NULL)
	    *pixmap_rtn = NULL;
	if(mask_rtn != NULL)
	    *mask_rtn = NULL;

	if(pixmap == NULL)
	    return;
	if((width <= 0) || (height <= 0))   
	    return;

	gdk_window_get_size(pixmap, &old_width, &old_height);
	if((old_width <= 0) || (old_height <= 0))
	    return;
		   
	/* No change in size? */
	if((old_width == width) && (old_height == height))
	{
	    if(pixmap_rtn != NULL)
	    {
		if(pixmap != NULL)
		    gdk_pixmap_ref(pixmap);
		*pixmap_rtn = pixmap;
	    }
	    if(mask_rtn != NULL)
	    {
		if(mask != NULL)
		    gdk_bitmap_ref(mask);
		*mask_rtn = mask;
	    }
	    return;
	}
	 
	/* Resize pixmap */
	if(pixmap_rtn != NULL)
	{
	    gint sw, sh, sbpl, tbpl;
	    guint8 *tdata, *sdata;
	    GdkGC *gc = GDK_GC_NEW();

	    /* Get source RGBA data from the specified pixmap */
	    sdata = gdk_get_rgba_image(
		pixmap,
		NULL,
		&sw, &sh, &sbpl
	    );
	    if(sdata == NULL)
	    {
		GDK_GC_UNREF(gc)
		return;
	    }

	    /* Create target RGBA data */
	    tbpl = width * 4;   
	    tdata = (guint8 *)g_malloc(tbpl * height);
	    if(tdata == NULL)
	    {
		g_free(sdata);
		GDK_GC_UNREF(gc) 
		return;
	    }

	    /* Copy/resize source RGBA data to the target RGBA data */
	    GUIImageBufferResize(
		4,
		sdata, sw, sh, sbpl,
		tdata, width, height, tbpl
	    );
	      
	    g_free(sdata);

	    /* Create target pixmap */
	    *pixmap_rtn = GDK_PIXMAP_NEW(width, height);
	    if(*pixmap_rtn == NULL)
	    {
		g_free(tdata);
		GDK_GC_UNREF(gc)
		return;
	    }

	    /* Put resized target RGBA data to the target pixmap */
	    gdk_draw_rgb_32_image(
		*pixmap_rtn,   
		gc,
		0, 0,
		width, height,
		GDK_RGB_DITHER_NORMAL,
		tdata, tbpl
	    );

	    /* Resize mask */
	    if(mask_rtn != NULL)
		*mask_rtn = GUICreateBitmapFromDataRGBA(
		    width, height, tbpl,
		    tdata, 0x80,
		    NULL
		);

	    g_free(tdata);
	    GDK_GC_UNREF(gc) 
	}
}


/*
 *	Gets the command value of the MIME Type command that matches
 *	the specified command name.
 */
const gchar *EDVMimeTypeGetCommandValueByName(
	edv_mime_type_struct *m, const gchar *name
)
{
	GList *glist;
	edv_mime_type_command_struct *cmd;

	if((m == NULL) || STRISEMPTY(name))
	    return(NULL);

	for(glist = m->commands_list; glist != NULL; glist = g_list_next(glist))
	{
	    cmd = EDV_MIME_TYPE_COMMAND(glist->data);
	    if(cmd == NULL)
		continue;

	    if(cmd->name == NULL)
		continue;

	    if(!g_strcasecmp(cmd->name, name))
		return(cmd->command);
	}

	return(NULL);
}

/*
 *	Gets the MIME Type command that matches the specified command
 *	name.
 */
edv_mime_type_command_struct *EDVMimeTypeGetCommandByName(
	edv_mime_type_struct *m, const gchar *name
)
{
	GList *glist;
	edv_mime_type_command_struct *cmd;

	if((m == NULL) || STRISEMPTY(name))
	    return(NULL);

	for(glist = m->commands_list; glist != NULL; glist = g_list_next(glist))
	{
	    cmd = EDV_MIME_TYPE_COMMAND(glist->data);
	    if(cmd == NULL)
		continue;

	    if(cmd->name == NULL)
		continue;

	    if(!g_strcasecmp(cmd->name, name))
		return(cmd);
	}

	return(NULL);
}


/*
 *	Creates a new MIME Type command.
 */
edv_mime_type_command_struct *EDVMimeTypeCommandNew(void)
{
	return(EDV_MIME_TYPE_COMMAND(g_malloc0(
	    sizeof(edv_mime_type_command_struct)
	)));
}

/*
 *	Deletes the MIME Type command.
 */
void EDVMimeTypeCommandDelete(edv_mime_type_command_struct *cmd)
{
	if(cmd == NULL)
	    return;

	g_free(cmd->name);
	g_free(cmd->command);
	g_free(cmd);
}


/*
 *	Creates a new MIME Type.
 */
edv_mime_type_struct *EDVMimeTypeNew(
	edv_mime_type_class mt_class,
	const gchar *value,
	const gchar *type,		/* MIME Type name */
	const gchar *description	/* Short verbose description */
)
{
	edv_mime_type_struct *m = EDV_MIME_TYPE(
	    g_malloc0(sizeof(edv_mime_type_struct))
	);
	if(m == NULL)
	    return(m);

	m->mt_class = mt_class;

	m->value = STRDUP(value);
	m->type = STRDUP(type);
	m->description = STRDUP(description);

	m->realized = FALSE;

	m->read_only = FALSE;

	memset(
	    m->small_pixmap, 0x00,
	    EDV_MIME_TYPE_TOTAL_ICON_STATES * sizeof(GdkPixmap *)
	);
	memset(
	    m->small_mask, 0x00,
	    EDV_MIME_TYPE_TOTAL_ICON_STATES * sizeof(GdkBitmap *)
	);

	memset(
	    m->medium_pixmap, 0x00,
	    EDV_MIME_TYPE_TOTAL_ICON_STATES * sizeof(GdkPixmap *)
	);
	memset(
	    m->medium_mask, 0x00,
	    EDV_MIME_TYPE_TOTAL_ICON_STATES * sizeof(GdkBitmap *)
	);

	memset(
	    m->large_pixmap, 0x00,
	    EDV_MIME_TYPE_TOTAL_ICON_STATES * sizeof(GdkPixmap *)
	);
	memset(
	    m->large_mask, 0x00,
	    EDV_MIME_TYPE_TOTAL_ICON_STATES * sizeof(GdkBitmap *)
	);

	m->handler = EDV_MIME_TYPE_HANDLER_COMMAND;

	m->commands_list = NULL;

	m->access_time = 0l;
	m->modify_time = 0l;
	m->change_time = 0l;

	return(m);
}

/*
 *	Loads the pixmap and mask pair from either xpm data or xpm file
 *	depending if data or file is NULL.
 *
 *	Passing both data and file as NULL will only unref the existing
 *	icons (if any) and not load new ones. So this function can also
 *	be used to just delete the icons.
 */
static void EDVMimeTypeLoadIconsNexus(
	GdkPixmap **pixmap, GdkBitmap **mask,
	guint8 ***data, const gchar **file,
	gint req_width, gint req_height
)
{
	gint i;

	/* Unload icons */
	for(i = 0; i < EDV_MIME_TYPE_TOTAL_ICON_STATES; i++)
	{
	    GDK_PIXMAP_UNREF(pixmap[i])
	    pixmap[i] = NULL;
	    GDK_BITMAP_UNREF(mask[i])
	    mask[i] = NULL;
	}

	/* Load icons if the XPM datas are specified */
	if(data != NULL)
	{
	    GdkBitmap *m;
	    GdkPixmap *p;

	    /* Load icons */
	    for(i = 0; i < EDV_MIME_TYPE_TOTAL_ICON_STATES; i++)
	    {
		p = GDK_PIXMAP_NEW_FROM_XPM_DATA(&m, data[i]);
		if(p == NULL)
		    continue;

		EDVResizePixmap(
		    p, m,
		    req_width, req_height,
		    &pixmap[i], &mask[i]
		);
		GDK_PIXMAP_UNREF(p)
		GDK_BITMAP_UNREF(m)
	    }
	}
	/* Load icons if the XPM files are specified */
	else if(file != NULL)
	{
	    GdkBitmap *m;
	    GdkPixmap *p;

	    /* Load icons */
	    for(i = 0; i < EDV_MIME_TYPE_TOTAL_ICON_STATES; i++)
	    {
		p = GDK_PIXMAP_NEW_FROM_XPM_FILE(&m, file[i]);
		if(p == NULL)
		    continue;

		EDVResizePixmap(
		    p, m,
		    req_width, req_height,
		    &pixmap[i], &mask[i]
		);
		GDK_PIXMAP_UNREF(p)
		GDK_BITMAP_UNREF(m)
	    }
	}
}

/*
 *	Loads the MIME Type's small sized icons.
 *
 *	The given icon data must be an array of
 *	EDV_MIME_TYPE_TOTAL_ICON_STATES guint8 ** pointers.
 *
 *	Passing data as NULL will only unref the existing icons (if any)
 *	and not load new ones.
 */
void EDVMimeTypeLoadSmallIconsData(
	edv_mime_type_struct *m, guint8 ***data
)
{
	if(m == NULL)
	    return;

	EDVMimeTypeLoadIconsNexus(
	    m->small_pixmap, m->small_mask,
	    data, NULL,
	    20, 20
	);
}

/*
 *	Loads the MIME Type's medium sized icons.
 *
 *	The given icon data must be an array of
 *	EDV_MIME_TYPE_TOTAL_ICON_STATES guint8 ** pointers.
 *
 *	Passing data as NULL will only unref the existing icons (if any)
 *	and not load new ones.
 */
void EDVMimeTypeLoadMediumIconsData(
	edv_mime_type_struct *m, guint8 ***data
)
{
	if(m == NULL)
	    return;

	EDVMimeTypeLoadIconsNexus(
	    m->medium_pixmap, m->medium_mask,
	    data, NULL,
	    32, 32
	);
}

/*
 *	Loads the MIME Type's large sized icons.
 *
 *	The given icon data must be an array of
 *	EDV_MIME_TYPE_TOTAL_ICON_STATES guint8 ** pointers.
 *
 *	Passing data as NULL will only unref the existing icons (if any)
 *	and not load new ones.
 */
void EDVMimeTypeLoadLargeIconsData(
	edv_mime_type_struct *m, guint8 ***data
)
{
	if(m == NULL)
	    return;

	EDVMimeTypeLoadIconsNexus(
	    m->large_pixmap, m->large_mask,
	    data, NULL,
	    48, 48
	);
}


/*
 *	Realizes the MIME Type, loading the icons and related resources.
 *
 *	If the MIME Type is already realized (member realized is TRUE)
 *	then nothing will be done, however if force_rerealize is TRUE
 *	then the above check will be ignored.
 */
void EDVMimeTypeRealize(
	edv_mime_type_struct *m, gboolean force_rerealize
)
{
	const gchar **path;


	if(m == NULL)
	    return;

	/* Not forcing a re-realize? */
	if(!force_rerealize)
	{
	    /* MIME Type already realized? */
	    if(m->realized)
		return;
	}

	/* Begin realizing this MIME Type */

	/* Get paths for small sized icons and load them */
	path = (const gchar **)m->small_icon_file;
	if(!STRISEMPTY(path))
	    EDVMimeTypeLoadIconsNexus(
		m->small_pixmap, m->small_mask,
		NULL, path,
		20, 20
	    );

	/* Get paths for medium sized icons and load them */
	path = (const gchar **)m->medium_icon_file;
	if(!STRISEMPTY(path))
	    EDVMimeTypeLoadIconsNexus(
		m->medium_pixmap, m->medium_mask,
		NULL, path,
		32, 32
	    );

	/* Get paths for large sized icons and load them */
	path = (const gchar **)m->large_icon_file;
	if(!STRISEMPTY(path))
	    EDVMimeTypeLoadIconsNexus(
		m->large_pixmap, m->large_mask,
		NULL, path,
		48, 48
	    );

	m->realized = TRUE;		/* Mark as realized */
}

/*
 *	Deletes the MIME Type.
 */
void EDVMimeTypeDelete(edv_mime_type_struct *m)
{
	gint i;

	if(m == NULL)
	    return;

	/* Call the icon loaders with NULL as the argument, this will
	 * only unload the icons
	 */
	EDVMimeTypeLoadSmallIconsData(m, NULL);
	EDVMimeTypeLoadMediumIconsData(m, NULL);
	EDVMimeTypeLoadLargeIconsData(m, NULL);

	/* Delete icon file paths */
	for(i = 0; i < EDV_MIME_TYPE_TOTAL_ICON_STATES; i++)
	    g_free(m->small_icon_file[i]);

	for(i = 0; i < EDV_MIME_TYPE_TOTAL_ICON_STATES; i++)
	    g_free(m->medium_icon_file[i]);

	for(i = 0; i < EDV_MIME_TYPE_TOTAL_ICON_STATES; i++)
	    g_free(m->large_icon_file[i]);

	/* Delete the commands */
	if(m->commands_list != NULL)
	{
	    g_list_foreach(
		m->commands_list, (GFunc)EDVMimeTypeCommandDelete, NULL
	    );
	    g_list_free(m->commands_list);
	}

	g_free(m->value);

	g_free(m->type);

	g_free(m->description);

	g_free(m);
}
