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

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

#include "edv_types.h"
#include "edv_history.h"
#include "edv_history_list.h"
#include "config.h"


edv_history_struct *EDVHistoryListAppend(
	edv_history_struct ***list, gint *total,
	gint *n, const gint max
);

void EDVHistoryListOpen(
	const gchar *path,
	edv_history_struct ***list, gint *total,
	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
);

void EDVHistoryAppendToStream(
	FILE *fp, const edv_history_struct *h
);
void EDVHistoryAppendToFile(
	const gchar *path, const edv_history_struct *h
);
void EDVHistoryListSave(
	const gchar *path,
	edv_history_struct **list, gint total,
	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
);


#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 a new history item to the history list.
 *
 *	The list and total specifies the pointers to the history list.
 *
 *	The n specifies the pointer to the new history item's index
 *	within the history list.
 *
 *	If max is positive and appending the new item would cause the
 *	maximum to be exceeded then the oldest (index 0) item will be
 *	deleted and the item pointers in the list will be shifted.
 *
 *	Returns the pointer to the new history item or NULL on error.
 */
edv_history_struct *EDVHistoryListAppend(
	edv_history_struct ***list, gint *total,
	gint *n, const gint max
)
{
	gint i;
	edv_history_struct *h;

	if(n != NULL)
	    *n = -1;

	if((list == NULL) || (total == NULL))
	    return(NULL);

	/* Allocate one more pointer and append a new item */
	i = MAX(*total, 0);
	*total = i + 1;
	*list = (edv_history_struct **)g_realloc(
	    *list,
	    (*total) * sizeof(edv_history_struct *)
	);
	if(*list == NULL)
	{
	    *total = 0;
	    return(NULL);
	}

	/* Allocate a new item at the highest index */
	(*list)[i] = h = EDVHistoryNew();
	if(h == NULL)
	    return(NULL);


	/* Check if total has exceeded maximum */
	if(max > 0)
	{
	    while(*total > max)
	    {
		/* Delete oldest (index 0) item */
		EDVHistoryDelete((*list)[0]);

		/* Reduce total */
		i = *total;
		*total = i - 1;

		/* Shift pointers */
		for(i = 0; i < *total; i++)
		    (*list)[i] = (*list)[i + 1];

		/* Reallocate pointers */
		*list = (edv_history_struct **)g_realloc(
		    *list,
		    (*total) * sizeof(edv_history_struct *)
		);
		if(*list == NULL)
		{
		    *total = 0;
	    	    return(NULL);
		}
	    }
	}

	/* Update index return */
	i = *total;
	if(n != NULL)
	    *n = i - 1;

	return(h);
}


/*
 *	Opens the history items from the history file.
 *
 *	The path specifies the history file.
 *
 *	The list and total specify the pointers to the history list,
 *	the history items opened from the history file will be
 *	appended to the history list.
 *
 *	If progress_cb is not NULL then it will be called during the
 *	operation to report the progress. If it returns non-zero then
 *	the operation will be aborted.
 *
 *	The data specifies the user data which will be passed to the
 *	progress_cb function.
 */
void EDVHistoryListOpen(
	const gchar *path,
	edv_history_struct ***list, gint *total,
	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
)
{
	gboolean aborted = FALSE;
	struct stat stat_buf;
	gchar *parm;
	FILE *fp;
	gulong file_size;
	gint h_num = -1;
	edv_history_struct *h = NULL;

	if((list == NULL) || (total == NULL) || STRISEMPTY(path))
	    return;

	/* Open the history file for reading */
	fp = fopen((const char *)path, "rb");
	if(fp == NULL)
	    return;

	if(fstat(fileno(fp), &stat_buf))
	    file_size = 0l;
	else
	    file_size = (gulong)stat_buf.st_size;

	/* Report the initial progress */
	if(progress_cb != NULL)
	{
	    if(progress_cb(data, 0l, file_size))
		aborted = TRUE;
	}

	/* Begin reading the history file */
	parm = NULL;
	while(!aborted)
	{
	    /* Read next parameter */
	    parm = FSeekNextParm(
		fp, parm,
		EDV_CFG_COMMENT_CHAR,
		EDV_CFG_DELIMINATOR_CHAR
	    );
	    if(parm == NULL)
		break;

	    /* Report the progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(data, (gulong)ftell(fp), file_size))
		{
		    aborted = TRUE;
		    break;
		}
	    }

	    /* Begin handling parameter */

	    /* Start of history item? */
	    if(!g_strcasecmp(parm, "Begin"))
	    {
		gint value[1];
		FGetValuesI(fp, value, 1);

		h_num = MAX(*total, 0);
		*total = h_num + 1;
		*list = (edv_history_struct **)g_realloc(
		    *list, (*total) * sizeof(edv_history_struct *)
		);
		if(*list == NULL)
		{
		    *total = 0;
		    h_num = -1;
		    h = NULL;
		    break;
		}

		(*list)[h_num] = h = EDVHistoryNew();
		if(h == NULL)
		    break;

		h->type = value[0];
	    }
	    /* TimeStart */
	    else if(!g_strcasecmp(parm, "TimeStart"))
	    {
		glong value[1];
		FGetValuesL(fp, value, 1);
		if(h != NULL)
		{
		    h->time_start = (gulong)value[0];
		}
	    }
	    /* TimeEnd */
	    else if(!g_strcasecmp(parm, "TimeEnd"))
	    {
		glong value[1];
		FGetValuesL(fp, value, 1);
		if(h != NULL)
		{
		    h->time_end = (gulong)value[0];
		}
	    }
	    /* Status */
	    else if(!g_strcasecmp(parm, "Status"))
	    {
		gint value[1];
		FGetValuesI(fp, value, 1);
		if(h != NULL)
		{
		    h->status = value[0];
		}
	    }
	    /* Source */
	    else if(!g_strcasecmp(parm, "Source"))
	    {
		gchar *value = FGetString(fp);
		if(h != NULL)
		{
		    g_free(h->src_path);
		    h->src_path = STRDUP(value);
		}
		g_free(value);
	    }
	    /* Target */
	    else if(!g_strcasecmp(parm, "Target"))
	    {
		gchar *value = FGetString(fp);
		if(h != NULL)
		{
		    g_free(h->tar_path);
		    h->tar_path = STRDUP(value);
		}
		g_free(value);
	    }
	    /* Comments */
	    else if(!g_strcasecmp(parm, "Comments"))
	    {
		gchar *value = FGetString(fp);
		if(h != NULL)
		{
		    g_free(h->comments);
		    h->comments = STRDUP(value);
		}
		g_free(value);
	    }
	    /* End */
	    else if(!g_strcasecmp(parm, "End"))
	    {
		FSeekNextLine(fp);

		/* Reset contexts */
		h_num = -1;
		h = NULL;
	    }
	    else
	    {
		FSeekNextLine(fp);
	    }
	}

	/* Delete the parameter */
	g_free(parm);

	/* Close the history file */
	fclose(fp);

	/* Report the final progress */
	if((progress_cb != NULL) && !aborted)
	{
	    if(progress_cb(data, file_size, file_size))
		aborted = TRUE;
	}
}


/*
 *	Appends the history item to the stream.
 *
 *	The fp specifies the stream.
 *
 *	The h specifies the history item.
 */
void EDVHistoryAppendToStream(
	FILE *fp, const edv_history_struct *h
)
{
	if((fp == NULL) || (h == NULL))
	    return;

	fprintf(
	    fp,
	    "Begin = %i\n",
	    h->type
	);
	if(h->time_start > 0)
	    fprintf(
		fp,
		"TimeStart = %ld\n",
		h->time_start
	    );
	if(h->time_end > 0)
	    fprintf(
		fp,
		"TimeEnd = %ld\n",
		h->time_end
	    );
	fprintf(
	    fp,
	    "Status = %i\n",
	    h->status
	);
	if(!STRISEMPTY(h->src_path))
	    fprintf(
		fp,
		"\tSource = %s\n",
		h->src_path
	    );
	if(!STRISEMPTY(h->tar_path))
	    fprintf(
		fp,
		"\tTarget = %s\n",
		h->tar_path
	    );
	if(!STRISEMPTY(h->comments))
	    fprintf(
		fp,
		"\tComments = %s\n",
		h->comments
	    );
	fprintf(fp, "End\n");
}

/*
 *	Appends the history item to the history file.
 *
 *	The path specifies the history file.
 *
 *	The h specifies the history item.
 */
void EDVHistoryAppendToFile(
	const gchar *path, const edv_history_struct *h
)
{
	FILE *fp = !STRISEMPTY(path) ?
	    fopen((const char *)path, "ab") : NULL;
	if(fp == NULL)
	    return;

	EDVHistoryAppendToStream(fp, h);

	fclose(fp);
}

/*
 *	Saves the history items to the history file.
 *
 *	The path specifies the history file.
 *
 *	The list and total specifies the history list.
 *
 *	If progress_cb is not NULL then it will be called during the
 *	operation to report the progress. If it returns non-zero then
 *	the operation will be aborted.
 *
 *	The data specifies the user data which will be passed to the
 *	progress_cb function.
 */
void EDVHistoryListSave(
	const gchar *path,
	edv_history_struct **list, const gint total,
	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
)
{
	gboolean aborted = FALSE;
	gint i;
	gchar *parent_path;
	FILE *fp;
	const edv_history_struct *h;

	if((list == NULL) || STRISEMPTY(path))
	    return;

	/* Get parent directory and create it as needed */
	parent_path = g_dirname(path);
	if(parent_path != NULL)
	{
	    rmkdir(parent_path, S_IRUSR | S_IWUSR | S_IXUSR);
	    g_free(parent_path);
	    parent_path = NULL;
	}

	/* Open the history file for writing */
	fp = fopen((const char *)path, "wb");
	if(fp == NULL)
	    return;

	/* Report initial progress */
	if(progress_cb != NULL)
	{
	    if(progress_cb(
		data, 0l, (gulong)total
	    ))
		aborted = TRUE;
	}

	/* Iterate through history items */
	for(i = 0; i < total; i++)
	{
	    if(aborted)
		break;

	    h = list[i];
	    if(h == NULL)
		continue;

	    /* Report progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(
		    data, (gulong)(i + 1), (gulong)total
		))
		{
		    aborted = TRUE;
		    break;
		}
	    }

	    EDVHistoryAppendToStream(fp, h);
	}

	/* Close history file */
	fclose(fp);

	/* Report final progress */
	if((progress_cb != NULL) && !aborted)
	    progress_cb(
		data, (gulong)total, (gulong)total
	    );
}
