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

#include "edv_context.h"
#include "edv_get.h"
#include "edv_mime_type.h"
#include "edv_mime_type_get.h"
#include "edv_window.h"
#include "edv_utils.h"
#include "edv_open.h"
#include "edv_cfg_list.h"
#include "config.h"


static gchar *EDVOpenStripExt(const gchar *s);
static gboolean EDVOpenIsFileExecutable(const gchar *path);
static gchar *EDVOpenFormatCommand(
	edv_context_struct *ctx, const gchar *cmd, GList *paths_list
);
static edv_mime_type_struct *EDVOpenGetAssociatedMIMEType(
	edv_context_struct *ctx, const gchar *type
);

static gint EDVOpenIteration(
	edv_context_struct *ctx,
	const gchar *path, const gchar *command_name
);
gint EDVOpen(
	edv_context_struct *ctx,
	GList *paths_list, const gchar *command_name
);


#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 a copy of the path or name without the extension.
 */
static gchar *EDVOpenStripExt(const gchar *s)
{
	gint len;
	const gchar *name, *ext;
	gchar *path_wo_ext;

	if(s == NULL)
	    return(NULL);

	/* Get the name */
	name = strrchr(s, G_DIR_SEPARATOR);
	if(name != NULL)
	    name++;
	else
	    name = s;

	/* Get the extension */
	if(*name == '.')
	    ext = strchr(name + 1, '.');
	else
	    ext = strchr(name, '.');

	/* No extension? */
	if(ext == NULL)
	    return(STRDUP(s));

	/* Get the path without the extension */
	len = (gint)(ext - s);
	path_wo_ext = (gchar *)g_malloc((len + 1) * sizeof(gchar));
	if(len > 0)
	    memcpy(path_wo_ext, s, len * sizeof(gchar));
	path_wo_ext[len] = '\0';

        /* Need to tack on the ending double quote character? */
        if(*path_wo_ext == '"')
        {
            gchar *s2 = g_strconcat(path_wo_ext, "\"", NULL);
            g_free(path_wo_ext);
            path_wo_ext = s2;
        }

	return(path_wo_ext);
}

/*
 *	Checks if the object specified by path exists, is a file, and
 *	is executable.
 */
static gboolean EDVOpenIsFileExecutable(const gchar *path)
{
	struct stat stat_buf;

	if(STRISEMPTY(path))
	    return(FALSE);

	/* Get destination stats */
	if(stat(path, &stat_buf))
	    return(FALSE);

#ifdef S_ISREG
	/* Does the path lead to a file? */
	if(!S_ISREG(stat_buf.st_mode))
	    return(FALSE);
#endif
      
	/* Is this file executable? */
	return(!access(path, X_OK) ? TRUE : FALSE);
}


/*
 *	Performs substitutions to the command string specified by cmd
 *	with the values specified in the context and the paths list.
 *
 *	Returns a dynamically allocated string describing the command  
 *	with all the substitutions made.
 */
static gchar *EDVOpenFormatCommand(
	edv_context_struct *ctx, const gchar *cmd, GList *paths_list
)
{
	const gchar *path, *first_path, *last_path;
	gchar	*s, *cmd_rtn,
		*cwd_quoted, *display_quoted, *home_quoted,
		*names_quoted, *paths_quoted,
		*first_name_quoted, *first_name_wo_ext_quoted,
		*last_name_quoted, *last_name_wo_ext_quoted,
		*first_path_quoted, *first_path_wo_ext_quoted,
		*last_path_quoted, *last_path_wo_ext_quoted,
		*pid_quoted;
	GList *glist;

	if(cmd == NULL)
	    return(NULL);

	/* Begin creating substitution values */

	/* Current working directory */
	s = EDVGetCWD();
	cwd_quoted = g_strdup_printf("\"%s\"", s);
	g_free(s);

	/* Display address */
	display_quoted = g_strdup_printf(
	    "\"%s\"",
	    g_getenv(ENV_VAR_NAME_DISPLAY)
	);

	/* Home directory */
	home_quoted = g_strdup_printf(
	    "\"%s\"",
	    g_getenv(ENV_VAR_NAME_HOME)
	);

	/* All selected object names */
	names_quoted = STRDUP("\"");
	for(glist = paths_list; glist != NULL; glist = g_list_next(glist))
	{
	    path = (gchar *)glist->data;
	    if(path == NULL)
		continue;

	    s = g_strconcat(
		names_quoted,
		g_basename(path),
		(glist->next != NULL) ? " " : "\"",
		NULL
	    );
	    g_free(names_quoted);
	    names_quoted = s;
	}

	/* All selected object paths */
	paths_quoted = STRDUP("\"");
	for(glist = paths_list; glist != NULL; glist = g_list_next(glist))
	{
	    path = (gchar *)glist->data;
	    if(path == NULL)
		continue;

	    s = g_strconcat(
		paths_quoted,
		path,
		(glist->next != NULL) ? " " : "\"",
		NULL
	    );
	    g_free(paths_quoted);
	    paths_quoted = s;
	}

	/* First selected object name and path */
	glist = g_list_first(paths_list);
	first_path = (glist != NULL) ? (gchar *)glist->data : NULL;
	if(first_path == NULL)
	    first_path = "";
	first_name_quoted = g_strdup_printf("\"%s\"", g_basename(first_path));
	first_name_wo_ext_quoted = EDVOpenStripExt(first_name_quoted);
	first_path_quoted = g_strdup_printf("\"%s\"", first_path);
	first_path_wo_ext_quoted = EDVOpenStripExt(first_path_quoted);

	/* Last selected object name and path */
	glist = g_list_last(paths_list);
	last_path = (glist != NULL) ? (gchar *)glist->data : NULL;
	if(last_path == NULL)
	    last_path = "";
	last_name_quoted = g_strdup_printf("\"%s\"", g_basename(last_path));
	last_name_wo_ext_quoted = EDVOpenStripExt(last_name_quoted);
	last_path_quoted = g_strdup_printf("\"%s\"", last_path);
	last_path_wo_ext_quoted = EDVOpenStripExt(last_path_quoted);

	/* Process ID */
	pid_quoted = g_strdup_printf("\"%i\"", getpid());


	/* Begin formatting the command, always use the quoted string
	 * as the value and substitute for the quoted token first then
	 * the unquoted token
	 */
	cmd_rtn = STRDUP(cmd);

#define CMDSUB(_token_,_val_)	{			\
 gchar *s = EDVStrSub(cmd_rtn, (_token_), (_val_));	\
 g_free(cmd_rtn);					\
 cmd_rtn = s;						\
}

	/* "%cwd" - Current working directory */
	CMDSUB("\"%cwd\"", cwd_quoted);
	CMDSUB("%cwd", cwd_quoted);

	/* "%display" - Display address */
	CMDSUB("\"%display\"", display_quoted);
	CMDSUB("%display", display_quoted);

	/* "%home" - Home directory */
	CMDSUB("\"%home\"", home_quoted);
	CMDSUB("%home", home_quoted);


	/* "%names" - Names of all selected objects (without paths) */
	CMDSUB("\"%names\"", names_quoted);
	CMDSUB("%names", names_quoted);

	/* "%paths" - Full paths of all selected objects */
	CMDSUB("\"%paths\"", last_path_quoted);
	CMDSUB("%paths", last_path_quoted);


	/* "%first_name_wo_ext" - Name of the first selected object
	 * (without path or extension)
	 */
	CMDSUB("\"%first_name_wo_ext\"", first_name_wo_ext_quoted);
	CMDSUB("%first_name_wo_ext", first_name_wo_ext_quoted);

	/* "%first_name" - Name of the first selected object (without
	 * path)
	 */
	CMDSUB("\"%first_name\"", first_name_quoted);
	CMDSUB("%first_name", first_name_quoted);

	/* "%first_path_wo_ext" - Full path to the last selected
	 * object without the extension
	 */
	CMDSUB("\"%first_path_wo_ext\"", first_path_wo_ext_quoted);
	CMDSUB("%first_path_wo_ext", first_path_wo_ext_quoted);

	/* "%first_path" - Full path to the last selected object */
	CMDSUB("\"%first_path\"", first_path_quoted);
	CMDSUB("%first_path", first_path_quoted);


	/* "%name_wo_ext" - Name of the last selected object (without
	 * path or ext)
	 */
	CMDSUB("\"%name_wo_ext\"", last_name_wo_ext_quoted);
	CMDSUB("%name_wo_ext", last_name_wo_ext_quoted);

	/* "%name" - Name of last selected object (without path) */
	CMDSUB("\"%name\"", last_name_quoted);
	CMDSUB("%name", last_name_quoted);

	/* "%path_wo_ext" - Full path to the last selected object
	 * without the extension
	 */
	CMDSUB("\"%path_wo_ext\"", last_path_wo_ext_quoted);
	CMDSUB("%path_wo_ext", last_path_wo_ext_quoted);

	/* "%path" - Full path to last selected object */
	CMDSUB("\"%path\"", last_path_quoted);
	CMDSUB("%path", last_path_quoted);


	/* "%pid" - This program's process id */
	CMDSUB("\"%pid\"", pid_quoted);
	CMDSUB("%pid", pid_quoted);


	/* "%pe" - Full path to the last selected object without
	 * the extension
	 */
	CMDSUB("\"%pe\"", last_path_wo_ext_quoted);
	CMDSUB("%pe", last_path_wo_ext_quoted);

	/* "%p" - Full path to last selected object */
	CMDSUB("\"%p\"", last_path_quoted);
	CMDSUB("%p", last_path_quoted);

	/* "%s" - Full path to last selected object: */
	CMDSUB("\"%s\"", last_path_quoted);
	CMDSUB("%s", last_path_quoted);

#undef CMDSUB

	g_free(cwd_quoted);
	g_free(display_quoted);
	g_free(home_quoted);
	g_free(names_quoted);
	g_free(paths_quoted);
	g_free(first_name_quoted);
	g_free(first_name_wo_ext_quoted);
	g_free(last_name_quoted);
	g_free(last_name_wo_ext_quoted);
	g_free(first_path_quoted);
	g_free(first_path_wo_ext_quoted);
	g_free(last_path_quoted);
	g_free(last_path_wo_ext_quoted);
	g_free(pid_quoted);

	return(cmd_rtn);
}


/*
 *	Gets a MIME Type of class program who's type matches the
 *	specified type.
 */
static edv_mime_type_struct *EDVOpenGetAssociatedMIMEType(
	edv_context_struct *ctx, const gchar *type
)
{
	gint i, n;
	edv_mime_type_struct *m, **list = EDVMimeTypeList(ctx, &n);
	if(list == NULL)
	    return(NULL);

	for(i = 0; i < n; i++)
	{
	    m = list[i];
	    if(m == NULL)
		continue;

	    if((m->mt_class != EDV_MIME_TYPE_CLASS_PROGRAM) ||
	       STRISEMPTY(m->type)
	    )
		continue;

	    if(!strcmp(m->type, type))
		return(m);
	}

	return(NULL);
}


/*
 *	Opens the object specified by path.
 */
static gint EDVOpenIteration(
	edv_context_struct *ctx,
	const gchar *path, const gchar *command_name
)
{
	struct stat lstat_buf;
	gint status = 0;
	edv_mime_type_struct *m;

	if(STRISEMPTY(path))
	    return(-2);

	/* Find a MIME Type associated with this object */
	m = EDVMimeTypeMatch(ctx, path, &lstat_buf);
	if(m != NULL)
	{
	    /* Found MIME Type associated with this object
	     *
	     * Use the appropriate handler specified by this MIME Type
	     * to open this object
	     */
	    switch(m->handler)
	    {
	      case EDV_MIME_TYPE_HANDLER_COMMAND:
		if(m->commands_list != NULL)
		{
		    const gchar *command_base = NULL;

		    /* If the command's name is specified then get the
		     * command who's name matches the specified command
		     * name
		     */
		    if(!STRISEMPTY(command_name))
		    {
			edv_mime_type_command_struct *cmd = EDVMimeTypeGetCommandByName(
			    m, command_name
			);
			if(cmd != NULL)
			    command_base = cmd->command;
		    }
		    else if(m->commands_list != NULL)
		    {
			GList *glist = m->commands_list;
			edv_mime_type_command_struct *cmd = EDV_MIME_TYPE_COMMAND(glist->data);
			if(cmd != NULL)
			    command_base = cmd->command;
		    }

		    /* Got command? */
		    if(!STRISEMPTY(command_base))
		    {
			/* If the command is not an absolute path then
			 * it contains the type (the name) of another
			 * MIME Type in which we must get the MIME Type
			 * that it refers to
			 */
			if(*command_base != G_DIR_SEPARATOR)
			{
			    m = EDVOpenGetAssociatedMIMEType(
				ctx, command_base
			    );
			    if(m != NULL)
			    {
				GList *glist = m->commands_list;
				edv_mime_type_command_struct *cmd = (glist != NULL) ?
				    EDV_MIME_TYPE_COMMAND(glist->data) : NULL;
				if(cmd != NULL)
				    command_base = cmd->command;
			    }
			}

			/* Enough information to open the object with
			 * this MIME Type?
			 */
			if((m != NULL) && !STRISEMPTY(command_base))
			{
			    gint pid;
			    gchar *parent, *cmd;
			    GList *paths_list = NULL;

			    /* Record previous working dir */
			    gchar *pwd = EDVGetCWD();

			    /* Set new working dir as the parent of
			     * the specified path
			     */
			    parent = g_dirname(path);
			    EDVSetCWD(parent);
			    g_free(parent);

			    /* Format command */
			    paths_list = g_list_append(
				paths_list, STRDUP(path)
			    );
			    cmd = EDVOpenFormatCommand(
				ctx, command_base, paths_list
			    );
			    g_list_foreach(paths_list, (GFunc)g_free, NULL);
			    g_list_free(paths_list);

			    /* Execute command */
			    pid = EDVSystem(cmd);
			    g_free(cmd);
			    if(pid <= 0)
				status = -1;

			    /* Change back to the previous working dir */
			    EDVSetCWD(pwd);
			    g_free(pwd);
			}
			else
			{
			    status = -7;
			}
		    }
		    else
		    {
			/* No command base */
			status = -7;
		    }
		}
		else
		{
		    /* No command */
		    status = -7;
		}
		break;

	      case EDV_MIME_TYPE_HANDLER_EDV_ARCHIVER:
		EDVWindowArchiverNew(ctx, path);
		EDVContextFlush(ctx);
		break;

	      case EDV_MIME_TYPE_HANDLER_EDV_IMAGE_BROWSER:
		EDVWindowImageBrowserNew(ctx, path);
		EDVContextFlush(ctx);
		break;

	      case EDV_MIME_TYPE_HANDLER_EDV_RECYCLE_BIN:
		EDVWindowRecycleBinMap(ctx);
		EDVContextFlush(ctx);
		break;
	    }
	}
	else
	{
	    /* No MIME Type associated with this object */
	    const gchar *def_viewer_cmd = EDVGetS(
		ctx, EDV_CFG_PARM_PROG_DEF_VIEWER
	    );

	    /* If the object executable then run it */
	    if(EDVOpenIsFileExecutable(path))
	    {
		gint pid;
		gchar *parent, *cmd;

		/* Record previous working dir */
		gchar *pwd = EDVGetCWD();

		/* Set new working dir as the parent of the specified
		 * path
		 */
		parent = g_dirname(path);
		EDVSetCWD(parent);
		g_free(parent);

		/* Format command to execute the object */
		cmd = g_strdup_printf(
		    "%s &",
		    path
		);

		/* Execute command */
		pid = EDVSystem(cmd);
		g_free(cmd);
		if(pid <= 0)
		    status = -1;

		/* Change back to the previous working dir */
		EDVSetCWD(pwd);
		g_free(pwd);
	    }
	    /* If the default viewer command is defined then open the
	     * object with the default viewer
	     */
	    else if(!STRISEMPTY(def_viewer_cmd))
	    {
		gint pid;
		gchar *parent, *cmd;
		GList *paths_list = NULL;

		/* Record previous working dir */
		gchar *pwd = EDVGetCWD();

		/* Set new working dir as the parent of the specified
		 * path
		 */
		parent = g_dirname(path);
		EDVSetCWD(parent);
		g_free(parent);

		/* Format command */
		paths_list = g_list_append(
		    paths_list,
		    STRDUP(path)
		);
		cmd = EDVOpenFormatCommand(
		    ctx, def_viewer_cmd, paths_list
		);
		g_list_foreach(paths_list, (GFunc)g_free, NULL);
		g_list_free(paths_list);

		/* Execute command */
		pid = EDVSystem(cmd);
		g_free(cmd);
		if(pid <= 0)
		    status = -1;

		/* Change back to the previous working dir */
		EDVSetCWD(pwd);
		g_free(pwd);
	    }
	    /* All else report that there was no method to open the
	     * object
	     */
	    else
	    {
		status = -7;
	    }
	}

	return(status);
}

/*
 *	Opens the object(s) specified by the list of strings paths_list.
 *
 *	If command_name is not NULL then the command on the associated
 *	MIME Type who's name matches command_name will be used.       
 *
 *	Typical values for command_name are; "default", "view",
 *	and "edit". However it is difficult to know what the user has
 *	set so it is recommended that you pass command_name as NULL
 *	when unsure.
 *
 *	Returns:
 *
 *	0	Success.
 *	-1	General error.
 *	-2	Invalid value or no such object.
 *	-3	System error or memory allocation error.
 *	-4	User aborted.
 *	-7	No associated MIME Type was found.
 */
gint EDVOpen(
	edv_context_struct *ctx,
	GList *paths_list, const gchar *command_name
)
{
	gint status = 0, result;
	GList *glist;

	if((ctx == NULL) || (paths_list == NULL))
	    return(-2);

	for(glist = paths_list; glist != NULL; glist = g_list_next(glist))
	{
	    result = EDVOpenIteration(
		ctx,
		(const gchar *)glist->data,
		command_name
	    );
	    if(status == 0)
		status = result;
	}

	return(status);
}
