#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

#include <glib.h>

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

#include "edvtypes.h"
#include "edvobj.h"
#include "edvutils.h"


edv_object_type EDVObjectGetTypeFromStatMode(mode_t m);
mode_t EDVObjectGetTypeFromEDVType(edv_object_type type);

edv_permission_flags EDVObjectGetPermissionsFromStatMode(
	mode_t m
);
mode_t EDVObjectGetPermissionsFromEDVPermissions(
	edv_permission_flags permissions
);

edv_object_struct *EDVObjectNew(void);
edv_object_struct *EDVObjectCopy(const edv_object_struct *obj);
void EDVObjectSetPath(
	edv_object_struct *obj, const gchar *path
);
void EDVObjectSetPath2(
	edv_object_struct *obj,
	const gchar *parent, const gchar *name
);
void EDVObjectSetStat(
	edv_object_struct *obj, const struct stat *lstat_buf
);
void EDVObjectValidateLink(edv_object_struct *obj);
void EDVObjectDelete(edv_object_struct *obj);


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


/*
 *	Returns the edv_object_type that matches the given stat()
 *	mode_t.
 */
edv_object_type EDVObjectGetTypeFromStatMode(mode_t m)
{
	if(S_ISLNK(m))
	    return(EDV_OBJECT_TYPE_LINK);
	else if(S_ISREG(m))
	    return(EDV_OBJECT_TYPE_FILE);
	else if(S_ISDIR(m))
	    return(EDV_OBJECT_TYPE_DIRECTORY);
	else if(S_ISCHR(m))
	    return(EDV_OBJECT_TYPE_DEVICE_CHARACTER);
	else if(S_ISBLK(m))
	    return(EDV_OBJECT_TYPE_DEVICE_BLOCK);
	else if(S_ISFIFO(m))
	    return(EDV_OBJECT_TYPE_FIFO);
	else if(S_ISSOCK(m))
	    return(EDV_OBJECT_TYPE_SOCKET);
	else
	    return(EDV_OBJECT_TYPE_FILE);
}

/*
 *	Returns the stat() mode_t that matches the given
 *	edv_object_type.
 */
mode_t EDVObjectGetTypeFromEDVType(edv_object_type type)
{
	mode_t m = 0;

	switch(type)
	{
	  case EDV_OBJECT_TYPE_UNKNOWN:
	    break;
	  case EDV_OBJECT_TYPE_FILE:
	    m |= S_IFREG;
	    break;
	  case EDV_OBJECT_TYPE_DIRECTORY:
	    m |= S_IFDIR;
	    break;
	  case EDV_OBJECT_TYPE_LINK:
	    m |= S_IFLNK;
	    break;
	  case EDV_OBJECT_TYPE_FIFO:
	    m |= S_IFIFO;
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_BLOCK:
	    m |= S_IFBLK;
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
	    m |= S_IFCHR;
	    break;
	  case EDV_OBJECT_TYPE_SOCKET:
	    m |= S_IFSOCK;
	    break;
	}

	return(m);
}

/*
 *	Returns the edv_permission_flags based on the given stat()
 *	mode_t.
 */
edv_permission_flags EDVObjectGetPermissionsFromStatMode(mode_t m)
{
	edv_permission_flags p = 0x00000000;

	if(m & S_IXUSR)
	    p |= EDV_PERMISSION_UEXECUTE;
	if(m & S_IRUSR)
	    p |= EDV_PERMISSION_UREAD;
	if(m & S_IWUSR)
	    p |= EDV_PERMISSION_UWRITE;

	if(m & S_IXGRP)
	    p |= EDV_PERMISSION_GEXECUTE;
	if(m & S_IRGRP)
	    p |= EDV_PERMISSION_GREAD;
	if(m & S_IWGRP)
	    p |= EDV_PERMISSION_GWRITE;

	if(m & S_IXOTH)
	    p |= EDV_PERMISSION_AEXECUTE;
	if(m & S_IROTH)
	    p |= EDV_PERMISSION_AREAD;
	if(m & S_IWOTH)
	    p |= EDV_PERMISSION_AWRITE;

	if(m & S_ISUID)
	    p |= EDV_PERMISSION_SETUID;
	if(m & S_ISGID)
	    p |= EDV_PERMISSION_SETGID;
	if(m & S_ISVTX)
	    p |= EDV_PERMISSION_STICKY;

	return(p);
}

/*
 *	Returns the stat() mode_t permissions based on the given
 *	edv_permission_flags.
 */
mode_t EDVObjectGetPermissionsFromEDVPermissions(
	edv_permission_flags permissions
)
{
	mode_t m = 0;

	if(permissions & EDV_PERMISSION_UEXECUTE)
	    m |= S_IXUSR;
	if(permissions & EDV_PERMISSION_UREAD)
	    m |= S_IRUSR;
	if(permissions & EDV_PERMISSION_UWRITE)
	    m |= S_IWUSR;

	if(permissions & EDV_PERMISSION_GEXECUTE)
	    m |= S_IXGRP;
	if(permissions & EDV_PERMISSION_GREAD)
	    m |= S_IRGRP;
	if(permissions & EDV_PERMISSION_GWRITE)
	    m |= S_IWGRP;

	if(permissions & EDV_PERMISSION_AEXECUTE)
	    m |= S_IXOTH;
	if(permissions & EDV_PERMISSION_AREAD)
	    m |= S_IROTH;
	if(permissions & EDV_PERMISSION_AWRITE)
	    m |= S_IWOTH;

	if(permissions & EDV_PERMISSION_SETUID)
	    m |= S_ISUID;
	if(permissions & EDV_PERMISSION_SETGID)
	    m |= S_ISGID;
	if(permissions & EDV_PERMISSION_STICKY)
	    m |= S_ISVTX;

	return(m);
}


/*
 *	Allocates a new edv_object_struct.
 */
edv_object_struct *EDVObjectNew(void)
{
	edv_object_struct *obj = EDV_OBJECT(
	    g_malloc0(sizeof(edv_object_struct))
	);
	obj->type = EDV_OBJECT_TYPE_FILE;
	obj->name = NULL;
	obj->full_path = NULL;
	obj->linked_to = NULL;
	obj->link_valid = FALSE;
	obj->permissions = 0x00000000;
	obj->access_time = 0l;
	obj->modify_time = 0l;
	obj->change_time = 0l;
	obj->owner_id = -1;
	obj->group_id = -1;
	obj->hard_links = 0;
	obj->size = 0l;
	obj->device = 0l;
	obj->inode = 0l;
	obj->device_type = 0;
	obj->block_size = 0l;
	obj->blocks = 0l;

	return(obj);
}


/*
 *	Creates a copy of the given disk object structure.
 */
edv_object_struct *EDVObjectCopy(const edv_object_struct *obj)
{
	const edv_object_struct *src = obj;
	edv_object_struct *tar;

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

	/* Create target Object and copy values to it */
	tar = EDVObjectNew();

	tar->type = src->type;

	tar->name = STRDUP(src->name);
	tar->full_path = STRDUP(src->full_path);

	tar->linked_to = STRDUP(src->linked_to);
	tar->link_valid = src->link_valid;

	tar->permissions = src->permissions;

	tar->access_time = src->access_time;
	tar->modify_time = src->modify_time;
	tar->change_time = src->change_time;

	tar->owner_id = src->owner_id;
	tar->group_id = src->group_id;

	tar->hard_links = src->hard_links;

	tar->size = src->size;

	tar->device = src->device;
	tar->inode = src->inode;

	tar->device_type = src->device_type;

	tar->block_size = src->block_size;
	tar->blocks = src->blocks;

	return(tar);
}


/*
 *	Sets the Object's name and full path from the specified path.
 *
 *	The specified path must be an absolute path.
 */
void EDVObjectSetPath(
	edv_object_struct *obj, const gchar *path
)
{
	if(obj == NULL)
	    return;

	/* Reset names first */
	g_free(obj->name);
	obj->name = NULL;
	g_free(obj->full_path);
	obj->full_path = NULL;

	/* Enough information to set path? */
	if(!STRISEMPTY(path))
	{
	    const gchar *s;

	    /* Set full path, assume given path is an absolute path */
	    obj->full_path = STRDUP(path);

	    /* Remove tailing deliminators if any */
	    StripPath(obj->full_path);

	    /* Find child and set name */
	    s = EDVGetPathName(obj->full_path);
	    if(!STRISEMPTY(s))
		obj->name = STRDUP(s);
	    else
		obj->name = STRDUP(obj->full_path);
	}
}

/*
 *	Sets the Object's name and full path from the specified name
 *	and full path.
 */
void EDVObjectSetPath2(
	edv_object_struct *obj,
	const gchar *parent, const gchar *name
)
{
	if(obj == NULL)
	    return;

	/* Reset names first */
	g_free(obj->name);
	obj->name = NULL;
	g_free(obj->full_path);
	obj->full_path = NULL;

	/* Enough info given to set full path? */
	if(!STRISEMPTY(parent) && !STRISEMPTY(name))
	    obj->full_path = STRDUP(PrefixPaths(parent, name));

	/* Enough info given to set name? */
	if(!STRISEMPTY(name))
	    obj->name = STRDUP(name);
}

/*
 *	Sets the Object's stats from the stats specified in lstat_buf.
 *
 *	The Object's path should be first with a call to
 *	EDVObjectSetPath*() before calling this function.
 */
void EDVObjectSetStat(
	edv_object_struct *obj, const struct stat *lstat_buf
)
{
	mode_t m;

	if((obj == NULL) || (lstat_buf == NULL))
	    return;

	m = lstat_buf->st_mode;

	/* Begin setting type and permissions */
	obj->type = EDVObjectGetTypeFromStatMode(m);
	obj->permissions = EDVObjectGetPermissionsFromStatMode(m);

	/* Begin setting time stamps */
	obj->access_time = (gulong)lstat_buf->st_atime;
	obj->modify_time = (gulong)lstat_buf->st_mtime;
	obj->change_time = (gulong)lstat_buf->st_ctime;

	/* Begin setting ownership id */
	obj->owner_id = lstat_buf->st_uid;
	obj->group_id = lstat_buf->st_gid;

	/* Set number of hard links */
	obj->hard_links = lstat_buf->st_nlink;

	/* Assume link is valid for now */
	obj->link_valid = TRUE;

	/* Set size in bytes */
	obj->size = (gulong)lstat_buf->st_size;


	/* Set device and inode */
	obj->device = lstat_buf->st_dev;
	obj->inode = lstat_buf->st_ino;

	obj->device_type = lstat_buf->st_rdev;

	/* Set block transfer size and blocks allocated */
	obj->block_size = lstat_buf->st_blksize;
	obj->blocks = lstat_buf->st_blocks;


	/* If this is a link then update the linked_to destination */
	if(S_ISLNK(m))
	{
	    gchar buf[PATH_MAX + NAME_MAX + 1];
	    gint bytes_read = readlink(
		obj->full_path, buf, sizeof(buf)
	    );
	    if(bytes_read > -1)
	    {
		if(bytes_read < sizeof(buf))
		    buf[bytes_read] = '\0';
		else
		    buf[sizeof(buf) - 1] = '\0';

		g_free(obj->linked_to);
		obj->linked_to = STRDUP(buf);
	    }
	    else
	    {
		g_free(obj->linked_to);
		obj->linked_to = NULL;
	    }
	}
}

/*
 *	Checks if the Object of type EDV_OBJECT_TYPE_LINK is valid and
 *	updates the Object's link_valid value.
 */
void EDVObjectValidateLink(edv_object_struct *obj)
{
	struct stat stat_buf;
	const gchar *full_path = (obj != NULL) ?
	    obj->full_path : NULL;
	if(STRISEMPTY(full_path))
	    return;

	/* Check if destination can be stat()'ed */
	if(stat(full_path, &stat_buf))
	    obj->link_valid = FALSE;
	else
	    obj->link_valid = TRUE;
}

/*
 *	Deletes the Object.
 */
void EDVObjectDelete(edv_object_struct *obj)
{
	if(obj == NULL)
	    return;

	g_free(obj->name);
	g_free(obj->full_path);

	g_free(obj->linked_to);

	g_free(obj);
}
