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

#include <glib.h>

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

#include "edv_types.h"
#include "edv_obj.h"
#include "edv_utils.h"


edv_object_type EDVObjectGetTypeFromStatMode(const mode_t m);
mode_t EDVObjectGetTypeFromEDVType(const edv_object_type type);
const gchar *EDVObjectGetTypeName(const edv_object_type type);
const gchar *EDVObjectGetTypeNameLower(const edv_object_type type);

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

gboolean EDVObjectCompareIndex(
	edv_object_struct *obj1, edv_object_struct *obj2
);
gboolean EDVObjectCompareIndexWithStat(
	edv_object_struct *obj, const struct stat *stat_buf
);

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 EDVObjectUpdateLinkFlags(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(const mode_t m)
{
#ifdef S_ISREG
	if(S_ISREG(m))
#else
	if(TRUE)
#endif
	    return(EDV_OBJECT_TYPE_FILE);
#ifdef S_ISDIR
	else if(S_ISDIR(m))
	    return(EDV_OBJECT_TYPE_DIRECTORY);
#endif
#ifdef S_ISLNK
	else if(S_ISLNK(m))
	    return(EDV_OBJECT_TYPE_LINK);
#endif
#ifdef S_ISCHR
	else if(S_ISCHR(m))
	    return(EDV_OBJECT_TYPE_DEVICE_CHARACTER);
#endif
#ifdef S_ISBLK
	else if(S_ISBLK(m))
	    return(EDV_OBJECT_TYPE_DEVICE_BLOCK);
#endif
#ifdef S_ISFIFO
	else if(S_ISFIFO(m))
	    return(EDV_OBJECT_TYPE_FIFO);
#endif
#ifdef S_ISSOCK
	else if(S_ISSOCK(m))
	    return(EDV_OBJECT_TYPE_SOCKET);
#endif
	else
	    return(EDV_OBJECT_TYPE_FILE);
}

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

	switch(type)
	{
	  case EDV_OBJECT_TYPE_UNKNOWN:
	    break;
	  case EDV_OBJECT_TYPE_FILE:
#ifdef S_IFREG
	    m |= S_IFREG;
#endif
	    break;
	  case EDV_OBJECT_TYPE_DIRECTORY:
#ifdef S_IFDIR
	    m |= S_IFDIR;
#endif
	    break;
	  case EDV_OBJECT_TYPE_LINK:
#ifdef S_IFLNK
	    m |= S_IFLNK;
#endif
	    break;
	  case EDV_OBJECT_TYPE_FIFO:
#if defined(S_IFFIFO)
	    m |= S_IFFIFO;
#elif defined(S_IFIFO)
	    m |= S_IFIFO;
#endif
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_BLOCK:
#ifdef S_IFBLK
	    m |= S_IFBLK;
#endif
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
#ifdef S_IFCHR
	    m |= S_IFCHR;
#endif
	    break;
	  case EDV_OBJECT_TYPE_SOCKET:
#ifdef S_IFSOCK
	    m |= S_IFSOCK;
#endif
	    break;
	  case EDV_OBJECT_TYPE_ERROR:
	    break;
	}

	return(m);
}

/*
 *	Returns the name of the object's type.
 */
const gchar *EDVObjectGetTypeName(const edv_object_type type)
{
	switch(type)
	{
	  case EDV_OBJECT_TYPE_UNKNOWN:
	    return("Unknown");
	    break;
	  case EDV_OBJECT_TYPE_FILE:
	    return("File");
	    break;
	  case EDV_OBJECT_TYPE_DIRECTORY:
	    return("Directory");
	    break;
	  case EDV_OBJECT_TYPE_LINK:
	    return("Link");
	    break;
	  case EDV_OBJECT_TYPE_FIFO:
	    return("FIFO Pipe");
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_BLOCK:
	    return("Block Device");
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
	    return("Character Device");
	    break;
	  case EDV_OBJECT_TYPE_SOCKET:
	    return("Socket");
	    break;
	  case EDV_OBJECT_TYPE_ERROR:
	    return("Error");
	    break;
	}

	return("Unknown");
}

/*
 *	Returns the name of the object's type without its first
 *	character capitalized..
 */
const gchar *EDVObjectGetTypeNameLower(const edv_object_type type)
{
	switch(type)
	{
	  case EDV_OBJECT_TYPE_UNKNOWN:
	    return("unknown");
	    break;
	  case EDV_OBJECT_TYPE_FILE:
	    return("file");
	    break;
	  case EDV_OBJECT_TYPE_DIRECTORY:
	    return("directory");
	    break;
	  case EDV_OBJECT_TYPE_LINK:
	    return("link");
	    break;
	  case EDV_OBJECT_TYPE_FIFO:
	    return("FIFO pipe");
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_BLOCK:
	    return("block device");
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
	    return("character device");
	    break;
	  case EDV_OBJECT_TYPE_SOCKET:
	    return("socket");
	    break;
	  case EDV_OBJECT_TYPE_ERROR:
	    return("error");
	    break;
	}

	return("unknown");
}


/*
 *	Returns the edv_permission_flags based on the given stat()
 *	mode_t.
 */
edv_permission_flags EDVObjectGetPermissionsFromStatMode(const 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(
	const 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);
}


/*
 *	Checks if the objects are the same by comparing their
 *	index values.
 */
gboolean EDVObjectCompareIndex(
	edv_object_struct *obj1, edv_object_struct *obj2
)
{
	if((obj1 == NULL) || (obj2 == NULL))
	    return(FALSE);

	if((obj1->device_index == obj2->device_index) &&
	   (obj1->index == obj2->index)
	)
	    return(TRUE);
	else
	    return(FALSE);
}

/*
 *	Checks if the object is the same as the one specified by
 *	the struct stat values.
 */
gboolean EDVObjectCompareIndexWithStat(
	edv_object_struct *obj, const struct stat *stat_buf
)
{
	if((obj == NULL) || (stat_buf == NULL))
	    return(FALSE);

	if((obj->device_index == (gulong)stat_buf->st_dev) &&
	   (obj->index == (gulong)stat_buf->st_ino)
	)
	    return(TRUE);
	else
	    return(FALSE);
}


/*
 *	Creates a new Object.
 */
edv_object_struct *EDVObjectNew(void)
{
	return(EDV_OBJECT(
	    g_malloc0(sizeof(edv_object_struct))
	));
}

/*
 *	Coppies the object.
 */
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->device_index = src->device_index;
	tar->index = src->index;

	tar->size = src->size;

	tar->link_value = STRDUP(src->link_value);
	tar->link_flags = src->link_flags;

	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->device_type = src->device_type;

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

	tar->hard_links = src->hard_links;

	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, always assume it is an absolute path */
	    obj->full_path = STRDUP(path);

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

	    /* Get name from path */
	    s = g_basename(obj->full_path);
	    obj->name = STRISEMPTY(s) ?
		STRDUP(obj->full_path) : STRDUP(s);
	}
}

/*
 *	Sets the Object's name and full path from the specified parent
 *	and name.
 */
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 name and full_path should be set by a prior call
 *	to EDVObjectSetPath*().
 *
 *	The object's type, permissions, access_time, modify_time,
 *	change_time, owner_id, group_id, hard_links, size, device,
 *	inode, device_type, block_size, and blocks will be set.
 */
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;

	/* Type */
	obj->type = EDVObjectGetTypeFromStatMode(m);

	/* Device index and inode */
	obj->device_index = (gulong)lstat_buf->st_dev;
	obj->index = (gulong)lstat_buf->st_ino;

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

	/* Assume link is valid when setting stats, calling function
	 * may update this afterwards
	 */
	obj->link_flags = EDV_OBJECT_LINK_VALID;

	/* Permissions */
	obj->permissions = EDVObjectGetPermissionsFromStatMode(m);

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

	/* Ownership */
	obj->owner_id = (gint)lstat_buf->st_uid;
	obj->group_id = (gint)lstat_buf->st_gid;

	/* Device Type */
	obj->device_type = (gint)lstat_buf->st_rdev;

	/* Block transfer size and blocks allocated */
	obj->block_size = (gulong)lstat_buf->st_blksize;
	obj->blocks = (gulong)lstat_buf->st_blocks;

	/* Number of hard links */
	obj->hard_links = (gint)lstat_buf->st_nlink;

#ifdef S_ISLNK
	/* If this is a link then update the Object's link_value */
	if(S_ISLNK(m))
	{
	    g_free(obj->link_value);
	    obj->link_value = EDVGetLinkValue(obj->full_path);
	}
#endif
}

/*
 *	Updates the object's link_flags.
 *
 *	The specified object must be of type EDV_OBJECT_TYPE_LINK and
 *	its full_path and link_value must be set by a prior call to
 *	EDVObjectSetStat().
 */
void EDVObjectUpdateLinkFlags(edv_object_struct *obj)
{
	gchar *path, *parent;
	struct stat stat_buf;

	if(obj == NULL)
	    return;

	obj->link_flags = 0;

	if(!EDV_OBJECT_IS_LINK(obj) ||
	   (obj->full_path == NULL) || (obj->link_value == NULL)
	)
	    return;

	parent = g_dirname(obj->full_path);
	if(parent == NULL)
	    return;

	path = EDVEvaluatePath(parent, obj->link_value);
	if(path == NULL)
	{
	    g_free(parent);
	    return;
	}

	if(!stat(path, &stat_buf))
	{
	    obj->link_flags |= EDV_OBJECT_LINK_VALID;
#ifdef S_ISDIR
	    if(S_ISDIR(stat_buf.st_mode))
	    {
		obj->link_flags |= EDV_OBJECT_LINK_TAR_DIRECTORY;
		if(EDVIsLinkInfinatelyRecursive(obj->full_path))
		    obj->link_flags |= EDV_OBJECT_LINK_TAR_GRAND_PARENT;
	    }
#endif
	}

	g_free(path);
	g_free(parent);
}

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

	g_free(obj);
}
