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

#include "../include/fio.h"

#include "cfg.h"
#include "edvinterps.h"
#include "edvutils.h"
#include "edvcfglist.h"
#include "config.h"


/* InterPS Lock Link */
gint EDVInterPSGetLock(const cfg_item_struct *cfg_list);
void EDVInterPSMakeLock(
	const cfg_item_struct *cfg_list,
	gint p, gboolean force
);
void EDVInterPSRemoveLock(const cfg_item_struct *cfg_list);

/* InterPS Command */
gboolean EDVInterPSHaveCommand(const cfg_item_struct *cfg_list);
void EDVInterPSSendCommand(
	const cfg_item_struct *cfg_list,
	gint p, gchar **cmdv, gint cmdc
);
gchar **EDVInterPSGetCommand(
	const cfg_item_struct *cfg_list,
	gint *total
);
void EDVInterPSRemoveCommand(const cfg_item_struct *cfg_list);


#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 pid of the currently running process of Endeavour or
 *	0 if it is not running.
 *
 *	The lock link will be checked and the actual process will be
 *	checked to see if it is actually running.
 */
gint EDVInterPSGetLock(const cfg_item_struct *cfg_list)
{
	gint p;
	gchar num_str[40];

	const gchar *s = CFGItemListGetValueS(
	    cfg_list, EDV_CFG_PARM_DIR_LOCAL
	);
	gchar *lock_link = g_strdup_printf(
	    "%s%c%s",
	    (s != NULL) ? s : "/",
	    G_DIR_SEPARATOR,
	    EDV_INTERPS_LOCK_LINK
	);

	/* Get lock link value */
	gint bytes_read = (gint)readlink(
	    (const char *)lock_link, (char *)num_str, sizeof(num_str)
	);
	if(bytes_read <= 0)
	{
	    /* Lock link does not exist or error reading it */
	    g_free(lock_link);
	    return(0);
	}
	if(bytes_read >= sizeof(num_str))
	    num_str[sizeof(num_str) - 1] = '\0';
	else
	    num_str[bytes_read] = '\0';

	g_free(lock_link);	/* Don't need this anymore */

	/* Get pid from the link's destination and check if it is
	 * actually running
	 */
	p = ATOI(num_str);
	if(EDVProcessIsRunning(p))
	    return(p);
	else
	    return(0);
}

/*
 *	Creates a new InterPS lock link that refers to the specified
 *	pid.
 *
 *	The link will be tested to see if it already exists. If it does
 *	not exist or force is TRUE then a new lock will be created and
 *	refer to the specified pid.
 */
void EDVInterPSMakeLock(
	const cfg_item_struct *cfg_list,
	gint p, gboolean force
)
{
	gchar num_str[40];

	const gchar *s = CFGItemListGetValueS(
	    cfg_list, EDV_CFG_PARM_DIR_LOCAL
	);
	gchar *lock_link = g_strdup_printf(
	    "%s%c%s",
	    (s != NULL) ? s : "/",
	    G_DIR_SEPARATOR,
	    EDV_INTERPS_LOCK_LINK
	);
	/* Lock link already exists and not forcing? */
	if(!access((const char *)lock_link, F_OK) && !force)
	{
	    g_free(lock_link);
	    return;
	}

	/* Format the link destination, which is the pid of this
	 * process
	 */
	g_snprintf(
	    num_str, sizeof(num_str),
	    "%i",
	    p
	);

	/* (Re)create the lock link */
	unlink((const char *)lock_link);
	symlink((const char *)num_str, (const char *)lock_link);

	g_free(lock_link);
}

/*
 *	Removes the InterPS lock link.
 */
void EDVInterPSRemoveLock(const cfg_item_struct *cfg_list)
{
	const gchar *s = CFGItemListGetValueS(
	    cfg_list, EDV_CFG_PARM_DIR_LOCAL
	);
	gchar *lock_link = g_strdup_printf(
	    "%s%c%s",
	    (s != NULL) ? s : "/",
	    G_DIR_SEPARATOR,
	    EDV_INTERPS_LOCK_LINK
	);

	/* Remove lock link */
	unlink((const char *)lock_link);
	g_free(lock_link);
}


/*
 *	Checks if the InterPS command file exists.
 */
gboolean EDVInterPSHaveCommand(const cfg_item_struct *cfg_list)
{
	const gchar *s = CFGItemListGetValueS(
	    cfg_list, EDV_CFG_PARM_DIR_LOCAL
	);
	gchar *cmd_file = g_strdup_printf(
	    "%s%c%s",
	    (s != NULL) ? s : "/",
	    G_DIR_SEPARATOR,
	    EDV_INTERPS_CMD_FILE
	);

	/* Command file exists? */
	gint status = (gint)access((const char *)cmd_file, F_OK);

	g_free(cmd_file);

	return(status ? FALSE : TRUE);
}

/*
 *	Sends the given InterPS commands to the process specified by
 *	pid.
 *
 *	The given InterPS command is appended to the InterPS command
 *	file.
 *
 *	If the InterPS command file does not exist then it will be
 *	created.
 *
 *	A newline character will automatically be appended to the
 *	InterPS command.
 *
 *	Finally, a signal SIGUSR1 will be sent to the process specified
 *	by the pid p to notify that there are new InterPS commands in
 *	the InterPS command file.
 */
void EDVInterPSSendCommand(
	const cfg_item_struct *cfg_list,
	gint p, gchar **cmdv, gint cmdc
)
{
	const gchar *s;
	gint i;
	gchar *cmd_file;
	FILE *fp;

	if((p <= 0) || (cmdv == NULL) || (cmdc <= 0))
	    return;

	s = CFGItemListGetValueS(
	    cfg_list, EDV_CFG_PARM_DIR_LOCAL
	);
	cmd_file = g_strdup_printf(
	    "%s%c%s",
	    (s != NULL) ? s : "/",
	    G_DIR_SEPARATOR,
	    EDV_INTERPS_CMD_FILE
	);

	/* Open command file for writing */
	fp = FOpen((const char *)cmd_file, "ab");
	if(fp != NULL)
	{
	    /* Append commands */
	    for(i = 0; i < cmdc; i++)
	    {
		if(cmdv[i] != NULL)
		    fprintf(fp, "%s\n", (const char *)cmdv[i]);
	    }

	    /* Close file */
	    FClose(fp);
#ifdef SIGUSR1
	    /* Notify the running Endeavour process */
	    if(p > 0)
		kill((int)p, SIGUSR1);
#endif
	}

	g_free(cmd_file);
}

/*
 *	Gets all commands from the InterPS command file.
 *
 *	The returned list of strings and the pointer array must be
 *	deleted by the calling function.
 */
gchar **EDVInterPSGetCommand(
	const cfg_item_struct *cfg_list,
	gint *total
)
{
	const gchar *s;
	FILE *fp;
	gchar *cmd_file, *buf;
	gint buf_len;
	gchar **cmdv = NULL;
	struct stat stat_buf;

	if(total != NULL)
	    *total = 0;

	s = CFGItemListGetValueS(
	    cfg_list, EDV_CFG_PARM_DIR_LOCAL
	);
	cmd_file = g_strdup_printf(
	    "%s%c%s",
	    (s != NULL) ? s : "/",
	    G_DIR_SEPARATOR,
	    EDV_INTERPS_CMD_FILE
	);

	/* Open command file for reading */
	fp = FOpen((const char *)cmd_file, "rb");
	if(fp == NULL)
	{
	    g_free(cmd_file);
	    return(cmdv);
	}
	if(fstat(fileno(fp), &stat_buf))
	{
	    FClose(fp);
	    g_free(cmd_file);
	    return(cmdv);
	}

	/* Calculate the required buffer length and allocate buffer */
	buf_len = (gint)(stat_buf.st_size + 1);
	if(buf_len < 1)
	    buf_len = 1;
	buf = (gchar *)g_malloc(buf_len * sizeof(gchar));
	if(buf == NULL)
	{
	    FClose(fp);
	    g_free(cmd_file);
	    return(cmdv);
	}

	/* Load the all contents of the InterPS command file into
	 * the buffer and then close the InterPS command file
	 */
	if(buf_len > 1)
	    fread(buf, sizeof(gchar), (size_t)(buf_len - 1), fp);
	buf[buf_len - 1] = '\0';
	FClose(fp);

	/* Explode commands at newline deliminators */
	cmdv = g_strsplit(buf, "\n", -1);
	if(cmdv != NULL)
	{
	    gint i;
	    for(i = 0; cmdv[i] != NULL; i++);
	    if(total != NULL)
		*total = i;
	}

	g_free(buf);
	g_free(cmd_file);

	return(cmdv);
}

/*
 *	Removes the InterPS command file.
 */
void EDVInterPSRemoveCommand(const cfg_item_struct *cfg_list)
{
	const gchar *s = CFGItemListGetValueS(
	    cfg_list, EDV_CFG_PARM_DIR_LOCAL
	);
	gchar *cmd_file = g_strdup_printf(
	    "%s%c%s",
	    (s != NULL) ? s : "/",
	    G_DIR_SEPARATOR,
	    EDV_INTERPS_CMD_FILE
	);

	/* Remove command file */
	unlink((const char *)cmd_file);

	g_free(cmd_file);
}
