#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <gtk/gtk.h>
#include <unistd.h>

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

#include "guiutils.h"
#include "cdialog.h"

#include "editorfio.h"
#include "manedit.h"
#include "maneditcb.h"
#include "maneditop.h"
#include "aboutdialog.h"


#include "images/icon_about_20x20.xpm"


GtkWidget *MEditCreateHelpMenu(
	medit_core_struct *core_ptr, GtkAccelGroup *accel_group
);
gchar *MEditCreateTempDir(medit_core_struct *core_ptr);
gint MEditExecuteFmtBlock(
	medit_core_struct *core_ptr,
	const gchar *cmd,
	const gchar *filename,
	const gchar *options,
	const gchar *stdout_path,
	const gchar *stderr_path
);
gint MEditDialogFromFile(
	medit_core_struct *core_ptr,
	const gchar *path
);
gint MEditDoEmergencySave(medit_core_struct *core_ptr);
gint MEditDoCrashRecovery(medit_core_struct *core_ptr);


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

#define ISCR(c)		(((c) == '\n') || ((c) == '\r'))


/*
 *	Creates the help menu, returns the menu widget.
 *
 *	Calling function needs to call GUIMenuAddToMenuBar()
 *	to add this menu to the menu bar.
 */
GtkWidget *MEditCreateHelpMenu(
	medit_core_struct *core_ptr, GtkAccelGroup *accel_group
)
{
	guint accel_key, accel_mods;
	gpointer client_data = core_ptr;
	guint8 **icon;
	const gchar *label = NULL;
	GtkWidget *w, *menu;
	void (*func_cb)(GtkWidget *w, gpointer);

	if(core_ptr == NULL)
	    return(NULL);

#define DO_ADD_MENU_ITEM_LABEL	{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_LABEL, accel_group,	\
  icon, label, accel_key, accel_mods, NULL,	\
  client_data, func_cb				\
 );						\
}

#define DO_ADD_MENU_ITEM_SUBMENU	{	\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_SUBMENU, accel_group, \
  icon, label, accel_key, accel_mods, NULL,	\
  client_data, func_cb				\
 );						\
 if(w != NULL)					\
  GUIMenuItemSetSubMenu(w, submenu);		\
}

#define DO_ADD_MENU_ITEM_CHECK	{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_CHECK, accel_group,	\
  icon, label, accel_key, accel_mods, NULL,	\
  client_data, func_cb				\
 );						\
}

#define DO_ADD_MENU_SEP		{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL,	\
  NULL, NULL, 0, 0, NULL,			\
  NULL, NULL					\
 );						\
}

	/* Create help menu */
	menu = GUIMenuCreate();
	if(menu != NULL)
	{
	    icon = NULL;
	    label = "Manual Page...";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = MEditHelpManualCB;
	    DO_ADD_MENU_ITEM_LABEL

	    DO_ADD_MENU_SEP

	    icon = NULL;
	    label = "How To Write ManPages...";
	    accel_key = 0; 
	    accel_mods = 0;
	    func_cb = MEditHelpManPageProceduresCB;
	    DO_ADD_MENU_ITEM_LABEL

	    icon = NULL;
	    label = "ManPage XML Format Reference...";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = MEditHelpManPageXMLRefCB;
	    DO_ADD_MENU_ITEM_LABEL

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_about_20x20_xpm;
	    label = "About...";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = MEditAboutDialogMapCB;
	    DO_ADD_MENU_ITEM_LABEL
	}

#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_ITEM_SUBMENU
#undef DO_ADD_MENU_ITEM_CHECK
#undef DO_ADD_MENU_SEP

	return(menu);
}

/*
 *	Creates a tempory directory for this program as needed and
 *	returns a dynamically allocated string containing the tempory
 *	directory.
 *
 *	If tempory directory already exists then it will not be created
 *	but everything else will run the same way, returning the pointer
 *	to an allocated string containing the tempory directory.
 *
 *	The returned pointer needs to be deleted by the calling
 *	function.
 */
gchar *MEditCreateTempDir(medit_core_struct *core_ptr)
{
	const gchar *s;
	gchar *tmp_dir_rtn;

	/* Get pointer to pref window, may be NULL */
	pref_struct *pref = core_ptr->pref;

	/* Allocate and reset tempory directory return to empty */
	tmp_dir_rtn = STRDUP("");

	/* Get tempory directory prefix */
	s = PrefParmGetValueP(
	    pref, MEDIT_PREF_PARM_LOCATIONS_TMP_DIR
	);
	if(s == NULL)
	    s = g_getenv("TMPDIR");
	if(s == NULL)
#if defined(P_tmpdir)
	    s = P_tmpdir;
#elif defined(_WIN32)
	    s = "C:\\TEMP";
#else
	    s = "/tmp";
#endif

	/* Cat tempory dir prefix */
	tmp_dir_rtn = strcatalloc(tmp_dir_rtn, s);

	/* Append deliminator to tmp_dir_rtn as needed */
	if(tmp_dir_rtn != NULL)
	{
	    const gint len = strlen(tmp_dir_rtn);
	    if((len > 0) ?
		(tmp_dir_rtn[len - 1] != G_DIR_SEPARATOR) : FALSE
	    )
		tmp_dir_rtn = strcatalloc(tmp_dir_rtn, "/");
	}

	/* Cat program's name */
	tmp_dir_rtn = strcatalloc(tmp_dir_rtn, "manedit-");

	/* Get user's name */
	s = g_getenv("USERNAME");
	if(s == NULL)
	    s = g_getenv("USER");
	/* All else assume anonymous */
	if(s == NULL)
	    s = "anon";

	/* Cat user's name */
	tmp_dir_rtn = strcatalloc(tmp_dir_rtn, s);


	/* Value of tmp_dir_rtn should be set properly now or may be
	 * NULL.
	 */


	/* Got valid tmp_dir_rtn? */
	if(tmp_dir_rtn != NULL)
	{
	    struct stat stat_buf;


	    /* Check if tempory directory exists */
	    if(stat(tmp_dir_rtn, &stat_buf))
	    {
		/* Not exist, so need to create a new one */
		rmkdir(
		    tmp_dir_rtn,
		    S_IRUSR | S_IWUSR | S_IXUSR |
		    S_IRGRP | S_IXGRP |
		    S_IROTH | S_IXOTH
		);
	    }
	}

	return(tmp_dir_rtn);
}


/*
 *	Formats the command cmd and executes it in blocking mode,
 *	outputting to stdout_path and stderr_path if any are not NULL.
 *
 *	Any occurance of `%f' in the given cmd will be replaced with
 *	the given filename. Any occurance of `%p' in the given cmd will
 *	be replaced with options. If filename or options are NULL then
 *	the replaced will be an empty string.
 *
 *	Returns non-zero on failure or error.
 */
gint MEditExecuteFmtBlock(
	medit_core_struct *core_ptr,
	const gchar *cmd,
	const gchar *filename,
	const gchar *options,
	const gchar *stdout_path,
	const gchar *stderr_path
)
{
	pid_t pid;
	gint fn_cnt, fn_value_len, opt_cnt, opt_value_len;
	const gchar *s;
	const gchar *fn_token = "%f";
	gint fn_token_len;
	const gchar *opt_token = "%p";
	gint opt_token_len;
	gint buf_len;
	gchar *buf;


	if(core_ptr == NULL)
	    return(-1);

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

	/* Count number of file name subs */
	fn_cnt = 0;
	fn_token_len = strlen(fn_token);
	fn_value_len = ((filename == NULL) ? 0 : strlen(filename));
	s = (const gchar *)strstr(cmd, fn_token);
	while(s != NULL)
	{
	    fn_cnt++;
	    s = (const gchar *)strstr(
		s + fn_token_len,
		fn_token
	    );
	}

	/* Count number of options subs */
	opt_cnt = 0;
	opt_token_len = strlen(opt_token);
	opt_value_len = ((options == NULL) ? 0 : strlen(options));
	s = (const gchar *)strstr(cmd, opt_token);
	while(s != NULL)
	{
	    opt_cnt++;
	    s = (const gchar *)strstr(
		s + opt_token_len,
		opt_token  
	    );
	}


	/* ********************************************************** */

	/* Allocate execution command buffer buf */
	buf_len = strlen(cmd) +
	    (MAX(fn_value_len - fn_token_len, fn_token_len) * fn_cnt) +
	    (MAX(opt_value_len - opt_token_len, opt_token_len) * opt_cnt);
	buf = (gchar *)g_malloc((buf_len + 1) * sizeof(char));
	if(buf == NULL)
	    return(-1);

	/* Copy given command format to allocated command buffer */
	strcpy(buf, cmd);

	/* Make substitutions */
	substr(buf, fn_token, (filename == NULL) ? "" : filename);
	substr(buf, opt_token, (options == NULL) ? "" : options);

	/* Execute command buffer */
	pid = ExecBAOE(buf, stdout_path, stderr_path);

	/* Delete execute buffer */
	g_free(buf);

	return((pid == 0) ? -1 : 0);
}


/*
 *	Maps the confirmation dialog with the message from the
 *	specified path on file. The dialog is only mapped if the given
 *	path exists and has a file size greater than 0 bytes.
 *
 *	Returns one of CDIALOG_RESPONSE_*.
 *
 *	Can return CDIALOG_RESPONSE_NOT_AVAILABLE if path does not
 *	exist or the file size is 0. Also will return
 *	CDIALOG_RESPONSE_NOT_AVAILABLE on error.
 */
gint MEditDialogFromFile(
	medit_core_struct *core_ptr,
	const gchar *path 
)
{
	gint column, max_columns;
	gint row, max_rows;
	gchar *buf;
	gint c, buf_pos, buf_len;

	FILE *fp;
	gint status = CDIALOG_RESPONSE_NOT_AVAILABLE;
	struct stat stat_buf;


	if((core_ptr == NULL) || (path == NULL))
	    return(status);

	/* Confirmation dialog already in query mode? */
	if(CDialogIsQuery())
	    return(status);

	/* Check if file exists */
	if(stat(path, &stat_buf))
	{
	    /* File does not exist */
	    return(status);
	}
	if(stat_buf.st_size <= 0)
	{
	    /* File size is 0 bytes */
	    return(status);
	}

	/* Open file */
	fp = FOpen(path, "rb");
	if(fp == NULL)
	{
	    /* Cannot open file */
	    return(status);
	}

	/* Begin reading file */
	max_columns = 80;
	column = 0;

	max_rows = 40;
	row = 0;

	buf = NULL;
	buf_pos = 0;
	buf_len = 0;

	while(row < max_rows)
	{
	    c = fgetc(fp);
	    if(c == EOF)
		break;

	    /* Is new line character? */
	    if(ISCR(c))
	    {
		row++;
		column = 0;
	    }
	    else
	    {
		column++;
	    }

	    /* Increase buffer allocation as needed */
	    if(buf_pos >= buf_len)
	    {
		buf_len = buf_pos + 8;
		buf = (gchar *)realloc(buf, (buf_len + 1) * sizeof(char));
		if(buf == NULL)
		{
		    buf_len = 0;
		    buf_pos = 0;
		    break;
		}
	    }

	    /* Set new character and increment buffer position */
	    buf[buf_pos] = (char)c;
	    buf_pos++;

	    /* Has column exceeded max_columns? */
	    if(column >= max_columns)
	    {
		/* Increase buffer allocation as needed */
		if(buf_pos >= buf_len)
		{
		    buf_len = buf_pos + 8;
		    buf = (gchar *)realloc(buf, (buf_len + 1) * sizeof(char));
		    if(buf == NULL)
		    {
			buf_len = 0;
			buf_pos = 0;
			break;
		    }
		}

		/* Set new character and increment buffer position */
		buf[buf_pos] = '\n';
		buf_pos++;

		/* Reset column and increment row */
		column = 0;
		row++;
	    }
	}

	/* Increase buffer allocation as needed */
	if(buf_pos >= buf_len)
	{
	    buf_len = buf_pos + 8;
	    buf = (gchar *)realloc(buf, (buf_len + 1) * sizeof(char));
	    if(buf == NULL)
	    {
		buf_len = 0;
		buf_pos = 0;
	    }
	}
	/* Add null terminating byte if possible */
	if(buf_pos < buf_len)
	    buf[buf_pos] = '\0';



	/* Close file */
	fclose(fp);
	fp = NULL;


	status = CDialogGetResponse(
	    "Subsystems Message",
	    buf,
	    NULL,
	    CDIALOG_ICON_WARNING,
	    CDIALOG_BTNFLAG_OK,
	    CDIALOG_BTNFLAG_OK
	);


	/* Delete buffer */
	g_free(buf);
	buf_len = 0;
	buf_pos = 0;

	return(status);
}

/*
 *	Procedure to perform an emergency save on all resources on
 *	the given core structure.
 *
 *	This function is often called right after a segmentation fault
 *	or other extreme critical error.
 *
 *	Returns 0 on success or -1 on general error.
 *
 *	Other returns aare as follows:
 *
 *	-2 cannot create tempory directory.
 */
gint MEditDoEmergencySave(medit_core_struct *core_ptr)
{
	gint i;
	editor_struct *editor;
	editor_item_struct *item;
	GtkCTreeNode *branch;
	GtkCTreeRow *branch_row;
	gchar *prog_tmp_path;


	if(core_ptr == NULL)
	    return(-1);

	/* Get program tempory directory */
	prog_tmp_path = MEditCreateTempDir(core_ptr);
	if(prog_tmp_path == NULL)
	    return(-2);

	/* Go through each editor on the core structure, saving each
	 * loaded manual page to a tempory file.
	 */
	for(i = 0; i < core_ptr->total_editors; i++)
	{
	    editor = core_ptr->editor[i];
	    if(editor == NULL)
		continue;

	    /* Get pointer to first branch on editor's layout ctree */
	    branch = EditorItemGetFirstToplevel(editor);

	    /* Begin saving each manual page loaded on the editor */
	    while(branch != NULL)
	    {
		/* Get pointer to item data */
		item = EditorBranchGetData(
		    (GtkCTree *)editor->layout_ctree,
		    branch
		);
		if(item != NULL)
		{
		    /* Got item, assume it is of type EditorItemTypeFile */

		    /* Change the file name full on this loaded manual
		     * page to the path specified by tmp_dir
		     */
		    g_free(item->full_path);
		    item->full_path = STRDUP(PrefixPaths(
			prog_tmp_path, "emergencyXXXXXX"
		    ));
		    mkstemp(item->full_path);

		    /* Save this branch */
		    EditorFileSave(editor, branch);
		}

		/* Get next branch */
		branch_row = GTK_CTREE_ROW(branch);
		branch = ((branch_row == NULL) ? NULL : branch_row->sibling);
	    }
	}

	/* Delete tempory directory */
	g_free(prog_tmp_path);
	prog_tmp_path = NULL;

	return(0);
}


/*
 *	Procedure to check if there was a `crash' in a previous run
 *	of this program. This function is designed to be called just after
 *	initialization.
 *
 *	The program's tempory directory will be checked for any existing
 *	files (with certain prefixes in their names) and prompt the
 *	user to load them in the current editor. These existing files may
 *	have been writteb by a previous call by a previous run of this
 *	program of function MEditDoEmergencySave().
 *
 *	If any only if the user responds with a `no' (to discard the files
 *	saved by an emergency save procedure) will the files be
 *	unlink()ed. If the user responds with a `yes' then those files
 *	will be loaded first then unlink()ed.
 *
 *	If there are no files found then this function performs no
 *	operation.
 */
gint MEditDoCrashRecovery(medit_core_struct *core_ptr)
{
	gboolean yes_to_all = FALSE;
	gint i, status;
	gchar **path, *path_ptr;
	gchar *prog_tmp_path;
	editor_struct *editor;


	if(core_ptr == NULL)
	    return(-1);

	/* Get first editor from core structure, it must be valid and
	 * initialized!
	 */
	i = 0;
	editor = ((i < core_ptr->total_editors) ?
	    core_ptr->editor[i] : NULL
	);
	if(editor == NULL)
	    return(-1);

	if(!editor->initialized)
	    return(-1);


	/* Get program tempory directory */
	prog_tmp_path = MEditCreateTempDir(core_ptr);
	if(prog_tmp_path == NULL)
	    return(-2);

	/* Get listing of objects in program's tempory directory */
	path = GetDirEntNames(prog_tmp_path);

	/* Check for any contents in directory, itterate through paths */
	i = 0;
	while(path[i] != NULL)
	{
	    path_ptr = path[i];
	    if(strcmp(path_ptr, ".") &&
	       strcmp(path_ptr, "..")
	    )
	    {
		gchar *filename = STRDUP(
		    PrefixPaths(prog_tmp_path, path_ptr)
		);
		if((filename != NULL) && !CDialogIsQuery())
		{
		    gboolean can_remove = FALSE;
		    gchar *s = g_strdup_printf(
"Found a file saved from an emergency save procedure:\n\
`%s'\n\n\
Do you wish to load this file? If you say `yes' then\n\
the file will be loaded otherwise it will be removed.\n\
\n\
If you say `yes', then remember to use file->save as to\n\
save the file under a different name before exiting!",
			    filename
		    );

		    if(yes_to_all)
			status = CDIALOG_RESPONSE_YES;
		    else
			status = CDialogGetResponse(
"Recover File?",
			    s,
"You are being asked if you want to load a file\n\
that was saved during an emergency save procedure\n\
from the last time you ran this program. This\n\
file may have been backed up because the program\n\
had crashed. If you say `yes' then the file will\n\
be loaded, if you say `no' then the file will be\n\
removed. If you are unsure say `yes' then save\n\
the loaded file to a different file name using\n\
file->save as.",
			    CDIALOG_ICON_WARNING,
			    CDIALOG_BTNFLAG_YES |
			    CDIALOG_BTNFLAG_YES_TO_ALL |
			    CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_HELP,
			    CDIALOG_BTNFLAG_YES
			);
		    g_free(s);

		    /* Handle response */
		    switch(status)
		    {
		      case CDIALOG_RESPONSE_YES_TO_ALL:
			yes_to_all = TRUE;
		      case CDIALOG_RESPONSE_YES:
		      case CDIALOG_RESPONSE_NOT_AVAILABLE:
		      case CDIALOG_RESPONSE_OK:
			if(EditorFileLoad(
			    editor, filename, NULL, FALSE
			))
			    can_remove = FALSE;
			else
			    can_remove = TRUE;
			break;

		      case CDIALOG_RESPONSE_NO:
		      case CDIALOG_RESPONSE_CANCEL:
			can_remove = TRUE;
			break;
		    }

		    /* Can remove filename? */
		    if(can_remove)
			unlink(filename);
		}

		g_free(filename);
	    }

	    /* Delete path and go to next path */
	    g_free(path[i]);
	    i++;
	}

	g_free(path);
	g_free(prog_tmp_path);

	return(0);
}
