#include <string.h>
#include <gtk/gtk.h>

#include "../include/string.h"
#include "../include/strexp.h"
#include "../include/prochandle.h"

#include "cfg.h"
#include "edvinterps.h"
#include "endeavour.h"
#include "edvhelp.h"
#include "edvmimetypesinstall.h"
#include "edvopen.h"
#include "edvcb.h"
#include "edvop.h"
#include "edvinterpsop.h"
#include "edvutilsgtk.h"
#include "edvcfglist.h"
#include "config.h"


void EDVInterPSOPProcessCommand(
	edv_core_struct *core_ptr, const gchar *cmd
);
void EDVInterPSOPProcessCommands(
	edv_core_struct *core_ptr,
	gchar **cmdv, gint cmdc
);


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


/*
 *	Processes the given InterPS command.
 */
void EDVInterPSOPProcessCommand(
	edv_core_struct *core_ptr, const gchar *cmd
)
{
	gint argc;
	gchar **argv;
	const gchar *op;
	cfg_item_struct *cfg_list = core_ptr->cfg_list;

	if(STRISEMPTY(cmd))
	    return;

	/* Explode command into arguments
	 *
	 * Note that we use ExecExplodeCommand() to handle arguments
	 * wraped in '"' characters
	 */
	argv = ExecExplodeCommand(cmd, &argc);
	if(argv == NULL)
	    return;

	/* Get operation argument (first argument) */
	op = (argc > 0) ? argv[0] : NULL;
	if(op == NULL)
	{
	    strlistfree(argv, argc);
	    return;
	}

	/* Begin handling operation */

	/* New Window */
	if(!g_strcasecmp(op, "new_window"))
	{
	    /* Note that the "new_window" command is sent by another
	     * Endeavour process (not another program) because otherwise
	     * if Endeavour is not running a new window cannot be
	     * created
	     *
	     * So other programs run Endeavour to instruct it to create
	     * a new window and Endeavour will check to see if it is
	     * already running and (if it is) then sends a "new_window"
	     * command which is handled here
	     */
	    if(argc >= 2)
	    {
		const gchar	*window_name = argv[1],
				*startup_path = (argc > 2) ? argv[2] : NULL,
				*window_extra = (argc > 3) ? argv[3] : NULL;

		/* Record new startup path if specified from the
		 * arguments, this is so that when a new window is
		 * created it will have the given startup path
		 */
		if((startup_path != NULL) ?
		    g_path_is_absolute(startup_path) : FALSE
		)
		    EDV_SET_S(EDV_CFG_PARM_DIR_START_UP, startup_path);

		/* Create the new window */
		EDVNewWindowNexus(
		    core_ptr,
		    window_name,	/* Window Name */
		    startup_path,	/* Startup Path */
		    window_extra,	/* Extra */
		    NULL		/* Toplevel */
		);
	    }
	}
	/* Set Cfg Integer */
	else if(!g_strcasecmp(op, "set_cfg_i"))
	{
	    if(argc >= 3)
	    {
		const gchar *parm = argv[1];
		gint val = ATOI(argv[2]);
		EDV_SET_I(parm, val);
	    }
	}
	/* Set Cfg Unsigned Integer */
	else if(!g_strcasecmp(op, "set_cfg_ui"))
	{
	    if(argc >= 3)
	    {
		const gchar *parm = argv[1];
		guint val = (guint)ATOI(argv[2]);
		EDV_SET_I(parm, (gint)val);
	    }
	}
	/* Set Cfg Long */
	else if(!g_strcasecmp(op, "set_cfg_l"))
	{
	    if(argc >= 3)
	    {
		const gchar *parm = argv[1];
		glong val = ATOL(argv[2]);
		EDV_SET_L(parm, val);
	    }
	}
	/* Set Cfg Unsigned Long */
	else if(!g_strcasecmp(op, "set_cfg_ul"))
	{
	    if(argc >= 3)
	    {
		const gchar *parm = argv[1];
		gulong val = ATOL(argv[2]);
		EDV_SET_UL(parm, val);
	    }
	}
	/* Set Cfg Float */
	else if(!g_strcasecmp(op, "set_cfg_f"))
	{
	    if(argc >= 3)
	    {
		const gchar *parm = argv[1];
		gfloat val = ATOF(argv[2]);
		EDV_SET_F(parm, val);
	    }
	}
	/* Set Cfg Double */
	else if(!g_strcasecmp(op, "set_cfg_d"))
	{
	    if(argc >= 3)
	    {
		const gchar *parm = argv[1];
		gdouble val = (gdouble)ATOF(argv[2]);
		EDV_SET_D(parm, val);
	    }
	}
	/* Set Cfg String */
	else if(!g_strcasecmp(op, "set_cfg_s"))
	{
	    if(argc >= 3)
	    {
		const gchar *parm = argv[1];
		const gchar *val = argv[2];
		EDV_SET_S(parm, val);
	    }
	}
	/* Open Object */
	else if(!g_strcasecmp(op, "open_object"))
	{
	    if(argc >= 2)
	    {
		const gchar	*path = argv[1],
				*command_name = (argc > 2) ? argv[2] : NULL;
		gchar	*stdout_path_rtn = NULL,
			*stderr_path_rtn = NULL;
		EDVOpenObjectPath(
		    core_ptr,
		    path,			/* Object Path */
		    command_name,		/* Command Name */
		    NULL,			/* Toplevel */
		    TRUE,			/* Verbose */
		    &stdout_path_rtn,		/* Stdout Path */
		    &stderr_path_rtn		/* Stderr Path */
		);
		g_free(stdout_path_rtn);
		g_free(stderr_path_rtn);
	    }
	}
	/* Write Protect Changed Notify */
	else if(!g_strcasecmp(op, "write_protect_changed_notify"))
	{
	    EDVWriteProtectChangedEmit(
		core_ptr,
		EDV_GET_B(EDV_CFG_PARM_WRITE_PROTECT)
	    );
	}
	/* Object Added Notify */
	else if(!g_strcasecmp(op, "object_added_notify"))
	{
	    if(argc >= 2)
	    {
		const gchar *path = argv[1];
		if(!STRISEMPTY(path))
		{
		    struct stat lstat_buf;
		    if(!lstat(path, &lstat_buf))
			EDVObjectAddedEmit(core_ptr, path, &lstat_buf);
		}
	    }
	}
	/* Object Modified Notify */
	else if(!g_strcasecmp(op, "object_modified_notify"))
	{
	    if(argc >= 2)
	    {
		const gchar	*path = argv[1],
				*new_path = (argc > 2) ? argv[2] : NULL;
		if(!STRISEMPTY(path))
		{
		    struct stat lstat_buf;
		    if(!lstat(
			STRISEMPTY(new_path) ? path : new_path,
			&lstat_buf
		    ))
			EDVObjectModifiedEmit(
			    core_ptr, path, new_path, &lstat_buf
			);
		}
	    }
	}
	/* Object Removed Notify */
	else if(!g_strcasecmp(op, "object_removed_notify"))
	{
	    if(argc >= 2)
	    {
		const gchar *path = argv[1];
		if(!STRISEMPTY(path))
		    EDVObjectRemovedEmit(core_ptr, path);
	    }
	}
	/* Object Mounted Notify or Object Unmounted Notify */
	else if(!g_strcasecmp(op, "object_mounted_notify") ||
	        !g_strcasecmp(op, "object_unmounted_notify")
	)
	{
	    if(argc >= 2)
	    {
		const gchar *path = argv[1];
		gint dev_num;
		edv_device_struct *dev_ptr;

		/* The path could be a mount path or device path, see
		 * which one it is by matching it in our list of device
		 * references
		 */
		for(dev_num = 0; dev_num < core_ptr->total_devices; dev_num++)
		{
		    dev_ptr = core_ptr->device[dev_num];
		    if(dev_ptr == NULL)
			continue;

		    if(!STRISEMPTY(dev_ptr->mount_path) &&
		       !STRISEMPTY(path)
		    )
		    {
			if(!strcmp(dev_ptr->mount_path, path))
			    break;
		    }
		    if(!STRISEMPTY(dev_ptr->device_path) &&
		       !STRISEMPTY(path)
		    )
		    {
			if(!strcmp(dev_ptr->device_path, path))
			    break;
		    }
		}
		/* Got match? */
		if((dev_num < core_ptr->total_devices) &&
		   (dev_ptr != NULL)
		)
		{
		    /* Need to update mount states and device capacities */
		    EDVDevicesListUpdateMountStates(
			core_ptr->device, core_ptr->total_devices
		    );
		    EDVDevicesListUpdateStats(
			core_ptr->device, core_ptr->total_devices
		    );
		    /* Emit mount or unmount signal */
		    EDVObjectMountEmit(
			core_ptr, dev_num, dev_ptr, dev_ptr->is_mounted
		    );
		}
	    }
	}
	/* Recycled Object Added Notify */
	else if(!g_strcasecmp(op, "recycled_object_added_notify"))
	{
	    if(argc >= 2)
	    {
		guint index = ATOI(argv[1]);
		EDVRecycledObjectAddedEmit(core_ptr, index);
	    }
	}
	/* Recycled Object Removed Notify */
	else if(!g_strcasecmp(op, "recycled_object_removed_notify"))
	{
	    if(argc >= 2)
	    {
		guint index = ATOI(argv[1]);
		EDVRecycledObjectRemovedEmit(core_ptr, index);
	    }
	}
	/* Reconfigured Notify */
	else if(!g_strcasecmp(op, "reconfigured_notify"))
	{
	    EDVReconfiguredEmit(core_ptr);
	}

	/* Append History */
	else if(!g_strcasecmp(op, "append_history"))
	{
	    /* Arguments:
	     *
	     * <type> <time_start> <time_end> <status>
	     * [source] [target] [comments]
	     */
	    if(argc >= 5)
	    {
		edv_history_type type = (edv_history_type)ATOI(argv[1]);
		gulong time_start = (gulong)ATOL(argv[2]);
		gulong time_end = (gulong)ATOL(argv[3]);
		gint status = ATOI(argv[4]);
		const gchar *source = (argc > 5) ? argv[5] : NULL;
		const gchar *target = (argc > 6) ? argv[6] : NULL;
		const gchar *comments = (argc > 7) ? argv[7] : NULL;
		/* Append history */
		EDVAppendHistory(
		    core_ptr,
		    type,
		    time_start,
		    time_end,
		    status,
		    source,
		    target,
		    comments
		);
	    }
	}
	/* Install MIME Type */
	else if(!g_strcasecmp(op, "install_mimetype"))
	{
	    /* Arguments:
	     *
	     * <class> <type>
	     * [value] [description]
	     * [handler]
	     * [icon_small_std_file] [icon_small_sel_file]
	     *  [icon_small_ext_file] [icon_small_hid_file]
	     * [icon_medium_std_file] [icon_medium_sel_file]
             *  [icon_medium_ext_file] [icon_medium_hid_file]
	     * [icon_large_std_file] [icon_large_sel_file]
	     *  [icon_large_ext_file] [icon_large_hid_file]
	     * [command_name] [command] [...]
	     */
	    if(argc >= 3)
	    {
		GtkWidget *toplevel = NULL;
		edv_mimetype_listwin_struct *lw = core_ptr->mimetype_listwin;
		edv_mimetype_struct *m = EDVMimeTypeNew(
		    ATOI(argv[1]),			/* Class */
		    (argc > 3) ? argv[3] : NULL,	/* Value */
		    argv[2],				/* Type */
		    (argc > 4) ? argv[4] : NULL		/* Description */
		);

		/* Handler (arg 5) */
		m->handler = (argc > 5) ?
		    ATOI(argv[5]) : EDV_MIMETYPE_HANDLER_COMMAND;

		/* Parse icon files (args 6 to 17) */
		if(argc > 17)
		{
		    m->small_icon_file[
			EDV_MIMETYPE_ICON_STATE_STANDARD
		    ] = STRDUP(argv[6]);
		    m->small_icon_file[
			EDV_MIMETYPE_ICON_STATE_SELECTED
		    ] = STRDUP(argv[7]);
		    m->small_icon_file[
			EDV_MIMETYPE_ICON_STATE_EXTENDED
		    ] = STRDUP(argv[8]);
                    m->small_icon_file[
                        EDV_MIMETYPE_ICON_STATE_HIDDEN
                    ] = STRDUP(argv[9]);

		    m->medium_icon_file[
			EDV_MIMETYPE_ICON_STATE_STANDARD
		    ] = STRDUP(argv[10]);
		    m->medium_icon_file[
			EDV_MIMETYPE_ICON_STATE_SELECTED
		    ] = STRDUP(argv[11]);
		    m->medium_icon_file[
			EDV_MIMETYPE_ICON_STATE_EXTENDED
		    ] = STRDUP(argv[12]);
                    m->medium_icon_file[
                        EDV_MIMETYPE_ICON_STATE_HIDDEN
                    ] = STRDUP(argv[13]);

		    m->large_icon_file[
			EDV_MIMETYPE_ICON_STATE_STANDARD
		    ] = STRDUP(argv[14]);
		    m->large_icon_file[
			EDV_MIMETYPE_ICON_STATE_SELECTED
		    ] = STRDUP(argv[15]);
		    m->large_icon_file[
			EDV_MIMETYPE_ICON_STATE_EXTENDED
		    ] = STRDUP(argv[16]);
                    m->large_icon_file[ 
                        EDV_MIMETYPE_ICON_STATE_HIDDEN
                    ] = STRDUP(argv[17]);
		}

		/* Parse commands (args 18 to ...) */
		if(argc > 18)
		{
		    gint i = 18, n;
		    while(i < argc)
		    {
			n = MAX(m->total_commands, 0);
			m->total_commands = n + 1;
			m->command_name = (gchar **)g_realloc(
			    m->command_name,
			    m->total_commands * sizeof(gchar *)
			);
			m->command = (gchar **)g_realloc(
			    m->command,
			    m->total_commands * sizeof(gchar *)
			);

			m->command_name[n] = (argc > i) ?
			    STRDUP(argv[i]) : NULL;
			i++;
			m->command[n] = (argc > i) ?
			    STRDUP(argv[i]) : NULL;
			i++;
		    }
		}

		if(EDVMimeTypesListWinIsMapped(lw))
		    toplevel = lw->toplevel;

		/* Install the MIME Type
		 *
		 * The MIME Type passed to this function will be
		 * installed (transfered) and should not be referenced
		 * again
		 */
		EDVMimeTypeInstall(
		    core_ptr,
		    m,			/* MIME Type */
		    -1,			/* Position */
		    TRUE,		/* Interactive */
		    toplevel		/* Toplevel */
		);
	    }
	}

	/* Help */
	else if(!g_strcasecmp(op, "help"))
	{
	    const gchar *subject = (argc > 1) ? argv[1] : NULL;
	    EDVHelp(
		core_ptr,
		subject,		/* Subject */
	        NULL			/* Toplevel */
	    );
	}
	/* About */
	else if(!g_strcasecmp(op, "about"))
	{
	    const gchar *page_name = (argc > 1) ? argv[1] : NULL;
	    EDVAboutPage(
		core_ptr,
		page_name,	/* Page Name */
		NULL		/* Toplevel */
	    );
	}
	/* All else display error message */
	else
	{
	    gchar *buf = g_strdup_printf(
"Unsupported InterPS command:\n\
\n\
    %s\n\
\n\
Was received by this application.",
		op
	    );
	    EDVPlaySoundWarning(core_ptr);
	    EDVMessageWarning(
"InterPS Command Warning",
buf,
"InterPS (Inter Process) commands are sent between different\n\
processes of this same application to ensure that only one copy of\n\
this application's process exists per user.\n\
\n\
If you are writing a program to send InterPS to this application,\n\
then you should contact the authors for assistance, otherwise you\n\
should regard this message as an internal programming error.\n",
		NULL
	    );
	    g_free(buf);
	}

	/* Delete arguments */
	strlistfree(argv, argc);
}


/*
 *      Processes the given InterPS commands.
 */
void EDVInterPSOPProcessCommands(
	edv_core_struct *core_ptr,
	gchar **cmdv, gint cmdc
)
{
	gint i;

	if(core_ptr == NULL)
	    return;

	for(i = 0; i < cmdc; i++)
	    EDVInterPSOPProcessCommand(core_ptr, cmdv[i]);
}
