#include <string.h>
#if defined(__FreeBSD__)
#include <sys/param.h>
#include <sys/mount.h>
#else
#include <sys/vfs.h>
#endif
#if defined(__SOLARIS__)
# include <sys/statvfs.h>
#endif
#include <glib.h>

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

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


static edv_fs_type_struct	fs_type_list[] = EDV_FS_TYPE_LIST;


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


/* Devices List Updating */
void EDVDevicesListUpdateMountStates(
	edv_device_struct **list, const gint total
);
void EDVDeviceUpdateMountState(edv_device_struct *d);

void EDVDevicesListUpdateStats(
	edv_device_struct **list, const gint total
);
void EDVDeviceUpdateStats(edv_device_struct *d);


/* Filesystem */
guint32 EDVDeviceGetFSTypeFromName(const gchar *name);
const gchar *EDVDeviceGetFSNameFromType(const 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 */
);
edv_device_struct *EDVDeviceCopy(const edv_device_struct *d);
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)


/*
 *	Matches the Device from the specified Devices List who's
 *	mount_path matches the specified mount_path.
 */
edv_device_struct *EDVDeviceListMatchMountPath(
	edv_device_struct **list, const 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 specified Devices List who's
 *	device_path matches the specified device_path.
 */
edv_device_struct *EDVDeviceListMatchDevicePath(
	edv_device_struct **list, const 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 specified Devices List that the
 *	object specified by path exists on.
 */
edv_device_struct *EDVDeviceListMatchObject(
	edv_device_struct **list, const 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 state for all the Devices in the specified
 *	Devices List.
 */
void EDVDevicesListUpdateMountStates(
	edv_device_struct **list, const gint total
)
{
	EDVDevicesListUpdateMountStatesFromSystem(
	    list, total
	);
}

/*
 *	Updates the mount state for the specified Device.
 */
void EDVDeviceUpdateMountState(edv_device_struct *d)
{
	edv_device_struct *list[1];
	list[0] = d;
	EDVDevicesListUpdateMountStates(list, 1);
}


/*
 *	Updates the blocks_total, blocks_available, and blocks_free
 *	statistics for all the Devices in the specified Devices List.
 *
 *	Statistics will not be obtained for Devices that are not
 *	mounted, you should call EDVDevicesListUpdateMountStates()
 *	prior to calling this function.
 */
void EDVDevicesListUpdateStats(
	edv_device_struct **list, const gint total
)
{
	gint i;
	const gchar *mount_path;
	edv_device_struct *dev;
#if defined(__SOLARIS__)
	struct statvfs buf;
#else
	struct statfs buf;
#endif

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

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

	    /* Get stats only if the Device is mounted */
	    if(EDV_DEVICE_IS_MOUNTED(dev))
	    {
		mount_path = dev->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->blocks_total = buf.f_blocks * block_div;
			dev->blocks_available = buf.f_bavail * block_div;
			dev->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->blocks_total = buf.f_blocks / block_div;
			dev->blocks_available = buf.f_bavail / block_div;
			dev->blocks_free = buf.f_bfree / block_div;
		    }
		}
	    }
	}
}

/*
 *	Updates the blocks_total, blocks_available, and blocks_free
 *	statistics of the specified Device by checking with the system.
 *
 *	Statistics will not be obtained if the Device is not marked
 *	as mounted so you should call EDVDeviceUpdateMountState()
 *      prior to calling this function.
 */
void EDVDeviceUpdateStats(edv_device_struct *d)
{
	edv_device_struct *list[1];
	list[0] = d;
	EDVDevicesListUpdateStats(list, 1);
}


/*
 *      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 = 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(const guint32 type) 
{
	gint i;
	const edv_fs_type_struct *fs_type;
	const edv_fs_type_struct *list = 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);

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

/*
 *	Coppies the Device.
 */
edv_device_struct *EDVDeviceCopy(const edv_device_struct *d)
{
	gint i, n;
	const edv_device_struct *src = d;
	edv_device_struct *tar;

	if(src == NULL)
	    return(NULL);

	tar = EDVDeviceNew(
	    src->fs_type,
	    src->name,
	    src->device_path,
	    src->mount_path
	);
	if(tar == NULL)
	    return(NULL);

	tar->flags = src->flags;

	n = EDV_DEVICE_TOTAL_ICON_STATES;
	for(i = 0; i < n; i++)
	{
	    tar->small_icon_file[i] = STRDUP(src->small_icon_file[i]);
	    tar->medium_icon_file[i] = STRDUP(src->medium_icon_file[i]);
	    tar->large_icon_file[i] = STRDUP(src->large_icon_file[i]);
	}

	tar->command_mount = STRDUP(src->command_mount);
	tar->command_unmount = STRDUP(src->command_unmount);
	tar->command_eject = STRDUP(src->command_eject);

	tar->command_check = STRDUP(src->command_check);
	tar->command_tools = STRDUP(src->command_tools);
	tar->command_format = STRDUP(src->command_format);

	tar->blocks_total = src->blocks_total;
	tar->blocks_available = src->blocks_available;
	tar->blocks_free = src->blocks_free;

	tar->last_mount_time = src->last_mount_time;
	tar->last_check_time = src->last_check_time;

	return(tar);
}

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

	if(d == NULL)
	    return;

	n = EDV_DEVICE_TOTAL_ICON_STATES;
	for(i = 0; i < n; 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);
}
