#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(__FreeBSD__)
# include <fstab.h>
# include <sys/param.h>
# include <sys/ucred.h>
# include <sys/mount.h>
#elif defined(__SOLARIS__)
# include <sys/vfs.h>
# include <sys/mnttab.h>
# include <sys/statvfs.h>
#else
# include <mntent.h>
#endif
#include <gtk/gtk.h>

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

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

#include "edv_types.h"
#include "edv_device.h"
#include "edv_devices_list.h"


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

/* Mount State */
void EDVDeviceUpdateMountStateFromSystem(edv_device_struct *d);
void EDVDeviceUpdateMountState(edv_device_struct *d);

/* Filesystem */
guint32 EDVDeviceGetFSTypeFromName(const gchar *name);
const gchar *EDVDeviceGetFSNameFromType(guint32 type);

/* Devices */
edv_device_struct *EDVDeviceNew(
	const 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, const 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 * sizeof(guint8);
	    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);
	}
}


/*
 *	Updates the mount state of the Device by checking with the
 *	system device's mount state.
 *
 *	Reminder: There is a version of this function in
 *	lib/edv_devices_list.c that needs to perform equvilently to
 *	this function.
 */
void EDVDeviceUpdateMountStateFromSystem(edv_device_struct *d)
{
	const gchar *device_path, *mount_path;
	FILE *fp;
#if defined(__FreeBSD__)
	gint i, mntsize;
	struct statfs *mt_buf;
#elif defined(__SOLARIS__)
	gint mtback;
	struct mnttab *mt_ptr;
#else
	struct mntent *mt_ptr;
#endif

	if(d == NULL)
	    return;

	/* Reset the device's mount state */
	d->flags &= ~EDV_DEVICE_MOUNTED;

	if(STRISEMPTY(d->device_path))
	    return;

	/* Open the system's mounted devices list file */
#if defined(__FreeBSD__)
	fp = (FILE *)1;		/* Mark the mount info file as opened */
#elif defined(__SOLARIS__)
	fp = FOpen("/etc/mnttab", "rb");
#else
	fp = setmntent("/proc/mounts", "rb");
#endif
	if(fp == NULL)
	    return;

	/* Begin reading the system's mounted devices list file */
#if defined(__FreeBSD__)
	if((mntsize = getmntinfo(&mt_buf, MNT_NOWAIT)) == 0)
	    return;
	i = mntsize - 1;
	while(i >= 0)
#elif defined(__SOLARIS__)
	mt_ptr = (struct mnttab *)g_malloc(sizeof(struct mnttab));
	mtback = getmntent(fp, mt_ptr);
	while(mtback != 0)
#else
	mt_ptr = getmntent(fp);
	while(mt_ptr != NULL)
#endif
	{
	    /* Check if this mount path's device path matches this
	     * Device's device path
	     */
	    device_path = (const gchar *)(
#if defined(__FreeBSD__)
		mt_buf[i].f_mntfromname
#elif defined(__SOLARIS__)
		mt_ptr->mnt_special
#else
		mt_ptr->mnt_fsname
#endif
	    );
	    if(!STRISEMPTY(d->device_path) && !STRISEMPTY(device_path))
	    {
		if(!strcmp(d->device_path, device_path))
		{
		    /* Mark this device as mounted */
		    d->flags |= EDV_DEVICE_MOUNTED;
		    break;
		}
	    }
	     
	    /* Also check if this mount path's mounted path matches
	     * this Device's mounted path
	     */
	    mount_path = (const gchar *)(
#if defined(__FreeBSD__)
		mt_buf[i].f_mntonname
#elif defined(__SOLARIS__)
		mt_ptr->mnt_mountp
#else
		mt_ptr->mnt_dir
#endif
	    );
	    if(!STRISEMPTY(d->mount_path) && !STRISEMPTY(mount_path))
	    {
		if(!strcmp(d->mount_path, mount_path))
		{
		    /* Mark this device as mounted */
		    d->flags |= EDV_DEVICE_MOUNTED;
		    break;
		}
	    }

	    /* Get the next mount entry */
#if defined(__FreeBSD__)
	    i--;
#elif defined(__SOLARIS__)
	    mtback = getmntent(fp, mt_ptr);
#else
	    mt_ptr = getmntent(fp);
#endif
	}

	/* Close the system's mounted devices list file */
#if defined(__FreeBSD__)
	fp = NULL;	/* Mark the mount info file as closed */
#elif defined(__SOLARIS__)
	FClose(fp);
	g_free(mt_ptr);
#else
	endmntent(fp);
#endif
}

/*
 *	Updates the Device's mount state.
 */
void EDVDeviceUpdateMountState(edv_device_struct *d)
{
	EDVDeviceUpdateMountStateFromSystem(d);
}


/*
 *	Gets the filesystem type code from the specified filesystem
 *	conical name.
 *
 *	Returns one of EDV_FS_TYPE_*.
 */
guint32 EDVDeviceGetFSTypeFromName(const gchar *name)
{
	gint i;
	const edv_fs_type_struct *fs_type;
	const edv_fs_type_struct list[] = EDV_FS_TYPE_LIST;

	if(STRISEMPTY(name))
	    return(EDV_FS_TYPE_EMPTY);

	for(i = 0; list[i].fs_type != EDV_FS_TYPE_EMPTY; i++)
	{
	    fs_type = &list[i];
	    if(fs_type->name == NULL)
		continue;

	    if(g_strcasecmp(fs_type->name, name))
		continue;

	    return(fs_type->fs_type);
	}

	return(EDV_FS_TYPE_EMPTY);
}

/*
 *	Gets the filesystem's conical name from the specified
 *	filesystem type code.
 *
 *	Returns a statically allocated string describing the
 *	filesystem's conical name.
 */
const gchar *EDVDeviceGetFSNameFromType(guint32 type)
{
	gint i;
	const edv_fs_type_struct *fs_type;
	const edv_fs_type_struct list[] = EDV_FS_TYPE_LIST;
	static gchar name[80];

	for(i = 0; list[i].fs_type != EDV_FS_TYPE_EMPTY; i++)
	{
	    fs_type = &list[i];
	    if(fs_type->name == NULL)
		continue;

	    if(fs_type->fs_type != type)
		continue;

	    strncpy(name, fs_type->name, sizeof(name));
	    name[sizeof(name) - 1] = '\0';
	    return(name);
	}

	return("");
}


/*
 *	Creates a new Device.
 */
edv_device_struct *EDVDeviceNew(
	const 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->flags = 0;

	d->fs_type = fs_type;

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

	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, const gboolean force_rerealize
)
{
	const gchar **path;

	if(d == NULL)
	    return;

	/* Not forcing realize? */
	if(!force_rerealize)
	{
	    /* Device already realized? */
	    if(EDV_DEVICE_IS_REALIZED(d))
		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->flags |= EDV_DEVICE_REALIZED;
}

/*
 *	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);
}
