#include <string.h>
#include <sys/vfs.h>
#if defined(__SOLARIS__)
# include <sys/statvfs.h>
#endif
#include <gtk/gtk.h>

#include "../include/string.h"

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

#include "edvtypes.h"
#include "edvdevices.h"
#include "edvdevicesfio.h"


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


/* Devices List Matching */
edv_device_struct *EDVDeviceListMatchMountPath(
	edv_device_struct **list, gint total,
	gint *n,
	const gchar *mount_path
);
edv_device_struct *EDVDeviceListMatchDevicePath(
	edv_device_struct **list, gint total,
	gint *n,
	const gchar *device_path
);
edv_device_struct *EDVDeviceListMatchObject(
	edv_device_struct **list, gint total,
	gint *n,
	const gchar *path
);

/* Devices List Updating */
void EDVDevicesListUpdateMountStates(
	edv_device_struct **list, gint total
);
void EDVDevicesListUpdateStats(
	edv_device_struct **list, gint total
);

/* Filesystem */
guint32 EDVDeviceGetFSFromString(const gchar *fs_type_str);
const gchar *EDVDeviceGetFSStringFromNumber(guint32 fs_type);

/* Devices */
edv_device_struct *EDVDeviceNew(
	guint32 fs_type,		/* One of EDV_FS_TYPE_* */
	const gchar *name,		/* Descriptive name */
	const gchar *device_path,       /* Path to device object */
	const gchar *mount_path         /* Path to mount point directory */
);
static void EDVDeviceLoadIconsNexus(
	GdkPixmap **pixmap, GdkBitmap **mask,
	guint8 ***data, const gchar **file,
	gint req_width, gint req_height
);
void EDVDeviceLoadSmallIconsData(
	edv_device_struct *d, guint8 ***data
);
void EDVDeviceMediumIconsData(
	edv_device_struct *d, guint8 ***data
);
void EDVDeviceLoadLargeIconsData(
	edv_device_struct *d, guint8 ***data
);

void EDVDeviceRealize(
	edv_device_struct *d, gboolean force_rerealize
);

void EDVDeviceDelete(edv_device_struct *d);


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


/*
 *	Matches the Device from the Devices List who's mount_path 
 *	matches the specified mount_path. 
 */
edv_device_struct *EDVDeviceListMatchMountPath(
	edv_device_struct **list, gint total,
	gint *n,
	const gchar *mount_path
)
{
	gint i;
	edv_device_struct *d;

	if(n != NULL)
	    *n = -1;

	if(STRISEMPTY(mount_path))
	    return(NULL);

	/* Iterate through devices list */
	for(i = 0; i < total; i++)
	{
	    d = list[i];
	    if((d != NULL) ? STRISEMPTY(d->mount_path) : TRUE)
		continue;

	    if(!strcmp(d->mount_path, mount_path))
	    {
		if(n != NULL)
		    *n = i;
		return(d);
	    }
	}

	return(NULL);
}

/*
 *	Matches the Device from the Devices List who's device_path
 *	matches the specified device_path.
 */
edv_device_struct *EDVDeviceListMatchDevicePath(
	edv_device_struct **list, gint total,
	gint *n,
	const gchar *device_path
)
{
	gint i;
	edv_device_struct *d;

	if(n != NULL)
	    *n = -1;

	if(STRISEMPTY(device_path))
	    return(NULL);

	/* Iterate through devices list */
	for(i = 0; i < total; i++)
	{
	    d = list[i];
	    if((d != NULL) ? STRISEMPTY(d->device_path) : TRUE)
		continue;

	    if(!strcmp(d->device_path, device_path))
	    {
		if(n != NULL)
		    *n = i;
		return(d);
	    }
	}

	return(NULL);
}

/*
 *	Matches the Device from the Devices List that the object
 *	specified by path is on.
 */
edv_device_struct *EDVDeviceListMatchObject(
	edv_device_struct **list, gint total,
	gint *n,
	const gchar *path
)
{
	gint i;
	edv_device_struct *d;

	if(n != NULL)
	    *n = -1;

	if(STRISEMPTY(path))
	    return(NULL);

	/* Iterate through devices list */
	for(i = 0; i < total; i++)
	{
	    d = list[i];
	    if((d != NULL) ? STRISEMPTY(d->mount_path) : TRUE)
		continue;

	    if(strpfx(path, d->mount_path))
	    {
		if(n != NULL)
		    *n = i;
		return(d);
	    }
	}    
	 
	return(NULL);
}


/*
 *	Updates the mount states on the Devices List.
 *
 *	The is_mounted state will be updated on all the devices in the
 *	list.
 */
void EDVDevicesListUpdateMountStates(
	edv_device_struct **list, gint total
)
{
	EDVDevicesListUpdateMountStatesFromSystem(
	    list, total
	);
}

/*
 *	Updates statistics on the Devices List.
 *
 *	If a Device is not marked as mounted then it will be skipped and
 *	its stats will be garbage.
 *
 *	EDVDevicesListUpdateMountStates() should be called prior to this
 *	function per update.
 */
void EDVDevicesListUpdateStats(
	edv_device_struct **list, gint total
)
{
	gint i;
	const gchar *mount_path;
	edv_device_struct *dev_ptr;
#if defined(__SOLARIS__)
	struct statvfs buf;
#else
	struct statfs buf;
#endif

	/* Iterate through devices */
	for(i = 0; i < total; i++)
	{
	    dev_ptr = list[i];
	    if(dev_ptr == NULL)
		continue;

	    /* Reset stats */
	    dev_ptr->blocks_total = 0l;
	    dev_ptr->blocks_available = 0l;
	    dev_ptr->blocks_free = 0l;

	    /* Get stats only if mounted */
	    if(dev_ptr->is_mounted)
	    {
		mount_path = dev_ptr->mount_path;
#if defined(__SOLARIS__)
		if(!STRISEMPTY(mount_path) ?
		    !statvfs(mount_path, &buf) : FALSE
		)
#else
		if(!STRISEMPTY(mount_path) ?
		    !statfs(mount_path, &buf) : FALSE
		)
#endif
		{
		    /* Check if the block size transfer rate (which is
		     * really just the block size), is larger than or
		     * equal to the base of 1024 bytes per block
		     */
		    const gulong block_size = buf.f_bsize;
		    if(block_size >= 1024l)
		    {
			const gulong block_div = block_size / 1024l;

			dev_ptr->blocks_total = buf.f_blocks * block_div;
			dev_ptr->blocks_available = buf.f_bavail * block_div;
			dev_ptr->blocks_free = buf.f_bfree * block_div;
		    }
		    else if(block_size > 0l)
		    {
			/* Block size is less than 1024 bytes but positive */
			const gulong block_div = 1024l / block_size;

			/* Note, block_div is in range of 1 to 1024 */
			dev_ptr->blocks_total = buf.f_blocks / block_div;
			dev_ptr->blocks_available = buf.f_bavail / block_div;
			dev_ptr->blocks_free = buf.f_bfree / block_div;
		    }
		}
	    }
	}
}


/*
 *	Returns one of EDV_FS_TYPE_* based on the given fs_type_str.
 */
guint32 EDVDeviceGetFSFromString(const gchar *fs_type_str)
{
	gint i;
	const edv_fs_type_struct *fs_type_ptr;
	const edv_fs_type_struct fs_type_list[] = EDV_FS_TYPE_LIST;

	if(STRISEMPTY(fs_type_str))
	    return(EDV_FS_TYPE_EMPTY);

	for(i = 0; fs_type_list[i].fs_type; i++)
	{
	    fs_type_ptr = &fs_type_list[i];
	    if(fs_type_ptr->name != NULL)
	    {
		if(!g_strcasecmp(fs_type_ptr->name, fs_type_str))
		    return(fs_type_ptr->fs_type);
	    }
	}

	return(EDV_FS_TYPE_EMPTY);
}

/*
 *      Returns a statically allocated string containing the name of
 *	the file system specified by fs_type.
 */
const gchar *EDVDeviceGetFSStringFromNumber(guint32 fs_type)
{
	gint i;
	const edv_fs_type_struct *fs_type_ptr;
	const edv_fs_type_struct fs_type_list[] = EDV_FS_TYPE_LIST;

	for(i = 0; fs_type_list[i].fs_type; i++)
	{
	    fs_type_ptr = &fs_type_list[i];
	    if(fs_type_ptr->name != NULL)
	    {
		if(fs_type_ptr->fs_type == fs_type)
		    return(fs_type_ptr->name);
	    }
	}

	return("");
}


/*
 *	Creates a new Device.
 */
edv_device_struct *EDVDeviceNew(
	guint32 fs_type,		/* One of EDV_FS_TYPE_* */
	const gchar *name,		/* Descriptive name */
	const gchar *device_path,	/* Path to device object */
	const gchar *mount_path		/* Path to mount point directory */
)
{
	edv_device_struct *d = EDV_DEVICE(
	    g_malloc0(sizeof(edv_device_struct))
	);
	if(d == NULL)
	    return(d);

	d->fs_type = fs_type;

	d->name = STRDUP(name);
	d->device_path = STRDUP(device_path);
	d->mount_path = STRDUP(mount_path);

	d->is_mounted = FALSE;

	d->no_unmount = FALSE;
	d->read_only = FALSE;
	d->unlisted = FALSE;

	d->realized = FALSE;

	d->internal = FALSE;

	memset(
	    d->small_pixmap, 0x00,
	    EDV_DEVICE_TOTAL_ICON_STATES * sizeof(GdkPixmap *)
	);
	memset(
	    d->small_mask, 0x00,
	    EDV_DEVICE_TOTAL_ICON_STATES * sizeof(GdkBitmap *)
	);

	memset(
	    d->medium_pixmap, 0x00,
	    EDV_DEVICE_TOTAL_ICON_STATES * sizeof(GdkPixmap *)
	);
	memset(
	    d->medium_mask, 0x00,
	    EDV_DEVICE_TOTAL_ICON_STATES * sizeof(GdkBitmap *)
	);

	memset(
	    d->large_pixmap, 0x00,
	    EDV_DEVICE_TOTAL_ICON_STATES * sizeof(GdkPixmap *)
	);
	memset(
	    d->large_mask, 0x00,
	    EDV_DEVICE_TOTAL_ICON_STATES * sizeof(GdkBitmap *)
	);

	d->command_mount = NULL;
	d->command_unmount = NULL;
	d->command_eject = NULL;

	d->command_check = NULL;
	d->command_tools = NULL;
	d->command_format = NULL;

	d->blocks_total = 0l;
	d->blocks_available = 0l;
	d->blocks_free = 0l;

	d->last_mount_time = 0l;
	d->last_check_time = 0l;

	return(d);
}

/*
 *	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 deallocate the icons.
 */
static void EDVDeviceLoadIconsNexus(
	GdkPixmap **pixmap, GdkBitmap **mask,
	guint8 ***data, const gchar **file,
	gint req_width, gint req_height
)
{
	gint i;

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

	/* Load the icon if the XPM data is given */
	if(data != NULL)
	{
	    GdkBitmap *m;
	    GdkPixmap *p;

	    /* Load icons */
	    for(i = 0; i < EDV_DEVICE_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 the icon if the XPM file is given */
	else if(file != NULL)
	{
	    GdkBitmap *m;
	    GdkPixmap *p;

	    /* Load icons */
	    for(i = 0; i < EDV_DEVICE_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 Device's small icons with the specified XPM data.
 *
 *	Passing data as NULL will delete the existing icons.
 */
void EDVDeviceLoadSmallIconsData(
	edv_device_struct *d, guint8 ***data
)
{
	if(d == NULL)
	    return;

	EDVDeviceLoadIconsNexus(
	    d->small_pixmap, d->small_mask,
	    data, NULL,
	    20, 20
	);
}

/*
 *	Loads the Device's medium icons with the specified XPM data.
 *
 *	Passing data as NULL will delete the existing icons.
 */
void EDVDeviceLoadMediumIconsData(
	edv_device_struct *d, guint8 ***data
)
{
	if(d == NULL)
	    return;

	EDVDeviceLoadIconsNexus(
	    d->medium_pixmap, d->medium_mask,
	    data, NULL,
	    32, 32
	);
}

/*
 *	Loads the Device's large icons with the specified XPM data.
 *
 *      Passing data as NULL will delete the existing icons.
 */
void EDVDeviceLoadLargeIconsData(
	edv_device_struct *d, guint8 ***data
)
{
	if(d == NULL)
	    return;

	EDVDeviceLoadIconsNexus(
	    d->large_pixmap, d->large_mask,
	    data, NULL,
	    48, 48
	);
}


/*
 *	Realizes the Device.
 *
 *	All icons specified by the Device will be loaded.
 *
 *	If the Device is already realized and force_rerealize is FALSE
 *	then nothing will be done, otherwise the Device will be
 *	realized.
 */
void EDVDeviceRealize(
	edv_device_struct *d, gboolean force_rerealize
)
{
	const gchar **path;

	if(d == NULL)
	    return;

	/* Not forcing realize? */
	if(!force_rerealize)
	{
	    /* Device already realized? */
	    if(d->realized)
		return;
	}

	/* Begin realizing this device */

	/* Load icons from the icon files */

	/* Get path array for small icon file */
	path = (const gchar **)d->small_icon_file;
	if(path != NULL)
	    EDVDeviceLoadIconsNexus(
		d->small_pixmap, d->small_mask,
		NULL, path,
		20, 20
	    );

	/* Get path array for medium icon file */
	path = (const gchar **)d->medium_icon_file;
	if(path != NULL)
	    EDVDeviceLoadIconsNexus(
		d->medium_pixmap, d->medium_mask,
		NULL, path,
		32, 32
	    );

	/* Get path for large icon file */
	path = (const gchar **)d->large_icon_file;
	if(path != NULL)
	    EDVDeviceLoadIconsNexus(
		d->large_pixmap, d->large_mask,
		NULL, path,
		48, 48
	    );

	/* Mark the Device as realized */
	d->realized = TRUE;
}

/*
 *	Deletes the Device.
 */
void EDVDeviceDelete(edv_device_struct *d)
{
	gint i;

	if(d == NULL)
	    return;

	/* Call the icon loaders with NULL as the argument to delete
	 * the icons
	 */
	EDVDeviceLoadSmallIconsData(d, NULL);
	EDVDeviceLoadMediumIconsData(d, NULL);
	EDVDeviceLoadLargeIconsData(d, NULL);

	for(i = 0; i < EDV_DEVICE_TOTAL_ICON_STATES; i++)
	{
	    g_free(d->small_icon_file[i]);
	    g_free(d->medium_icon_file[i]);
	    g_free(d->large_icon_file[i]);
	}

	g_free(d->command_mount);
	g_free(d->command_unmount);
	g_free(d->command_eject);

	g_free(d->command_check);
	g_free(d->command_tools);
	g_free(d->command_format);

	g_free(d->name);
	g_free(d->device_path);
	g_free(d->mount_path);

	g_free(d);
}
