#ifndef _USE_BSD
/* For wait3() */
# define _USE_BSD
#endif
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <sched.h>
#include <sys/wait.h>
#include <fnmatch.h>
#include <unistd.h>
#include <glib.h>

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

#include "edvtypes.h"
#include "edvutils.h"
#include "config.h"


extern char **environ;


gchar *EDVSizeStrDelim(gulong i);

const gchar *EDVGetPathName(const gchar *path);

void EDVSimplifyPath(gchar *path);

gboolean EDVIsParentPath(const gchar *parent, const gchar *child);
static gboolean EDVIsExtensionIterate(
	const gchar *name, gint name_len,
	const gchar *ext, gint ext_len
);
gboolean EDVIsExtension(const gchar *name, const gchar *ext);

gchar *EDVCopyShortenPath(const gchar *path, gint max);
gchar *EDVCopyEvaluateInputPath(
	const gchar *parent, const gchar *input_path
);
gint EDVCHDir(const gchar *path);
gchar *EDVGetCWD(void);   
gchar *EDVTmpName(const gchar *dir);
gchar *EDVWhich(const gchar *name);

void EDVGetDeviceNumbers(gint rdev, gint *major, gint *minor);
gint EDVFormatDeviceNumbers(gint major, gint minor);

gboolean EDVProcessIsRunning(gint pid);

static void EDVSystemWaitCB(gint s);
static gpointer EDVSystemAddSignal(gint signum, void (*handler)(gint));
gint EDVSystem(const gchar *cmd);
gint EDVSystemBlock(const gchar *cmd, gint *status);

GList *EDVOpenFileGList(const gchar *path, gint max_lines);
void EDVSaveFileGList(const gchar *path, const GList *glist);


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


static gchar *G_STRCAT(gchar *s, const gchar *s2)
{
	if(s != NULL) {
	    if(s2 != NULL) {
		gchar *sr = g_strconcat(s, s2, NULL);
		g_free(s);
		s = sr;
	    }
	} else {
	    if(s2 != NULL)
		s = STRDUP(s2);
	    else
		s = STRDUP("");
	}
	return(s);
}


/*
 *	Similar to EDVGetObjectSizeStr() except that the string is
 *	always deliminated.
 */
gchar *EDVSizeStrDelim(gulong i)
{
	gint comma_countdown, slen;
	gchar ss[80], *ss_ptr;
	static gchar ts[80], *ts_ptr;


	g_snprintf(ss, sizeof(ss), "%ld", i);
	slen = STRLEN(ss);

	/* 3 digits or less? (no commas needed) */
	if(slen <= 3)
	{
	    strcpy(ts, ss);
	    return(ts);
	}

	ts_ptr = ts;
	ss_ptr = ss;

	/* Initialize comma counter */
	comma_countdown = slen % 3;
	if(comma_countdown <= 0)
	    comma_countdown = 3;

	/* Iterate through size string until end is reached */
	while(*ss_ptr != '\0')
	{
	    /* Reset comma counter and put in a comma? */
	    if(comma_countdown <= 0)
	    {
		*ts_ptr++ = ',';
		comma_countdown = 3;
	    }

	    *ts_ptr++ = *ss_ptr++;
	    comma_countdown--;
	}

	/* Null terminate return string */
	*ts_ptr = '\0';

	return(ts);
}


/*
 *	Returns a pointer within the given path that points to the
 *	object's name in the path.
 *
 *	Example "/dir/file.ext" returns "file.ext".
 *
 *	If there are no path deliminators in the given path then the
 *	pointer to path is returned.
 */
const gchar *EDVGetPathName(const gchar *path)
{
	const gchar *name = (path != NULL) ?
	    strrchr(path, G_DIR_SEPARATOR) : NULL;

	if(name != NULL)
	    name++;
	else
	    name = path;

	if(name == NULL)
	    return(NULL);

	return((*name != '\0') ? name : path);
}


/*
 *	Simplifies the path by removing any tailing deliminator characters
 *	and reducing any occurances of "/.." in the given path.
 */
void EDVSimplifyPath(gchar *path)
{
	gboolean is_absolute;

	if(STRISEMPTY(path))
	    return;

	is_absolute = g_path_is_absolute(path);

	/* Remove tailing deliminator characters if any */
	StripPath(path);

	/* Reduce all occurances of "/.." in the path */
	SimplifyPath(path);

	/* If the path was originally an absolute path and now its
	 * become an empty string then set path as toplevel
	 */
	if(is_absolute && (*path == '\0'))
	    strcpy(path, "/");
}


/*
 *	Returns TRUE if the given parent path is a parent or grand
 *	parent of the given child path.
 *
 *	Will also return TRUE if parent path and child paths are
 *	identical.
 *
 *	Both paths must be absolute and simplified.
 */
gboolean EDVIsParentPath(const gchar *parent, const gchar *child)
{
	gboolean status;
	gint len;
	gchar	*lparent,
		*lchild,
		delim_str[2];

#define DO_FREE_LOCALS	{	\
 g_free(lparent);		\
 lparent = NULL;		\
 g_free(lchild);		\
 lchild = NULL;			\
}
	
	if(STRISEMPTY(parent) || STRISEMPTY(child))
	    return(FALSE);

	/* Both paths must be absolute */
	if(!g_path_is_absolute(parent) || !g_path_is_absolute(child))
	    return(FALSE);

	/* Make coppies of the parent and child paths */
	lparent = STRDUP(parent);
	lchild = STRDUP(child);

	/* Need to tack on a tailing G_DIR_SEPARATOR character to the
	 * parent path for proper prefix matching below
	 */
	delim_str[0] = G_DIR_SEPARATOR;
	delim_str[1] = '\0';

	len = STRLEN(lparent);
	if(len > 0)
	{
	    if(lparent[len - 1] != G_DIR_SEPARATOR)
		lparent = G_STRCAT(lparent, delim_str);
	    if(lparent == NULL)
	    {
		DO_FREE_LOCALS
		return(FALSE);
	    }
	}

	len = STRLEN(lchild);
	if(len > 0)
	{
	    if(lchild[len - 1] != G_DIR_SEPARATOR)
		lchild = G_STRCAT(lchild, delim_str);
	    if(lchild == NULL)
	    {
		DO_FREE_LOCALS
		return(FALSE);
	    }
	}

	/* Check if the lparent is a prefix of lchild, if it is then
	 * it suggest that it is a parent or grand parent of the child
	 * path
	 */
	if(strpfx(lchild, lparent))
	    status = TRUE;
	else
	    status = FALSE;

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Called by EDVIsExtension() to check if ext matches the path.
 *
 *	The ext must only contain one word (no spaces).
 *
 *	Both path and ext may not be NULL.
 */
static gboolean EDVIsExtensionIterate(
	const gchar *name, gint name_len,
	const gchar *ext, gint ext_len
)
{
	/* Extension starts with a '.' deliminator? */
	if(*ext == '.')
	{
	    const gchar *name_ext = name + (name_len - ext_len);

	    /* Check if ext is a suffix of name */
	    if(name_len < ext_len)
		return(FALSE);

	    return((g_strcasecmp(name_ext, ext)) ? FALSE : TRUE);
	}
	/* Not an extension, so use fnmatch() */
	else
	{
	    return((fnmatch(ext, name, 0) == 0) ? TRUE : FALSE);
	}
}

/*
 *	Checks if at least one of the specified list of space separated
 *	extensions matches the extension of the specified name
 *
 *	The following example will return TRUE.
 *
 *	name="myfile.doc"
 *	ext=".txt .doc .nfo *rc Makefile*"
 */
gboolean EDVIsExtension(const gchar *name, const gchar *ext)
{
	const gchar *ss, *ext_ptr;
	gint name_len, ext_len;
	gchar *st;
	gchar cur_ext[NAME_MAX];

	if(STRISEMPTY(name) || STRISEMPTY(ext))
	    return(FALSE);

	name_len = STRLEN(name);

	/* Set ext_ptr to start of extensions list string, seeking past
	 * any initial spaces
	 */
	ext_ptr = ext;
	while(ISBLANK(*ext_ptr))
	    ext_ptr++;

	/* Iterate through each word in the extensions list */
	while(*ext_ptr != '\0')
	{
	    /* Copy this word in the extensions list string to
	     * cur_ext and calculate this extension's length as
	     * ext_len
	     */
	    for(ext_len = 0,
		st = cur_ext,
		ss = ext_ptr;
		(ext_len < (sizeof(cur_ext) - 1)) &&
		!ISBLANK(*ss) && (*ss != '\0');
	        ext_len++
	    )
		*st++ = *ss++;
	    *st = '\0';

	    /* Check this extension word matches */
	    if(EDVIsExtensionIterate(name, name_len, cur_ext, ext_len))
		return(TRUE);

	    /* At this point ss should be at the end of the word (at
	     * the first space) or at the end of the extensions list
	     * string
	     */
	    ext_ptr = ss;
	    while(ISBLANK(*ext_ptr))
		ext_ptr++;
	}

	return(FALSE);
}


/*
 *	Returns a dynamically allocated path that contains no more
 *	than max characters.
 *
 *	If the given path is longer than max then the shortened
 *	return stirng will contain "..." prefixed to it.
 */
gchar *EDVCopyShortenPath(const gchar *path, gint max)
{
	gint len;


	if(path == NULL)
	    return(NULL);

	len = STRLEN(path);
	if((len > max) && (max > 3))
	{
	    /* Need to shorten string */
	    gint i = len - max + 3;

	    return(g_strdup_printf(
		"...%s", &path[i]
	    ));
	}
	else
	{
	    return(STRDUP(path));   
	}
}

/*
 *	Returns a dynamically allocated path evaluated (all variables
 *	and special notations substituted) based on the specified path.
 *
 *	If input_path (after evaluation) is not an absolute path then the
 *	given parent path (if it is not NULL) will be prefixed to it.
 *
 *	The final path will be simplified.
 *
 *	The calling function must delete the returned pointer.
 */
gchar *EDVCopyEvaluateInputPath(
	const gchar *parent, const gchar *input_path
)
{
	gchar *new_path;

	if(input_path == NULL)
	    return(NULL);

	if(parent == NULL)
	    parent = "/";

	/* Check for special prefix substitutions for the input path */
	if(strcasepfx(input_path, "~"))
	{
	    /* Prefix the value from the HOME environment variable to
	     * the input path and generate the new path
	     */
	    const gchar	*home = g_getenv(ENV_VAR_NAME_HOME),
			*s = input_path + STRLEN("~");

	    /* If home directory environment variable value was not set
	     * then assume toplevel
	     */
	    if(home == NULL)
		home = "/";

	    /* Seek input path after the special prefix and past any
	     * deliminator characters (deliminator character will be
	     * added when the new path is generated)
	     */
	    while(*s == G_DIR_SEPARATOR)
		s++;

	    /* Generate new path */
	    new_path = STRDUP(PrefixPaths(home, s));
	}
	else
	{
	    /* If the given input path is an absolute path then do not
	     * prefix the given parent to it
	     */
	    if(g_path_is_absolute(input_path))
		new_path = STRDUP(input_path);
	    else
		new_path = STRDUP(PrefixPaths(parent, input_path));
	}

	/* Begin simplifying the generated path */

	/* Simplify path, reducing occurances of "../" */
	SimplifyPath(new_path);

	/* Remove tailing deliminators */
	StripPath(new_path);

	return(new_path);
}

/*
 *	Changes the current working directory.
 *
 *	Returns non-zero on error.
 */
gint EDVCHDir(const gchar *path)
{
	if(STRISEMPTY(path))
	    return(-1);

	return(chdir(path));
}

/*
 *	Returns a string describing the current working directory.
 *
 *	The calling function must delete the returned string.
 */
gchar *EDVGetCWD(void)
{
	gchar path[PATH_MAX];

	if(getcwd(path, sizeof(path)) == NULL)
	    return(NULL);

	path[sizeof(path) - 1] = '\0';

	return(STRDUP(path));
}

/*
 *	Generates a string describing a unique file name in the
 *	directory specified by dir that is gauranteed to not exist.
 *
 *	The calling function must delete the returned string.
 */
gchar *EDVTmpName(const gchar *dir)
{
	gchar *path;

	if(STRISEMPTY(dir))
#ifdef P_tmpdir
	    dir = P_tmpdir;
#else
	    dir = "/tmp";
#endif

	/* Create template path */
	path = STRDUP(PrefixPaths(
	    dir,
	    PROG_NAME "XXXXXX"
	));
	if(path == NULL)
	    return(NULL);

	/* Create tempory file and modify the template path */
	mkstemp(path);

	return(path);
}


/*
 *	Returns an absolute path of the program found from the PATH
 *	environment variable for the specified name or NULL if there is
 *	no match.
 *
 *	The calling function must delete the returned pointer.
 */
gchar *EDVWhich(const gchar *name)
{
	gint i;
	const gchar *path_list;
	gchar *s, *matched_path, **pathv;

	if(STRISEMPTY(name))
	    return(NULL);

	path_list = g_getenv(ENV_VAR_NAME_PATH);
	if(path_list == NULL)
	    return(NULL);

	pathv = g_strsplit(path_list, ":", -1);
	if(pathv == NULL)
	    return(NULL);

	matched_path = NULL;
	for(i = 0; pathv[i] != NULL; i++);
	for(i--; i >= 0; i--)
	{
	    s = STRDUP(PrefixPaths(
		(const char *)pathv[i], (const char *)name
	    ));
	    if(s == NULL)
		continue;

	    if(!access((const char *)s, X_OK))
	    {
		matched_path = s;
		break;
	    }

	    g_free(s);
	}

	g_strfreev(pathv);

	return(matched_path);
}


/*
 *	Returns the device major and minor numbers parsed from the
 *	specified (dev_t)rdev value (obtained from stat()).
 */
void EDVGetDeviceNumbers(gint rdev, gint *major, gint *minor)
{
	if(major != NULL)
	    *major = (gint)((rdev >> 8) & 0xff);
	if(minor != NULL)
	    *minor = (gint)((rdev >> 0) & 0xff);
}

/*
 *	Returns the (dev_t)rdev value (obtained from stat()) parsed from
 *	the specified device major and minor numbers.
 */
gint EDVFormatDeviceNumbers(gint major, gint minor)
{
	return((gint)(
	    ((dev_t)major << 8) | ((dev_t)minor << 0)
	));
}


/*
 *	Checks if the specified process is still running.
 */
gboolean EDVProcessIsRunning(gint pid)
{
#if defined(__linux__)
	gchar path[PATH_MAX + NAME_MAX];
	g_snprintf(path, sizeof(path), "/proc/%i", pid);
	return(access(path, F_OK) ? FALSE : TRUE);
#else
	struct sched_param sp;

	if(pid <= 0)
	    return(FALSE);

	if(sched_getparam(
	    (pid_t)pid,
	    &sp
	) == 0)
	    return(TRUE);
	else
	    return(FALSE);
#endif   
}


/*
 *	SIGCHLD signal callback.
 *
 *	Used by EDVSystem*().
 */
static void EDVSystemWaitCB(gint s)
{
	gint status;
	while(wait3((int *)&status, WNOHANG, NULL) > 0);
}

/*
 *	Adds a signal using the BSD style signal(2).
 *
 *	Returns the old signal action handler of type
 *	void (*handler)(gint).
 */
static gpointer EDVSystemAddSignal(gint signum, void (*handler)(gint))
{
	struct sigaction act, old_act;

	act.sa_handler = handler;
	act.sa_flags = 0;

	if(sigaction((int)signum, &act, &old_act) == -1)
	    return((gpointer)SIG_ERR);
	else
	    return((gpointer)old_act.sa_handler);
}

/*
 *	Executes the command specified by cmd in the background.
 *
 *	Returns the process ID or -1 on error.
 */
gint EDVSystem(const gchar *cmd)
{
	gint pid;

	if(STRISEMPTY(cmd))
	    return(-1);

#ifdef SIGCHLD
	EDVSystemAddSignal(SIGCHLD, EDVSystemWaitCB);
#endif

	/* Fork */
	pid = (gint)fork();
	if(pid == -1)
	    return(-1);

	/* Are we the child? */
	if(pid == 0)
	{
	    /* We are the child, execute command */
	    gchar *argv[4];
	    argv[0] = "sh";
	    argv[1] = "-c";
	    argv[2] = (gchar *)cmd;
	    argv[3] = 0;
	    execve("/bin/sh", (char **)argv, environ);
	    exit(0);
	}
	else
	{
	    /* We are the parent */
	}

	return(pid);
}

/*
 *	Executes the command specified by cmd and blocks until it has
 *	exited.
 *
 *	If status is not NULL then the status of the command
 *	be returned.
 *
 *	Returns the process ID or -1 on error.
 */
gint EDVSystemBlock(const gchar *cmd, gint *status)
{
	gint pid;

	if(STRISEMPTY(cmd))
	    return(-1);

	/* Fork */
	pid = (gint)fork();
	if(pid == -1)
	    return(-1);

	/* Are we the child? */
	if(pid == 0)
	{
	    /* We are the child, execute command */
	    gchar *argv[4];
	    argv[0] = "sh";
	    argv[1] = "-c";
	    argv[2] = (gchar *)cmd;
	    argv[3] = 0;
	    execve("/bin/sh", (char **)argv, environ);
	    exit(0);
	}
	else
	{
	    /* We are the parent, wait for child to exit */
	    const gint wait_status = (gint)waitpid(
		(int)pid, (int *)status, 0
	    );
	    if(wait_status == -1)
	    {
		/* Interrupted? */
		if(errno == EINTR)
		{

		}
	    }
	}

	return(pid);
}


/*
 *	Loads the contents of the file specified by path and returns
 *	them as a list of allocated strings in a GList.
 *
 *	If max_lines is positive then no more than max_lines will be
 *	read from the file.
 */
GList *EDVOpenFileGList(const gchar *path, gint max_lines)
{
	gint lines_read;
	GList *glist;
	gchar *s;

	/* Open file for reading */
	FILE *fp = FOpen((const char *)path, "rb");
	if(fp == NULL)
	    return(NULL);

	/* Read each line of the file and store them to a GList */
	lines_read = 0;
	glist = NULL;
	s = (gchar *)FGetStringLiteral(fp);
	while(s != NULL)
	{
	    lines_read++;

	    /* Append line to GList and read next line (if any) */
	    glist = g_list_append(glist, s);
	    s = (gchar *)FGetStringLiteral(fp);

	    /* Is max_lines specified and number of lines read has
	     * reached max_lines?
	     */
	    if((max_lines > 0) && (lines_read >= max_lines))
	    {
		g_free(s);
		break;
	    }
	}

	/* Close file */
	FClose(fp);

	return(glist);
}

/*
 *	Writes each string in the GList to the file specified by path.
 */
void EDVSaveFileGList(const gchar *path, const GList *glist)
{
	gint lines_written;
	const gchar *s;
	/* Open file for writing */
	FILE *fp = FOpen((const char *)path, "wb");
	if(fp == NULL)
	    return;

	/* Write each line in the GList */
	lines_written = 0;
	while(glist != NULL)
	{
	    /* Get current line */
	    s = (gchar *)glist->data;
	    if(s != NULL)
	    {
		/* Write line */
		fwrite(s, sizeof(gchar), STRLEN(s), fp);
		fputc('\n', fp);	/* Add newline character */
		lines_written++;
	    }
	    glist = g_list_next(glist);
	}


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

