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

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

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

#include "viewer.h"
#include "viewerfio.h"

#include "manedit.h"
#include "maneditop.h"
#include "config.h"


static gint ViewerLoadProgressCB(glong cur, glong max, gpointer data);
gint ViewerLoadFile(
	viewer_struct *v, const gchar *filename, const gchar *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)


/*
 *	Viewer load progress callback.
 */
static gint ViewerLoadProgressCB(glong cur, glong max, void *data)
{
	gfloat percent;
	viewer_struct *v = VIEWER(data);
	if(v == NULL)
	    return(0);

	if(max > 0l)
	    percent = (gfloat)cur / (gfloat)max;
	else
	    percent = 1.0f;

	ViewerSetStatusProgress(v, percent);

	return(0);
}


/*
 *	Takes the given manual page format file specified by filename
 *	and converts it to groff output as a tempory file and loads
 *	that tempory file. The tempory file is then removed.
 *
 *	Returns non-zero on error.
 */
gint ViewerLoadFile(
	viewer_struct *v, const gchar *filename, const gchar *name
)
{
	gint status;
	FILE *fp;
	gchar *buf;
	gint buf_len;

	gchar *prog_tmp_dir;
	gchar *stdout_path;
	gchar *stderr_path;
	const gchar *converter_cmd;
	GtkAdjustment *vadj;

	medit_core_struct *core_ptr;
	struct stat stat_buf;


	if((v == NULL) || (filename == NULL))
	    return(-1);

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

	/* Already processing? */
	if(v->processing)
	    return(-1);

	/* Mark as processing */
	v->processing = TRUE;

	/* If given filename is an absolute path, then check if it
	 * exists. This is to allow the converter program to search for
	 * relative paths
	 */
	if(ISPATHABSOLUTE(filename))
	{
	    if(stat(filename, &stat_buf))
	    {
		v->processing = FALSE;
		return(-2);
	    }
	}

	core_ptr = (medit_core_struct *)v->core_ptr;
	if(core_ptr == NULL)
	{
	    v->processing = FALSE;
	    return(-1);
	}

	/* Get Manual Page to output converter command */
	converter_cmd = (const gchar *)PrefParmGetValueP(
	    core_ptr->pref,
	    MEDIT_PREF_PARM_CONVERTERS_MANPAGE_TO_OUTPUT
	);
	if((converter_cmd == NULL) ? 1 : ((*converter_cmd) == '\0'))
	{
	    v->processing = FALSE;

	    CDialogGetResponse(
"Command not defined!",
"Manual Page to output converter command is not\n\
defined. Cannot continue with loading of Manual\n\
Page.",
"The command to convert Manual Pages to readable\n\
output has not been defined, therefore the loading\n\
of the Manual Page cannot be done. You can define\n\
this command under edit->preferences->converters.",
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );

	    return(-1);
	}

	/* Create tempory files directory for this program as needed
	 * (return string needs to be deleted).
	 */
	prog_tmp_dir = MEditCreateTempDir(core_ptr);
	if(prog_tmp_dir == NULL)
	{
	    CDialogGetResponse(
"Non-critical internal error!",
"EditorDoPreview(): MEditCreateTempDir() did not return a path.",
"Could not obtain program tempory file directory path,\n\
and thus unable to generate tempory files for preview.\n\
Cannot continue with preview.",
		CDIALOG_ICON_ERROR,
		CDIALOG_BTNFLAG_OK | CDIALOG_BTNFLAG_HELP,
		CDIALOG_BTNFLAG_OK
	    );
	    v->processing = FALSE;
	    return(-1);
	}

	/* Get standard output file path to output conversion to,
	 * note that stdout_path needs to be deleted later
	 */
	stdout_path = STRDUP(PrefixPaths(
	    prog_tmp_dir, "preview_stdoutXXXXXX"
	));
	mkstemp(stdout_path);
	stderr_path = STRDUP(PrefixPaths(
	    prog_tmp_dir, "preview_stderrXXXXXX"
	));
	mkstemp(stderr_path);

	/* Delete prog_tmp_dir, it is no longer needed */
	g_free(prog_tmp_dir);
	prog_tmp_dir = NULL;

	/* No output file path available? */
	if(stdout_path == NULL)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);

	    v->processing = FALSE;
	    return(-1);
	}

	/* Begin converting */

	ViewerSetBusy(v);
	ViewerSetStatusProgress(v, 0.0f);
	ViewerSetStatusMessage(v, "Converting...");

	/* Convert file specified by filename to groff output as a tempory
	 * file specified by tmp_path.
	 */

/* Define values in enviroment variables? */

	status = MEditExecuteFmtBlock(
	    core_ptr,
	    converter_cmd,	/* Command */
	    filename,		/* Filename */
	    NULL,		/* Options */
	    stdout_path,	/* Stdout path */
	    stderr_path		/* Stderr path */
	);

	ViewerSetStatusMessage(v, "Convert done");

	/* If stderr_path exists, notify of error */
	MEditDialogFromFile(core_ptr, stderr_path);


	/* Begin loading tempory output file */

	/* Error occured during execution or stdout_path does not exist? */
	if(status || stat(stdout_path, &stat_buf))
	{
	    unlink(stdout_path);
	    g_free(stdout_path);
	    unlink(stderr_path);
	    g_free(stderr_path);
	    ViewerSetStatusProgress(v, 0.0f);
	    ViewerSetStatusMessage(v, "Load done");
	    ViewerSetReady(v);

	    v->processing = FALSE;

	    return(-2);
	}
	else
	{
	    buf_len = stat_buf.st_size;
	}

	ViewerSetStatusMessage(v, "Loading...");

	if(buf_len < 0)
	    buf_len = 0;

	/* Allocate memory to load file contents */
	buf = (gchar *)g_malloc((buf_len + 1) * sizeof(char));
	if(buf == NULL)
	{
	    unlink(stdout_path);
	    g_free(stdout_path);
	    unlink(stderr_path);
	    g_free(stderr_path);
	    ViewerSetStatusProgress(v, 0.0f);
	    ViewerSetStatusMessage(v, "Load done");
	    ViewerSetReady(v);

	    v->processing = FALSE;

	    return(-1);
	}

	/* Open file */
	fp = FOpen(stdout_path, "rb");
	if(fp == NULL)
	{
	    unlink(stdout_path);
	    g_free(stdout_path);
	    unlink(stderr_path);
	    g_free(stderr_path);
	    ViewerSetStatusProgress(v, 0.0f);
	    ViewerSetStatusMessage(v, "Load done");
	    ViewerSetReady(v);

	    g_free(buf);

	    v->processing = FALSE;

	    return(-2);
	}

	/* Begin reading file data into buffer */
	status = (gint)fread(buf, sizeof(char), buf_len, fp);
	if(status < buf_len)
	{
	    if(status > 0)
	    {
		buf[status] = '\0';
		buf_len = status;
	    }
	}
	else
	{
	    buf[buf_len] = '\0';    /* Yes its allocated */
	}

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

	/* Remove tempory output file and delete output path */
	unlink(stdout_path);
	g_free(stdout_path);
	stdout_path = NULL;

	unlink(stderr_path);
	g_free(stderr_path);
	stderr_path = NULL;

	ViewerSetStatusProgress(v, 0.5);


	/* Load buffer to viewer */
	ViewerTextInsert(
	    v, buf, buf_len, (void *)v, ViewerLoadProgressCB
	);

	ViewerSetStatusProgress(v, 1.0);

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


	/* Check last manual page path and see if they match new one */
	if((v->cur_manpage_name != NULL) && (name != NULL))
	{
	    /* Name same? */
	    if((v->cur_manpage_name == name) ?
	        1 : !strcmp(v->cur_manpage_name, name)
	    )
	    {
		/* Names match, so scroll to last view_text position */
		vadj = ((v->view_text == NULL) ?
		    NULL : GTK_TEXT(v->view_text)->vadj
		);
		if(vadj != NULL)
		{
		    if(v->last_scroll_vpos < vadj->lower)
			v->last_scroll_vpos = vadj->lower;
		    if(v->last_scroll_vpos > (vadj->upper + vadj->page_size))
			v->last_scroll_vpos = vadj->upper + vadj->page_size;
		    gtk_adjustment_set_value(
			vadj, v->last_scroll_vpos
		    );
		}
	    }
	}



	/* Record current opened manual page file on viewer */
	if((filename != v->cur_manpage_path) && (filename != NULL))
	{
	    g_free(v->cur_manpage_path);
	    v->cur_manpage_path = STRDUP(filename);
	}

	/* Record name */
	if((name != v->cur_manpage_name) && (name != NULL))
	{
	    g_free(v->cur_manpage_name);
	    v->cur_manpage_name = STRDUP(name);
	}


	/* Update last opened path on viewer */
	g_free(v->last_open_path);
	v->last_open_path = STRDUP(filename);
	if(v->last_open_path != NULL)
	{
	    gchar *s = strrchr(v->last_open_path, G_DIR_SEPARATOR);
	    if(s != NULL)
		*s = '\0';
	}


	ViewerSetStatusProgress(v, 0.0f);
	ViewerSetStatusMessage(v, "Load done");

	v->processing = FALSE;

	ViewerUpdateMenus(v);
	ViewerSetReady(v);

	/* Switch to viewer page */
	if(v->main_notebook != NULL)
	{
	    gtk_notebook_set_page(
		GTK_NOTEBOOK(v->main_notebook), ViewerPageNumView
	    );
	}

	/* Update text on current manpage combo */
	if(v->manpage_combo != NULL)
	{
	    GtkCombo *combo = (GtkCombo *)v->manpage_combo;
	    const gchar *s = strrchr(filename, ' ');
	    if(s != NULL)
	    {
		s++;
	        GUIComboAddItem(GTK_WIDGET(combo), s);
	        gtk_entry_set_text(GTK_ENTRY(combo->entry), s);
	    }
	    else
	    {
		GUIComboAddItem(GTK_WIDGET(combo), filename);
		gtk_entry_set_text(GTK_ENTRY(combo->entry), filename);
	    }
	    gtk_editable_select_region(GTK_EDITABLE(combo->entry), 0, -1);

	    /* Also, if manpage_combo specifies a full path, then select
	     * the exact section on the section combo
	     */
	    if(v->section_combo != NULL)
	    {
		GtkEntry *manpage_entry = GTK_ENTRY(combo->entry);

		s = gtk_entry_get_text(manpage_entry);
		if((s != NULL) ? (*s == G_DIR_SEPARATOR) : FALSE)
		{
		    GtkCombo *section_combo = GTK_COMBO(v->section_combo);
		    gtk_entry_set_text(
			GTK_ENTRY(section_combo->entry),
			MEDIT_SECT_NAME_EXACT
		    );
		}
	    }
	}

	return(0);
}
