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

#include "../../include/fio.h"
#include "../../include/disk.h"

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

#include "config.h"


/* Update Mount State From System */
void EDVDevicesListUpdateMountStatesFromSystem(
	edv_device_struct **list, const gint total
);

/* Open Devices List */
edv_device_struct **EDVDevicesListFileOpen(
	const gchar *filename, gint *total
);
/* Save Devices List */
void EDVDeviceListFileSave(
	const gchar *filename,
	edv_device_struct **list, const gint total
);


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


/*
 *	Updates the mount states of all Devices in the list to
 *	reflect current mount states of the devices from the system.
 */
void EDVDevicesListUpdateMountStatesFromSystem(
	edv_device_struct **list, const gint total
)
{
	FILE *fp;
	gint dev_num;
	edv_device_struct *dev;
#if defined(__FreeBSD__)
        gint i, mntsize;  
        struct statfs *mt_buf;
#elif defined(__SOLARIS__)
	struct mnttab *mt_ptr;
	int mtback;
#else
	struct mntent *mt_ptr;
#endif

	if((list == NULL) || (total <= 0))
	    return;

	/* Reset mount states on all Devices */
	for(dev_num = 0; dev_num < total; dev_num++)
	{
	    dev = list[dev_num];
	    if(dev == NULL)
		continue;

	    dev->flags &= ~EDV_DEVICE_MOUNTED;
	}

	/* 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 the this mounted device's devie path matches
	     * a Device in the specified list
	     */
	    dev = EDVDeviceListMatchDevicePath(
		list, total,
		&dev_num,
#if defined(__FreeBSD__)
		mt_buf[i].f_mntfromname
#elif defined(__SOLARIS__)
		mt_ptr->mnt_special
#else
		mt_ptr->mnt_fsname
#endif
	    );
	    if(dev != NULL)
	    {
		/* Mark this device as mounted */
		dev->flags |= EDV_DEVICE_MOUNTED;
	    }

	    /* Also check if this mounted device's mount path matches
	     * a Device in the specified list
	     */
	    dev = EDVDeviceListMatchMountPath(
		list, total,
		&dev_num,
#if defined(__FreeBSD__)
                mt_buf[i].f_mntonname
#elif defined(__SOLARIS__)
		mt_ptr->mnt_mountp
#else
		mt_ptr->mnt_dir
#endif
	    );
	    if(dev != NULL)
	    {
		/* Mark this device as mounted */
		dev->flags |= EDV_DEVICE_MOUNTED;
	    }

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


/*
 *	Loads all Devices from the specified file.
 */
edv_device_struct **EDVDevicesListFileOpen(
	const gchar *filename, gint *total
)
{
	FILE *fp;
	gchar *parm;
	gint dev_num = -1;
	edv_device_struct **list = NULL, *dev = NULL;

	if(total != NULL)
	    *total = 0;

	if((total == NULL) || STRISEMPTY(filename))
	    return(NULL);

	/* Open devices file */
	fp = FOpen(filename, "rb");
	if(fp == NULL)
	    return(NULL);

	/* Begin file */
	parm = NULL;
	while(TRUE)
	{
	    /* Read next parameter */
	    parm = FSeekNextParm(
		fp, parm,
		EDV_CFG_COMMENT_CHAR,
		EDV_CFG_DELIMINATOR_CHAR
	    );
	    if(parm == NULL)
		break;

	    /* Begin handling by parameter */

	    /* BeginDevice */
	    if(!g_strcasecmp(parm, "BeginDevice"))
	    {
		gchar *device_path = FGetString(fp);

		/* Check if an existing Device already exists in
		 * the list that matches the device_path
		 */
		dev = EDVDeviceListMatchDevicePath(
		    list, *total,
		    &dev_num, device_path
		);
		if(dev == NULL)
		{
		    /* No existing Device found that matches the
		     * device_path so create a new Device
		     */
		    dev_num = *total;
		    *total = dev_num + 1;
		    list = (edv_device_struct **)g_realloc(
			list, (*total) * sizeof(edv_device_struct *)
		    );
		    if(list == NULL)
		    {
			*total = 0;
			dev_num = -1;
			dev = NULL;
		    }
		    else
		    {
			list[dev_num] = dev = EDVDeviceNew(
			    EDV_FS_TYPE_EMPTY, NULL, device_path, NULL
			);
			if(dev == NULL)
			{
			    dev_num = -1;
			}
		    }
		}
	    }

	    /* FSType */
	    else if(!g_strcasecmp(parm, "FSType") ||
		    !g_strcasecmp(parm, "FileSystemType")
	    )
	    {
		gint v[1];
		FGetValuesI(fp, v, 1);
		if(dev != NULL)
		    dev->fs_type = v[0];
	    }
	    /* NoUnmount */
	    else if(!g_strcasecmp(parm, "NoUnmount"))
	    {
		gint v[1];
		FGetValuesI(fp, v, 1);
		if(dev != NULL)
		{
		    if(v[0])
			dev->flags |= EDV_DEVICE_NO_UNMOUNT;
		    else
			dev->flags &= ~EDV_DEVICE_NO_UNMOUNT;
		}
	    }
	    /* ReadOnly */
	    else if(!g_strcasecmp(parm, "ReadOnly"))
	    {
		gint v[1];
		FGetValuesI(fp, v, 1);
		if(dev != NULL)
		{
		    if(v[0])
			dev->flags |= EDV_DEVICE_READ_ONLY;
		    else
			dev->flags &= ~EDV_DEVICE_READ_ONLY;
		}
	    }
	    /* Unlisted */
	    else if(!g_strcasecmp(parm, "Unlisted"))
	    {
		gint v[1];
		FGetValuesI(fp, v, 1);
		if(dev != NULL)
		{
		    if(v[0])
			dev->flags |= EDV_DEVICE_UNLISTED;
		    else
			dev->flags &= ~EDV_DEVICE_UNLISTED;
		}
	    }
	    /* Name */
	    else if(!g_strcasecmp(parm, "Name"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    g_free(dev->name);
		    dev->name = STRDUP(v);
		}
		g_free(v);
	    }
	    /* MountPath */
	    else if(!g_strcasecmp(parm, "MountPath"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    g_free(dev->mount_path);
		    dev->mount_path = STRDUP(v);
		}
		g_free(v);
	    }
	    /* CommandMount */
	    else if(!g_strcasecmp(parm, "CommandMount"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    g_free(dev->command_mount);
		    dev->command_mount = STRDUP(v);
		}
		g_free(v);
	    }
	    /* CommandUnmount */
	    else if(!g_strcasecmp(parm, "CommandUnmount"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    g_free(dev->command_unmount);
		    dev->command_unmount = STRDUP(v);
		}
		g_free(v);
	    }
	    /* CommandEject */
	    else if(!g_strcasecmp(parm, "CommandEject"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    g_free(dev->command_eject);
		    dev->command_eject = STRDUP(v);
		}
		g_free(v);
	    }
	    /* CommandCheck */
	    else if(!g_strcasecmp(parm, "CommandCheck"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    g_free(dev->command_check);
		    dev->command_check = STRDUP(v);
		}
		g_free(v);
	    }
	    /* CommandTools */
	    else if(!g_strcasecmp(parm, "CommandTools"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    g_free(dev->command_tools);
		    dev->command_tools = STRDUP(v);
		}
		g_free(v);
	    }
	    /* CommandFormat */
	    else if(!g_strcasecmp(parm, "CommandFormat"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    g_free(dev->command_format);
		    dev->command_format = STRDUP(v);
		}
		g_free(v);
	    }

	    /* IconSmallStandard */
	    else if(!g_strcasecmp(parm, "IconSmallStandard"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    gchar **t = &dev->small_icon_file[
			EDV_DEVICE_ICON_STATE_STANDARD
		    ];
		    g_free(*t);
		    *t = STRDUP(v);
		}
		g_free(v);
	    }
	    /* IconSmallSelected */
	    else if(!g_strcasecmp(parm, "IconSmallSelected"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    gchar **t = &dev->small_icon_file[
			EDV_DEVICE_ICON_STATE_SELECTED
		    ];
		    g_free(*t);
		    *t = STRDUP(v);
		}
		g_free(v);
	    }
	    /* IconSmallUnmounted */
	    else if(!g_strcasecmp(parm, "IconSmallUnmounted"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    gchar **t = &dev->small_icon_file[
			EDV_DEVICE_ICON_STATE_UNMOUNTED
		    ];
		    g_free(*t);
		    *t = STRDUP(v);
		}
		g_free(v);
	    }

	    /* IconMediumStandard */
	    else if(!g_strcasecmp(parm, "IconMediumStandard"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    gchar **t = &dev->medium_icon_file[
			EDV_DEVICE_ICON_STATE_STANDARD
		    ];
		    g_free(*t);
		    *t = STRDUP(v);
		}
		g_free(v);
	    }
	    /* IconMediumSelected */
	    else if(!g_strcasecmp(parm, "IconMediumSelected"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    gchar **t = &dev->medium_icon_file[
		        EDV_DEVICE_ICON_STATE_SELECTED
		    ];
		    g_free(*t);
		    *t = STRDUP(v);
		}
		g_free(v);
	    }
	    /* IconMediumUnmounted */
	    else if(!g_strcasecmp(parm, "IconMediumUnmounted"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    gchar **t = &dev->medium_icon_file[
			EDV_DEVICE_ICON_STATE_UNMOUNTED
		    ];
		    g_free(*t);
		    *t = STRDUP(v);
		}
		g_free(v);
	    }

	    /* IconLargeStandard */
	    else if(!g_strcasecmp(parm, "IconLargeStandard"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    gchar **t = &dev->large_icon_file[
		        EDV_DEVICE_ICON_STATE_STANDARD
		    ];
		    g_free(*t);
		    *t = STRDUP(v);
		}
		g_free(v);
	    }
	    /* IconLargeSelected */
	    else if(!g_strcasecmp(parm, "IconLargeSelected"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    gchar **t = &dev->large_icon_file[
			EDV_DEVICE_ICON_STATE_SELECTED
		    ];
		    g_free(*t);
		    *t = STRDUP(v);
		}
		g_free(v);
	    }
	    /* IconLargeUnmounted */
	    else if(!g_strcasecmp(parm, "IconLargeUnmounted"))
	    {
		gchar *v = FGetString(fp);
		if(dev != NULL)
		{
		    gchar **t = &dev->large_icon_file[
			EDV_DEVICE_ICON_STATE_UNMOUNTED
		    ];
		    g_free(*t);
		    *t = STRDUP(v);
		}
		g_free(v);
	    }

	    /* LastMountTime */
	    else if(!g_strcasecmp(parm, "LastMountTime"))
	    {
		glong v[1];
		FGetValuesL(fp, v, 1);
		if(dev != NULL)
		    dev->last_mount_time = (gulong)v[0];
	    }
	    /* LastCheckTime */
	    else if(!g_strcasecmp(parm, "LastCheckTime"))
	    {
		glong v[1];
		FGetValuesL(fp, v, 1);
		if(dev != NULL)
		    dev->last_check_time = (gulong)v[0];
	    }

	    /* End of a device block? */
	    else if(!g_strcasecmp(parm, "EndDevice"))
	    {
		FSeekNextLine(fp);

		/* Reset contexts */
		dev_num = -1;
		dev = NULL;
	    }
	    /* All else unsupported parameter or wrong context */
	    else
	    {
		FSeekNextLine(fp);
	    }
	}

	/* Delete parameter */
	g_free(parm);

	/* Close devices file */
	FClose(fp);

	return(list);
}

/*
 *	Saves the list of devices to the specified file.
 */
void EDVDeviceListFileSave(
	const gchar *filename,
	edv_device_struct **list, const gint total
)
{
	gint dev_num;
	const edv_device_struct *dev;
	gchar *parent_path;
	FILE *fp;
	const gchar *path;

	if((list == NULL) || STRISEMPTY(filename))
	    return;

	/* Get parent directory and create it as needed */
	parent_path = g_dirname(filename);
	if(parent_path != NULL)
	{
#if defined(S_IRUSR) && defined(S_IWUSR) && defined(S_IXUSR)
	    rmkdir(parent_path, S_IRUSR | S_IWUSR | S_IXUSR);
#endif
	    g_free(parent_path);
	}

	/* Open devices file for writing */
	fp = FOpen(filename, "wb");
	if(fp == NULL)
	    return;

	/* Iterate through all device structures */
	for(dev_num = 0; dev_num < total; dev_num++)
	{
	    dev = list[dev_num];
	    if(dev == NULL)
		continue;

	    /* BeginDevice */
	    fprintf(
		fp,
		"BeginDevice = %s\n",
		dev->device_path
	    );

	    /* FSType */
	    fprintf(
		fp,
		"\tFSType = %i\n",
		dev->fs_type
	    );
	    /* NoUnmount */
	    fprintf(
		fp,
		"\tNoUnmount = %i\n",
		EDV_DEVICE_IS_NO_UNMOUNT(dev)
	    );
	    /* ReadOnly */
	    fprintf(
		fp,
		"\tReadOnly = %i\n",
		EDV_DEVICE_IS_READ_ONLY(dev)
	    );
	    /* Unlisted */
	    fprintf(
		fp,
		"\tUnlisted = %i\n",
		EDV_DEVICE_IS_UNLISTED(dev)
	    );
	    /* Name */
	    if(!STRISEMPTY(dev->name))
		fprintf(
		    fp,
		    "\tName = %s\n",
		    dev->name
		);
	    /* MountPath */
	    if(!STRISEMPTY(dev->mount_path))
		fprintf(
		    fp,
		    "\tMountPath = %s\n",
		    dev->mount_path
		);

	    /* CommandMount */
	    if(!STRISEMPTY(dev->command_mount))
		fprintf(
		    fp,
		    "\tCommandMount = %s\n",
		    dev->command_mount
		);
	    /* CommandUnmount */
	    if(!STRISEMPTY(dev->command_unmount))
		fprintf(
		    fp,
		    "\tCommandUnmount = %s\n",
		    dev->command_unmount
		);
	    /* CommandEject */
	    if(!STRISEMPTY(dev->command_eject))
		fprintf(
		    fp,
		    "\tCommandEject = %s\n",
		    dev->command_eject
		);
	    /* CommandCheck */
	    if(!STRISEMPTY(dev->command_check))
		fprintf(
		    fp,
		    "\tCommandCheck = %s\n",
		    dev->command_check
		);
	    /* CommandTools */
	    if(!STRISEMPTY(dev->command_tools))
		fprintf(
		    fp,
		    "\tCommandTools = %s\n",
		    dev->command_tools
		);
	    /* CommandFormat */
	    if(!STRISEMPTY(dev->command_format))
		fprintf(
		    fp,
		    "\tCommandFormat = %s\n",
		    dev->command_format
		);

	    /* IconSmallStandard */
	    path = dev->small_icon_file[EDV_DEVICE_ICON_STATE_STANDARD];
	    if(!STRISEMPTY(path))
		fprintf(
		    fp,
		    "\tIconSmallStandard = %s\n",
		    path
		);
	    /* IconSmallSelected */
	    path = dev->small_icon_file[EDV_DEVICE_ICON_STATE_SELECTED];
	    if(!STRISEMPTY(path))
		fprintf(
		    fp,
		    "\tIconSmallSelected = %s\n",
		    path
		);
	    /* IconSmallUnmounted */
	    path = dev->small_icon_file[EDV_DEVICE_ICON_STATE_UNMOUNTED];
	    if(!STRISEMPTY(path))
		fprintf(
		    fp,
		    "\tIconSmallUnmounted = %s\n",
		    path
		);

	    /* IconMediumStandard */
	    path = dev->medium_icon_file[EDV_DEVICE_ICON_STATE_STANDARD];
	    if(!STRISEMPTY(path))
		fprintf(
		    fp,
		    "\tIconMediumStandard = %s\n",
		    path
		);
	    /* IconMediumSelected */
	    path = dev->medium_icon_file[EDV_DEVICE_ICON_STATE_SELECTED];
	    if(!STRISEMPTY(path))
		fprintf(
		    fp,
		    "\tIconMediumSelected = %s\n",
		    path
		);
	    /* IconMediumUnmounted */
	    path = dev->medium_icon_file[EDV_DEVICE_ICON_STATE_UNMOUNTED];
	    if(!STRISEMPTY(path))
		fprintf(
		    fp,
		    "\tIconMediumUnmounted = %s\n",
		    path
		);

	    /* IconLargeStandard */
	    path = dev->large_icon_file[EDV_DEVICE_ICON_STATE_STANDARD];
	    if(!STRISEMPTY(path))
		fprintf(
		    fp,
		    "\tIconLargeStandard = %s\n",
		    path
		);
	    /* IconLargeSelected */
	    path = dev->large_icon_file[EDV_DEVICE_ICON_STATE_SELECTED];
	    if(!STRISEMPTY(path))
		fprintf(
		    fp,
		    "\tIconLargeSelected = %s\n",
		    path
		);
	    /* IconLargeUnmounted */
	    path = dev->large_icon_file[EDV_DEVICE_ICON_STATE_UNMOUNTED];
	    if(!STRISEMPTY(path))
		fprintf(
		    fp,
		    "\tIconLargeUnmounted = %s\n",
		    path
		);

	    /* LastMountTime */
	    if(dev->last_mount_time > 0l)
		fprintf(
		    fp,
		    "\tLastMountTime = %ld\n",
		    dev->last_mount_time
		);
	    /* LastCheckTime */
	    if(dev->last_check_time > 0l)
		fprintf(
		    fp,
		    "\tLastCheckTime = %ld\n",
		    dev->last_check_time
		);

	    /* EndDevice */
	    fprintf(
		fp,
		"EndDevice\n"
	    );
	}

	/* Close devices file */
	FClose(fp);
}
