#include <string.h>
#include <glib.h>
#include "../../include/string.h"
#include "../../include/disk.h"
#include "cfg.h"
#include "../cfgfio.h"
#include "../edvinterps.h"
#include "edvdevices.h"
#include "edvdevicesfio.h"
#include "edvmimetypes.h"
#include "edvmimetypesfio.h"
#include "edvcontext.h"
#include "edvnotify.h"
#include "edvcfglist.h"
#include "config.h"


static gint EDVContextDeviceAppend(
	edv_context_struct *ctx, edv_device_struct *d
);
static gint EDVContextMimeTypeAppend(
	edv_context_struct *ctx, edv_mimetype_struct *m
);

edv_context_struct *EDVContextNew(void);
void EDVContextLoadConfigurationFile(
	edv_context_struct *ctx,
	const gchar *path		/* Can be NULL for default file */
);
static void EDVContextLoadDevices(edv_context_struct *ctx);
static void EDVContextLoadMimeTypes(edv_context_struct *ctx);
gint EDVContextCommandsPending(edv_context_struct *ctx);
void EDVContextQueueCommand(
	edv_context_struct *ctx, const gchar *cmd
);
void EDVContextFlush(edv_context_struct *ctx);
void EDVContextWait(edv_context_struct *ctx);
void EDVContextDelete(edv_context_struct *ctx);


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


/*
 *	Appends the Device to the Context.
 *
 *	The specified Device is transfered and must not be referenced
 *	again after this call.
 */
static gint EDVContextDeviceAppend(
	edv_context_struct *ctx, edv_device_struct *d
)
{
	gint i = MAX(ctx->total_devices, 0);
	ctx->total_devices = i + 1;         
	ctx->device = (edv_device_struct **)g_realloc(
	    ctx->device,
	    ctx->total_devices * sizeof(edv_device_struct *)
	);
	if(ctx->device == NULL)
	{
	    ctx->total_devices = 0;
	    EDVDeviceDelete(d);
	    return(-1);
	}
	 
	ctx->device[i] = d;

	return(i);
}

/*
 *	Appends the MIME Type to the Context.
 *
 *	The specified MIME Type is transfered and must not be referenced
 *	again after this call.
 */
static gint EDVContextMimeTypeAppend(
	edv_context_struct *ctx, edv_mimetype_struct *m
)
{
	gint i = MAX(ctx->total_mimetypes, 0);
	ctx->total_mimetypes = i + 1;
	ctx->mimetype = (edv_mimetype_struct **)g_realloc(
	    ctx->mimetype,
	    ctx->total_mimetypes * sizeof(edv_mimetype_struct *)
	);
	if(ctx->mimetype == NULL)
	{
	    ctx->total_mimetypes = 0;
	    EDVMimeTypeDelete(m);
	    return(-1);
	}

	ctx->mimetype[i] = m;

	return(i);
}


/*
 *	Allocates a new Endeavour Context.
 */
edv_context_struct *EDVContextNew(void)
{
	edv_context_struct *ctx = EDV_CONTEXT(g_malloc0(
	    sizeof(edv_context_struct)
	));
	if(ctx == NULL)
	    return(NULL);

	ctx->cfg_list = NULL;

	ctx->mimetype = NULL;
	ctx->total_mimetypes = 0;

	ctx->device = NULL;
	ctx->total_devices = 0;

	ctx->queued_command = NULL;
	ctx->total_queued_commands = 0;

	ctx->recycled_index_file = NULL;
	ctx->prog_file = NULL;
	ctx->prog_full_path = NULL;

	return(ctx);
}

/*
 *	Loads the configuration from the specified Endeavour 
 *	configuration file.
 *
 *	If path is NULL then the default configuration file found in the
 *	user's home directory will be used:
 *
 *		$HOME/.endeavour2/endeavour2.ini
 *
 *	Additional configuration file loading and set up will also be
 *	performed during this call.
 *
 *      This should be called right after EDVContextNew() to ensure
 *      that the context ctx is set up properly before passing to any
 *	other function.
 */
void EDVContextLoadConfigurationFile(
	edv_context_struct *ctx,
	const gchar *path		/* Can be NULL for default file */
)
{
	gchar *cfg_file;
	const gchar *prog_loc[] = EDV_PROGRAM_LOCATIONS;
	const cfg_item_struct src_cfg_list[] = EDV_CONFIGURATION_LIST;


	if(ctx == NULL)
	    return;

	/* Get Endeavour program location */
	if(STRISEMPTY(ctx->prog_full_path))
	{
	    gint i;
	    struct stat stat_buf;

	    /* Search through list of possible program locations */
	    for(i = 0; prog_loc[i] != NULL; i++)
	    {
		/* Exists at this location? */
		if(!stat(prog_loc[i], &stat_buf))
		{
		    const gchar *s = prog_loc[i];

		    g_free(ctx->prog_full_path);
		    ctx->prog_full_path = STRDUP(s);
		    g_free(ctx->prog_file);
		    ctx->prog_file = STRDUP(strrchr(s, G_DIR_SEPARATOR));
		    break;
		}
	    }

	    /* Unable to find program location? */
	    if(STRISEMPTY(ctx->prog_full_path))
	    {
		g_printerr(
"EDVContextLoadConfigurationFile(): Warning:\
 Unable to find the %s program in the following locations:\n",
		    PROG_NAME
		);
		for(i = 0; prog_loc[i] != NULL; i++)
		    g_printerr(
			"\t%s\n",
			prog_loc[i]
		    );
	    }
	}

	/* Copy source configuration list to the target configuration
	 * list on the context
	 */
	if(ctx->cfg_list == NULL)
	    ctx->cfg_list = CFGItemListCopyList(src_cfg_list);
	if(ctx->cfg_list == NULL)
	    return;

	/* Format configuration file location as cfg_file. Check if we
	 * should use the default configuration file path by checking if
	 * the given path is NULL
	 */
	if(path == NULL)
	{
	    /* Use default configuration file, located in the user's
	     * home directory:
	     *
	     * $HOME/EDV_DEF_LOCAL_DATA_DIR/EDV_DEF_CONFIG_FILE
	     */
	    const gchar *home = g_getenv("HOME");
	    cfg_file = g_strdup_printf(
		"%s%c%s%c%s",
		(home != NULL) ? home : "/", G_DIR_SEPARATOR,
		EDV_DEF_LOCAL_DATA_DIR, G_DIR_SEPARATOR,
		EDV_DEF_CONFIG_FILE
	    );
	}
	else
	{
	    /* Configuration file path given, copy it as cfg_file */
	    cfg_file = STRDUP(path);
	}

	/* Load configuration item values from the configuration file */
	CFGLoadFromFile(cfg_file, ctx->cfg_list);

	/* Update values on the context based on the newly loaded
	 * configuration item values from the configuration file
	 */
	if(ctx->cfg_list != NULL)
	{
	    const cfg_item_struct *cfg_list = ctx->cfg_list;
	    const gchar *s;
	    gint version_major, version_minor, version_release;

	    /* Check version obtained from the configuration file */
	    version_major = CFGItemListGetValueI(
		cfg_list, EDV_CFG_PARM_VERSION_MAJOR
	    );
	    version_minor = CFGItemListGetValueI(
		cfg_list, EDV_CFG_PARM_VERSION_MINOR
	    );
	    version_release = CFGItemListGetValueI(
		cfg_list, EDV_CFG_PARM_VERSION_RELEASE
	    );
	    if((version_major != PROG_VERSION_MAJOR) ||
	       (version_minor != PROG_VERSION_MINOR) ||
	       (version_release != PROG_VERSION_RELEASE)
	    )
	    {
#if 0
		g_printerr(
"%s: Warning: Version from configuration file %i.%i.%i differs from\
 %s library version %i.%i.%i.\n",
		    cfg_file,
		    version_major, version_minor, version_release,
		    PROG_NAME,
		    PROG_VERSION_MAJOR, PROG_VERSION_MINOR, PROG_VERSION_RELEASE
		);
#endif
	    }


	    /* Recycled objects index file */
	    s = CFGItemListGetValueS(
		cfg_list, EDV_CFG_PARM_FILE_RECYCLED_INDEX
	    );
	    g_free(ctx->recycled_index_file);
	    ctx->recycled_index_file = STRDUP(s);


/* Add support for additional members that need to be coppied here */
	}

	/* Load all Devices */
	EDVContextLoadDevices(ctx);

	/* Load all MIME Types */
	EDVContextLoadMimeTypes(ctx);


	g_free(cfg_file);
}

/*
 *	Called by EDVContextLoadConfigurationFile().
 *
 *	Loads local and global Devices.
 */
static void EDVContextLoadDevices(edv_context_struct *ctx)
{
	const gchar *s;

	/* Begin loading Devices from file */

	/* Load local Devices from file */   
	s = CFGItemListGetValueS(
	    ctx->cfg_list, EDV_CFG_PARM_FILE_DEVICES
	);
	if(!STRISEMPTY(s))
	{
	    gint i, total;
	    edv_device_struct **list = EDVDevicesListLoadFromFile(
		s, &total
	    );
	    for(i = 0; i < total; i++)
		EDVContextDeviceAppend(ctx, list[i]);
	    g_free(list);
	}

#if 0
	/* Load global Devices from file */
	s = CFGItemListGetValueS(
	    ctx->cfg_list, EDV_CFG_PARM_FILE_DEVICES_GLOBAL
	);
	if(!STRISEMPTY(s))
	{
	    gint i, total;
	    edv_device_struct **list = EDVDevicesListLoadFromFile(
		s, &total
	    );  
	    for(i = 0; i < total; i++)
		EDVContextDeviceAppend(ctx, list[i]);
	    g_free(list);
	}
#endif
}

/*
 *	Called by EDVContextLoadConfigurationFile().
 *
 *	Loads default/system, local, and global MIME Types.
 */
static void EDVContextLoadMimeTypes(edv_context_struct *ctx)
{
	const gchar *s;
	gchar *icons_dir_global;
	edv_mimetype_struct *m;

#define SET_MIMETYPE_ICON_FILES(state,sfile,mfile,lfile) { \
 if(!STRISEMPTY(icons_dir_global)) {		\
						\
 g_free(m->small_icon_file[(state)]);		\
 m->small_icon_file[(state)] = STRDUP(		\
  PrefixPaths(icons_dir_global, (sfile))	\
 );						\
						\
 g_free(m->medium_icon_file[(state)]);		\
 m->medium_icon_file[(state)] = STRDUP(		\
  PrefixPaths(icons_dir_global, (mfile))	\
 );						\
						\
 g_free(m->large_icon_file[(state)]);		\
 m->large_icon_file[(state)] = STRDUP(		\
  PrefixPaths(icons_dir_global, (lfile))	\
 );						\
} }

	/* Get global icons directory */
	s = CFGItemListGetValueS(
	    ctx->cfg_list, EDV_CFG_PARM_DIR_GLOBAL
	);
	icons_dir_global = STRDUP(PrefixPaths(s, "icons"));


	/* Begin loading system MIME Types */

	/* File */
	m = EDVMimeTypeNew(
	    EDV_MIMETYPE_CLASS_SYSTEM,
	    NULL,
	    EDV_MIMETYPE_STR_FILE,
	    NULL
	);
	SET_MIMETYPE_ICON_FILES(
	    EDV_MIMETYPE_ICON_STATE_STANDARD,
	    "icon_file_20x20.xpm",
	    "icon_file_32x32.xpm",
	    "icon_file_48x48.xpm"
	)
	EDVContextMimeTypeAppend(ctx, m);

	/* Executable */
	m = EDVMimeTypeNew(
	    EDV_MIMETYPE_CLASS_SYSTEM,
	    NULL,
	    EDV_MIMETYPE_STR_FILE_EXECUTABLE,
	    NULL
	);
	SET_MIMETYPE_ICON_FILES(
	    EDV_MIMETYPE_ICON_STATE_STANDARD,
	    "icon_executable_20x20.xpm",
	    "icon_executable_32x32.xpm",
	    "icon_executable_48x48.xpm"
	)
	EDVContextMimeTypeAppend(ctx, m);

	/* Directory */
	m = EDVMimeTypeNew(
	    EDV_MIMETYPE_CLASS_SYSTEM,
	    NULL,
	    EDV_MIMETYPE_STR_DIRECTORY,
	    NULL
	);
	SET_MIMETYPE_ICON_FILES(
	    EDV_MIMETYPE_ICON_STATE_STANDARD,
	    "icon_folder_closed_20x20.xpm",
	    "icon_folder_closed_32x32.xpm",
	    "icon_folder_closed_48x48.xpm"
	)
	SET_MIMETYPE_ICON_FILES(
	    EDV_MIMETYPE_ICON_STATE_SELECTED,
	    "icon_folder_opened_20x20.xpm",
	    "icon_folder_opened_32x32.xpm",
	    "icon_folder_opened_48x48.xpm"
	)
	EDVContextMimeTypeAppend(ctx, m);

	/* Link */
	m = EDVMimeTypeNew(
	    EDV_MIMETYPE_CLASS_SYSTEM,
	    NULL,
	    EDV_MIMETYPE_STR_LINK,
	    NULL
	);
	SET_MIMETYPE_ICON_FILES(
	    EDV_MIMETYPE_ICON_STATE_STANDARD,
	    "icon_link2_20x20.xpm",
	    "icon_link2_32x32.xpm",
	    "icon_link2_48x48.xpm"
	)
	SET_MIMETYPE_ICON_FILES(
	    EDV_MIMETYPE_ICON_STATE_EXTENDED,
	    "icon_link_broken_20x20.xpm",
	    "icon_link2_32x32.xpm",
	    "icon_link2_48x48.xpm"
	)
	EDVContextMimeTypeAppend(ctx, m);

	/* Device Block */
	m = EDVMimeTypeNew(
	    EDV_MIMETYPE_CLASS_SYSTEM,
	    NULL,
	    EDV_MIMETYPE_STR_DEVICE_BLOCK,
	    NULL
	);
	SET_MIMETYPE_ICON_FILES(
	    EDV_MIMETYPE_ICON_STATE_STANDARD,
	    "icon_device_block_20x20.xpm",
	    "icon_device_block_32x32.xpm",
	    "icon_device_block_48x48.xpm"
	)
	EDVContextMimeTypeAppend(ctx, m);

	/* Device Character */
	m = EDVMimeTypeNew(
	    EDV_MIMETYPE_CLASS_SYSTEM,
	    NULL,
	    EDV_MIMETYPE_STR_DEVICE_CHARACTER,
	    NULL
	);
	SET_MIMETYPE_ICON_FILES(
	    EDV_MIMETYPE_ICON_STATE_STANDARD,
	    "icon_device_character_20x20.xpm",
	    "icon_device_character_32x32.xpm",
	    "icon_device_character_48x48.xpm"
	)
	EDVContextMimeTypeAppend(ctx, m);

	/* FIFO Pipe */
	m = EDVMimeTypeNew(
	    EDV_MIMETYPE_CLASS_SYSTEM,
	    NULL,
	    EDV_MIMETYPE_STR_FIFO,
	    NULL
	);
	SET_MIMETYPE_ICON_FILES(
	    EDV_MIMETYPE_ICON_STATE_STANDARD,
	    "icon_pipe_20x20.xpm",
	    "icon_pipe_32x32.xpm",
	    "icon_pipe_48x48.xpm"
	)
	EDVContextMimeTypeAppend(ctx, m);

	/* Socket */
	m = EDVMimeTypeNew(
	    EDV_MIMETYPE_CLASS_SYSTEM,
	    NULL,
	    EDV_MIMETYPE_STR_SOCKET,
	    NULL
	);
	SET_MIMETYPE_ICON_FILES(
	    EDV_MIMETYPE_ICON_STATE_STANDARD,
	    "icon_socket_20x20.xpm",
	    "icon_socket_32x32.xpm",
	    "icon_socket_48x48.xpm"
	)
	EDVContextMimeTypeAppend(ctx, m);


	/* Begin loading MIME Types from file */

	/* Load local MIME Types from file */
	s = CFGItemListGetValueS(
	    ctx->cfg_list, EDV_CFG_PARM_FILE_MIME_TYPES
	);
	if(!STRISEMPTY(s))
	{
	    gint i, total;
	    edv_mimetype_struct **list = EDVMimeTypeListLoadFromFile(
		s, &total
	    );
	    for(i = 0; i < total; i++)
		EDVContextMimeTypeAppend(ctx, list[i]);
	    g_free(list);
	}

	/* Load global MIME Types from file */
	s = CFGItemListGetValueS(
	    ctx->cfg_list, EDV_CFG_PARM_FILE_MIME_TYPES_GLOBAL
	);
	if(!STRISEMPTY(s))
	{
	    gint i, total;
	    edv_mimetype_struct **list = EDVMimeTypeListLoadFromFile(
		s, &total
	    );
	    for(i = 0; i < total; i++)
		EDVContextMimeTypeAppend(ctx, list[i]);
	    g_free(list);
	}


	g_free(icons_dir_global);

#undef SET_MIMETYPE_ICON_FILES
}


/*
 *      Returns the number of queued commands on the context ctx.
 */
gint EDVContextCommandsPending(edv_context_struct *ctx)
{
	return((ctx != NULL) ? ctx->total_queued_commands : 0);
}

/*
 *	Appends the given cmd to the list of queued commands on the
 *	context ctx.
 *
 *	The command is queued and will not actually be sent until
 *	EDVContextFlush() is called.
 */
void EDVContextQueueCommand(
	edv_context_struct *ctx, const gchar *cmd
)
{
	gint i;

	if((ctx == NULL) || STRISEMPTY(cmd))
	    return;

	/* Allocate more pointers */
	i = MAX(ctx->total_queued_commands, 0);
	ctx->total_queued_commands = i + 1;
	ctx->queued_command = (gchar **)g_realloc(
	    ctx->queued_command,
	    ctx->total_queued_commands * sizeof(gchar *)
	);
	if(ctx->queued_command == NULL)
	{
	    ctx->total_queued_commands = 0;
	    return;
	}

	ctx->queued_command[i] = STRDUP(cmd);
}

/*
 *	Processes and flushes all pending operations and resources on
 *      the given context ctx.
 *
 *	All queued commands will be sent (regardless if Endeavour is
 *	running or not).
 *
 *      This call will not block/wait for Endeavour to acknowlage
 *      the request, for that use EDVContextWait();
 */
void EDVContextFlush(edv_context_struct *ctx)
{
	gint p;

	if(ctx == NULL)
	    return;

	/* Get InterPS lock link to find out the pid of the currently
	 * running Endeavour (if it is running)
	 *
	 * Note that we do not store the pid on the context ctx because
	 * Endeavour might exit and restart, creating a new lock link
	 * and have a different pid
	 */
	p = EDVInterPSGetLock(ctx->cfg_list);
	if(p > 0)
	{
	    /* Got pid and is running. */

	    /* Send out any queued commands */
	    EDVInterPSSendCommand(
		ctx->cfg_list, p,
		ctx->queued_command, ctx->total_queued_commands
	    );
	}

	/* Remove all queued commands (regardless if they were sent or
	 * not)
	 */
	strlistfree(ctx->queued_command, ctx->total_queued_commands);
	ctx->queued_command = NULL;
	ctx->total_queued_commands = 0;
}

/*
 *	Waits for a response from Endeavour (if it is running) to
 *	indicate that it has processed all the commands sent to it
 *	from a prior call to EDVContextFlush() (if any).
 *
 *	If Endeavour is not running then this call returns immediately.
 */
void EDVContextWait(edv_context_struct *ctx)
{
	cfg_item_struct *cfg_list;

	if(ctx == NULL)
	    return;

	cfg_list = ctx->cfg_list;

	while(
	    (EDVInterPSGetLock(cfg_list) > 0) ?
		EDVInterPSHaveCommand(cfg_list) : FALSE
	);
}

/*
 *	Deletes the Endeavour Context and all it's resources.
 *
 *	Any pending operations will not be performed and will be
 *	discarded.
 */
void EDVContextDelete(edv_context_struct *ctx)
{
	gint i;

	if(ctx == NULL)
	    return;

#define FREEP(p)	\
{ if((p) != NULL) { g_free(*(p)); *(p) = NULL; } }

	/* Delete any queued commands */
	strlistfree(ctx->queued_command, ctx->total_queued_commands);
	ctx->queued_command = NULL;
	ctx->total_queued_commands = 0;

	/* Delete other resources */
	FREEP(&ctx->recycled_index_file);
	FREEP(&ctx->prog_file);
	FREEP(&ctx->prog_full_path);

	/* Delete Devices list */
	for(i = 0; i < ctx->total_devices; i++)
	    EDVDeviceDelete(ctx->device[i]);
	g_free(ctx->device);
	ctx->device = NULL; 
	ctx->total_devices = 0;

	/* Delete MIME Types list */
	for(i = 0; i < ctx->total_mimetypes; i++)
	    EDVMimeTypeDelete(ctx->mimetype[i]);
	g_free(ctx->mimetype);
	ctx->mimetype = NULL;
	ctx->total_mimetypes = 0;

	/* Delete configuration list */
	CFGItemListDeleteList(ctx->cfg_list);
	ctx->cfg_list = NULL;

	/* Delete structure itself */
	FREEP(&ctx);

#undef FREEP
}
