#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#ifdef HAVE_GZIP
#include <zlib.h>
#endif
#ifdef HAVE_BZIP2
#include <bzlib.h>
#endif

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

#include "mpfio.h"


#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 ISCR(c)		(((c) == '\n') || ((c) == '\r'))


/* Low level file IO (for loading and saving as regular or compressed). */
static char *MPLoadFileBZip2(const char *path, int *rtn_buf_len);
static char *MPLoadFileGZip(const char *path, int *rtn_buf_len);
static char *MPLoadFile(const char *path, int *rtn_buf_len);
static void MPSaveFileBZip2(const char *path, const char *buf, int buf_len);
static void MPSaveFileGZip(const char *path, const char *buf, int buf_len);
static void MPSaveFile(const char *path, const char *buf, int buf_len);

/* Groff format string parsers. */
static const char *MPLoadStringNextCR(const char *buf);
static const char *MPLoadStringNextDoubleQuote(const char *buf);
static void MPLoadExplodeNewLines(
        char ***line, int *total_lines,
        const char *buf, const char *buf_end
);
static void MPLoadExplodeParseMPFmt(
	char ***line, int *total_lines,
        const char *buf, const char *buf_end
);
static char *MPSaveFormatMP(char *line_ptr);

static char *MPSubStringIterate(
        char *buf, char **sub_start,
        const char *token, const char *replace,
	int strip_tail
);
static char *MPSubString(
	char *buf, const char *token, const char *replace,
	int strip_tail
);

/* Load and save front ends. */
int MPLoad(
        const char *filename, const char *data,
        mp_header_struct **mp_header_rtn,
        mp_section_struct ***mp_section_rtn, int *mp_total_sections_rtn,
	void *client_data, int (*progress_cb)(long, long, void *)
);
int MPSave(
        const char *filename, char **data_rtn,
        mp_header_struct *mp_header,
        mp_section_struct **mp_section, int mp_total_sections,
        void *client_data, int (*progress_cb)(long, long, void *)
);

/* Deallocaters. */
void MPDeleteHeader(mp_header_struct *mp_header);
void MPDeleteAllSections(
        mp_section_struct **mp_section,
        int mp_total_sections
);



/*
 *	Load from bzip2 compressed file, called by MPLoadFile().
 */
static char *MPLoadFileBZip2(const char *path, int *rtn_buf_len)
{
#ifdef HAVE_BZIP2
        int status, bzerror;
        FILE *fp;
        BZFILE *zfp;
#define bzip2_chunk_size        32767
        char *buf = NULL;
        int buf_cur_pos, buf_len = 0;


        if(path == NULL)
            return(NULL);

        fp = FOpen(path, "rb");
        if(fp == NULL)
            return(NULL);
        zfp = BZ2_bzReadOpen(
            &bzerror, fp,
            0,          /* Do not conserve memory, do this fast. */
            0,          /* No need to be verbose. */
            NULL, 0     /* Nonsense buffer, always NULL and 0. */
        );
        if(bzerror != BZ_OK)
        {
            BZ2_bzReadClose(&bzerror, zfp);
            FClose(fp);
            return(NULL);
        }

        while(1)
        {
            /* Record current buffer index and increase buffer
             * allocation.
             */
            buf_cur_pos = buf_len;
            buf_len += bzip2_chunk_size;
            buf = (char *)realloc(
                buf,
                (buf_len + 1) * sizeof(char)
            );
            if(buf == NULL)
            {
                buf_len = 0;
                break;
            }


            status = BZ2_bzRead(&bzerror, zfp, &buf[buf_cur_pos], bzip2_chunk_size);
            if((status <= 0) ||
               ((bzerror != BZ_OK) && (bzerror != BZ_STREAM_END))
            )
            {
                buf[buf_cur_pos] = '\0';
                break;
            }

            if((buf_cur_pos + status) <= buf_len)
                buf[buf_cur_pos + status] = '\0';
            else
                buf[buf_len - 1] = '\0';
        }

        BZ2_bzReadClose(&bzerror, zfp);
        zfp = NULL;
        FClose(fp);
        fp = NULL;

        if(rtn_buf_len != NULL)
            *rtn_buf_len = buf_len;

        return(buf);
#undef bzip2_chunk_size
#else
	return(NULL);
#endif
}

/*
 *	Load from gzip compressed file, called by MPLoadFile().
 */
static char *MPLoadFileGZip(const char *path, int *rtn_buf_len)
{
#ifdef HAVE_GZIP
        int status;
        gzFile zfp;
#define zlib_chunk_size         32767
        char *buf = NULL;
        int buf_cur_pos, buf_len = 0;



        if(path == NULL)
            return(NULL);

        zfp = gzopen(path, "rb");
        if(zfp == NULL)
            return(NULL);

        while(1)
        {
            /* Record current buffer index and increase buffer
             * allocation.
             */
            buf_cur_pos = buf_len;
            buf_len += zlib_chunk_size;
            buf = (char *)realloc(
                buf,
                (buf_len + 1) * sizeof(char)
            );
            if(buf == NULL)
            {
                buf_len = 0;
                break;
            }

            status = gzread(zfp, &buf[buf_cur_pos], zlib_chunk_size);
            if(status <= 0)
            {
                buf[buf_cur_pos] = '\0';
                break;
            }

            if((buf_cur_pos + status) <= buf_len)
                buf[buf_cur_pos + status] = '\0';
            else
                buf[buf_len - 1] = '\0';
        }

        gzclose(zfp);
        zfp = NULL;

	if(rtn_buf_len != NULL)
	    *rtn_buf_len = buf_len;

        return(buf);
#undef zlib_chunk_size
#else
        return(NULL);
#endif
}

/*
 *	Front end to load file, the extension of the given path will
 *	be checked and loaded accordingly (if it is compressed or not).
 */
static char *MPLoadFile(const char *path, int *rtn_buf_len)
{
	const char *cstrptr, *ext_ptr;
	int need_load_regular = 0;


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

	if(path == NULL)
	    return(NULL);

	/* Get pointer to extension (if any). */
	cstrptr = strrchr(path, DIR_DELIMINATOR);
	if(cstrptr != NULL)
	    cstrptr++;
	else
	    cstrptr = path;
	ext_ptr = strrchr(cstrptr, '.');

	if(ext_ptr != NULL)
	{
            if(!strcasecmp(ext_ptr, ".bz2"))
                return(MPLoadFileBZip2(path, rtn_buf_len));
	    else if(!strcasecmp(ext_ptr, ".gz"))
		return(MPLoadFileGZip(path, rtn_buf_len));
	    else
		need_load_regular = 1;
	}
	else
	{
	    need_load_regular = 1;
	}

	/* Load as regular non-compressed file? */
	if(need_load_regular)
	{
	    FILE *fp = FOpen(path, "rb");
	    char *buf;
	    int buf_len;
	    struct stat stat_buf;

	    if(fp == NULL)
		return(NULL);

	    if(fstat(fileno(fp), &stat_buf))
		stat_buf.st_size = 0;

	    buf_len = stat_buf.st_size;
	    if(buf_len > 0)
		buf = (char *)malloc((buf_len + 1) * sizeof(char));
	    else
		buf = NULL;
	    if(buf == NULL)
	    {
		FClose(fp);
		return(NULL);
	    }

	    fread(buf, sizeof(char), buf_len, fp);
	    buf[buf_len] = '\0';

	    FClose(fp);

	    if(rtn_buf_len != NULL)
		*rtn_buf_len = buf_len;

	    return(buf);
	}
	else
	{
	    return(NULL);
	}
}


/*
 *	Saves the data using bzip2 compression, called from MPSaveFile().
 */
static void MPSaveFileBZip2(const char *path, const char *buf, int buf_len)
{
#ifdef HAVE_BZIP2
        int bzerror;
        FILE *fp;
        BZFILE *zfp;
#define bzip2_chunk_size        32767
        int buf_cur_pos, buf_cur_len;


        fp = FOpen(path, "wb");
        if(fp == NULL)
            return;
        zfp = BZ2_bzWriteOpen(
            &bzerror, fp,
            9,          /* Compression strength (0 to 9). */
            0,          /* No need to be verbose. */
            30          /* Work factor (default is 30). */
        );
        if(bzerror != BZ_OK)
        {
            BZ2_bzWriteClose(&bzerror, zfp, 0, NULL, NULL);
            FClose(fp);
            return;
        }

        /* Begin writing compressed file. */
        buf_cur_pos = 0;
        while(buf_cur_pos < buf_len)
        {
            /* Calculate current segment length, making it no longer
             * than (buf_len - buf_cur_pos) or bzip2_chunk_size,
             * whichever is shorter.
             */
            buf_cur_len = buf_len - buf_cur_pos;
            if(buf_cur_len > bzip2_chunk_size)
                buf_cur_len = bzip2_chunk_size;
            if(buf_cur_len <= 0)
                break;

            /* Write the current calculated buffer segment. */
            BZ2_bzWrite(&bzerror, zfp, (void *)&buf[buf_cur_pos], buf_cur_len);
            if(bzerror != Z_OK)
                break;

            /* Increment current position past the number of bytes that
             * were written.
             */
            buf_cur_pos += buf_cur_len;
        }

        BZ2_bzWriteClose(&bzerror, zfp, 0, NULL, NULL);
        zfp = NULL;
        FClose(fp);
        fp = NULL;

#undef bzip2_chunk_size
#endif
}

/*
 *      Saves the data using gzip compression, called from MPSaveFile().
 */
static void MPSaveFileGZip(const char *path, const char *buf, int buf_len)
{
#ifdef HAVE_GZIP
        int status;
        gzFile zfp;
#define zlib_chunk_size         32767
	int buf_cur_pos, buf_cur_len;


        zfp = gzopen(path, "wb");
        if(zfp == NULL)
            return;

        /* Begin writing compressed file. */
        buf_cur_pos = 0;
        while(buf_cur_pos < buf_len)
        {
            /* Calculate current segment length, making it no longer
             * than (buf_len - buf_cur_pos) or bzip2_chunk_size,
             * whichever is shorter.
             */
            buf_cur_len = buf_len - buf_cur_pos;
            if(buf_cur_len > zlib_chunk_size)
                buf_cur_len = zlib_chunk_size;
            if(buf_cur_len <= 0)
                break;

            /* Write the current calculated buffer segment. */
	    status = gzwrite(zfp, (const voidp)&buf[buf_cur_pos], buf_cur_len);
            if(status <= 0)
                break;

            /* Increment current position past the number of bytes that
             * were written.
             */
            buf_cur_pos += buf_cur_len;
        }

        gzclose(zfp);
        zfp = NULL;

#undef zlib_chunk_size
#endif
}

/*
 *      Front end to save to file, the extension of the given path will
 *      be checked and saved accordingly (needing compression or not).
 */
static void MPSaveFile(const char *path, const char *buf, int buf_len)
{
        const char *cstrptr, *ext_ptr;
        int need_save_regular = 0;


        if((path == NULL) || (buf == NULL) || (buf_len <= 0))
            return;

        /* Get pointer to extension (if any). */
        cstrptr = strrchr(path, DIR_DELIMINATOR);
        if(cstrptr != NULL)
            cstrptr++;
        else
            cstrptr = path;
        ext_ptr = strrchr(cstrptr, '.');

        if(ext_ptr != NULL)
        {
            if(!strcasecmp(ext_ptr, ".bz2"))
                MPSaveFileBZip2(path, buf, buf_len);
            else if(!strcasecmp(ext_ptr, ".gz"))
                MPSaveFileGZip(path, buf, buf_len);
            else
                need_save_regular = 1;

	    /* Saved as one of the compressed formats? If so then just
	     * return now and skip further processing.
	     */
	    if(!need_save_regular)
		return;
        }
        else
        {
            need_save_regular = 1;
        }

        /* Save as regular non-compressed file? */
        if(need_save_regular)
        {
            FILE *fp = FOpen(path, "wb");
            if(fp == NULL)
                return;

	    fwrite(buf, sizeof(char), buf_len, fp);

            FClose(fp);
        }
}


/*
 *	Returns pointer in buf to next occurance of non-escaped
 *	newline character or NULL if there isn't any.
 */
static const char *MPLoadStringNextCR(const char *buf)
{
	while((*buf) != '\0')
	{
	    if((*buf) == '\\')
	    {
		buf++;		/* Increment past current '\\' char. */

		/* Next char is a null? */
		if((*buf) == '\0')
		    return(NULL);

		/* Skip whatever next char was. */
		buf++;
		continue;
	    }

	    if(ISCR(*buf))
               return(buf);

	    buf++;
	}

	return(NULL);
}

/*
 *      Returns pointer in buf to next occurance of non-escaped
 *      double quote character or NULL if there isn't any.
 */
static const char *MPLoadStringNextDoubleQuote(const char *buf)
{
        while((*buf) != '\0')
        {
            if((*buf) == '\\')
            {
                buf++;          /* Increment past current '\\' char. */

                /* Next char is a null? */
                if((*buf) == '\0')
                    return(NULL);

                /* Skip whatever next char was. */
                buf++;
                continue;
            }

            if((*buf) == '"')
               return(buf);

            buf++;
        }

        return(NULL);
}

/*
 *	Explodes the buf into lines from buf to buf_end (but not
 *	including buf_end) at all non-escaped deliminated newline
 *	characters..
 *
 *	Inputs are assumed valid.
 */
static void MPLoadExplodeNewLines(
	char ***line, int *total_lines,
	const char *buf, const char *buf_end
)
{
	const char *buf_limit_ptr;
	const char *buf_cur_ptr;
	int n, cur_seg_len;
	char *line_ptr;


	if((*total_lines) < 0)
	    (*total_lines) = 0;

	buf_cur_ptr = buf;

	do
	{
	    buf_limit_ptr = MPLoadStringNextCR(buf_cur_ptr);
	    if((buf_limit_ptr == NULL) || (buf_limit_ptr > buf_end))
	        buf_limit_ptr = buf_end;

	    cur_seg_len = MAX(
		(int)buf_limit_ptr - (int)buf_cur_ptr, 0
	    );

	    /* Allocate a new line. */
	    n = (*total_lines);
	    (*total_lines) = n + 1;
	    (*line) = (char **)realloc(*line, (*total_lines) * sizeof(char *));
	    if((*line) == NULL)
	    {
		(*total_lines) = 0;
		break;
	    }
	    else
	    {
		line_ptr = (char *)malloc((cur_seg_len + 1) * sizeof(char));
		(*line)[n] = line_ptr;
		if(line_ptr != NULL)
		{
		    if(cur_seg_len > 0)
			strncpy(line_ptr, buf_cur_ptr, cur_seg_len);
		    line_ptr[cur_seg_len] = '\0';
		}
	    }

	    /* Seek past current buffer limit pointer. */
	    buf_cur_ptr = buf_limit_ptr + 1;

	} while(buf_cur_ptr < buf_end);

}


/*
 *      Coppies the given buffer into a local tempory buffer then
 *	parses it with all manual page format substitutions into
 *	XML format.
 *
 *	Then explodes each line deliminated by non-escaped newline
 *	characters.
 *
 *      Inputs are assumed valid.
 */
static void MPLoadExplodeParseMPFmt(
        char ***line, int *total_lines,
        const char *buf, const char *buf_end
)
{
	char *tmp_buf;
        int cur_seg_len;


	/* Copy given buffer to a tempory buffer. */
	cur_seg_len = MAX((int)buf_end - (int)buf, 0);
	tmp_buf = (char *)malloc((cur_seg_len + 1) * sizeof(char));
	if(tmp_buf == NULL)
	    return;

	if(cur_seg_len > 0)
	    memcpy(tmp_buf, buf, cur_seg_len * sizeof(char));
	tmp_buf[cur_seg_len] = '\0';

	/* Make substitutions to tmp_buf, note that the order is
	 * important.
	 *
	 * When adding new substitutions, make sure the inverse operation
	 * is included in function MPSaveFormatMP() so that saving does
	 * the inverse substitution. Remember to make each inverse
	 * substitution in reverse order from substitutions here.
	 *
	 * Don't forget to add an universe operation below in
	 * MPSaveFormatMP()!
	 */
	tmp_buf = MPSubString(tmp_buf, "\\-",	"-",		0);
	tmp_buf = MPSubString(tmp_buf, "&",     "&amp;",	0);
	tmp_buf = MPSubString(tmp_buf, "<",	"&lt;", 	0);
	tmp_buf = MPSubString(tmp_buf, ">",	"&gt;", 	0);
	/* Now we can replace to replace strings with < and >. */
	tmp_buf = MPSubString(tmp_buf, ".BI",	"<BI>",		1);
	tmp_buf = MPSubString(tmp_buf, ".BR",   "<BR>", 	1);
	tmp_buf = MPSubString(tmp_buf, ".br",	"<br>", 	1);
	tmp_buf = MPSubString(tmp_buf, ".fi",	"<fi>", 	1);
	tmp_buf = MPSubString(tmp_buf, ".If",	"<If>",		1);
	tmp_buf = MPSubString(tmp_buf, ".Im",	"<Im>",		1);
	tmp_buf = MPSubString(tmp_buf, ".IP",   "<IP>", 	1);
	tmp_buf = MPSubString(tmp_buf, ".LP",   "<LP>", 	1);
	tmp_buf = MPSubString(tmp_buf, ".RP",   "<RP>",		1);
	tmp_buf = MPSubString(tmp_buf, ".TP",   "<TP>",         1);
	tmp_buf = MPSubString(tmp_buf, ".nf",   "<nf>",		1);
	tmp_buf = MPSubString(tmp_buf, ".PP",   "<PP>",		1);
	tmp_buf = MPSubString(tmp_buf, ".RB",   "<RB>",		1);
	tmp_buf = MPSubString(tmp_buf, ".RI",   "<RI>",		1);
	tmp_buf = MPSubString(tmp_buf, ".Sp",	"<Sp>",		1);
	tmp_buf = MPSubString(tmp_buf, ".ta",	"<ta>",		1);
	/* Single character id substitutions after two character ids. */
	tmp_buf = MPSubString(tmp_buf, ".B",    "<B>",		1);
	tmp_buf = MPSubString(tmp_buf, ".I",    "<I>",		1);
/*
 * Need to handle:

.ad b
.hy 1
.if t

 */
	tmp_buf = MPSubString(tmp_buf, "\\1i",	"<li>",		1);
	tmp_buf = MPSubString(tmp_buf, "\\fB",  "<fB>",		0);
	tmp_buf = MPSubString(tmp_buf, "\\fI",	"<fI>",		0);
	tmp_buf = MPSubString(tmp_buf, "\\fR",  "<fR>",         0);
	tmp_buf = MPSubString(tmp_buf, "\\fP",	"<fP>",		0);


	/* Get new length, since it may have changed after the above
	 * substitutions.
	 */
	cur_seg_len = strlen(tmp_buf);

	/* Explode at all new line occurances. */
	MPLoadExplodeNewLines(
	    line, total_lines,
	    tmp_buf, (const char *)(tmp_buf + cur_seg_len)
	);

	/* Free the tempory buffer. */
	free(tmp_buf);
}


/*
 *      Takes the given buffer and makes XML to manual page substitutions
 *	then returns a reallocated buffer. Basically doing the opposite
 *	of MPLoadExplodeParseMPFmt() but does not concatinate any lines.
 *
 *	The given buffer should no longer be referenced after this call.
 *
 *      Inputs are assumed valid.
 */
static char *MPSaveFormatMP(char *line_ptr)
{
	char *tmp_buf = line_ptr;


        /* Make substitutions to tmp_buf, note that the order is
         * important and should be in reverse inverse order from function
	 * MPLoadExplodeParseMPFmt().
	 */
        tmp_buf = MPSubString(tmp_buf, "<fP>", 	"\\fP",		0);
	tmp_buf = MPSubString(tmp_buf, "<fR>",  "\\fR",         0);
        tmp_buf = MPSubString(tmp_buf, "<fI>",	"\\fI",		0);
	tmp_buf = MPSubString(tmp_buf, "<fB>",  "\\fB",		0);
        tmp_buf = MPSubString(tmp_buf, "<li>",	"\\1i ",	0);

	/* Single character tags. */
        tmp_buf = MPSubString(tmp_buf, "<I>",	".I ",		0);
        tmp_buf = MPSubString(tmp_buf, "<B>",	".B ",		0);

        /* Two character tags. */
        tmp_buf = MPSubString(tmp_buf, "<ta>",	".ta ",		0);
        tmp_buf = MPSubString(tmp_buf, "<Sp>",	".Sp ",		0);
        tmp_buf = MPSubString(tmp_buf, "<RI>",	".RI ",		0);
        tmp_buf = MPSubString(tmp_buf, "<RB>",	".RB ",		0);
        tmp_buf = MPSubString(tmp_buf, "<PP>",	".PP ",		0);
        tmp_buf = MPSubString(tmp_buf, "<nf>",	".nf ",		0);
	tmp_buf = MPSubString(tmp_buf, "<TP>",  ".TP ",         0);
        tmp_buf = MPSubString(tmp_buf, "<RP>",	".RP ",		0);
	tmp_buf = MPSubString(tmp_buf, "<LP>",  ".LP ",		0);
        tmp_buf = MPSubString(tmp_buf, "<IP>",	".IP ",		0);
	tmp_buf = MPSubString(tmp_buf, "<Im>",	".Im ",		0);
	tmp_buf = MPSubString(tmp_buf, "<If>",	".If ",		0);
        tmp_buf = MPSubString(tmp_buf, "<fi>",	".fi ",		0);
        tmp_buf = MPSubString(tmp_buf, "<br>",	".br ",		0);
        tmp_buf = MPSubString(tmp_buf, "<BR>",	".BR ",		0);
	tmp_buf = MPSubString(tmp_buf, "<BI>",	".BI ",		0);

	/* Literal deliminator characters. */
        tmp_buf = MPSubString(tmp_buf, "&gt;",	">",		0);
        tmp_buf = MPSubString(tmp_buf, "&lt;",	"<",		0);
        tmp_buf = MPSubString(tmp_buf, "&amp;",  "&",		0);
        tmp_buf = MPSubString(tmp_buf, "-",	"\\-",		0);

	return(tmp_buf);
}


/*
 *	Used by MPSubString(), no other functions should call this.
 *	Inputs assumed valid.
 *
 *	Returns a reallocated string from buf, the given buf should no
 *	longer be referenced. The sub_start will be a pointer to a pointer
 *	in buf in which the substitution should start at. If a
 *	substitution is made then sub_start will be set to the pointer
 *	in the new buffer after the substituted segment, otherwise if no
 *	substitution is made then sub_start will be set to point to NULL.
 *
 *	Inputs assumed valid.
 */
static char *MPSubStringIterate(
	char *buf, char **sub_start,
	const char *token, const char *replace, int strip_tail
)
{
	char *bp;
	int i, start_offset;
	int buf_len, token_len, replace_len;


	token_len = strlen(token);
	replace_len = strlen(replace);

	/* Search for token in buf at the given starting position
	 * assumed to be in buf.
	 */
	(*sub_start) = strstr(*sub_start, token);
	if((*sub_start) == NULL)
	    return(buf);

	/* Number of bytes into buf that matched the token. */
	start_offset = (int)(*sub_start) - (int)buf;

	/* Now (*sub_start) is positioned at the start of the
	 * substitution. Begin removing token string from buf.
	 */
	bp = (*sub_start);
	while(1)
	{
	    (*bp) = (*(bp + token_len));
	    if((*bp) == '\0')
		break;
	    else
		bp++;
	}

	/* Reallocate buffer to accomidate the current length plus
	 * the added replace string.
	 */
	buf_len = strlen(buf) + replace_len;
	buf = (char *)realloc(buf, (buf_len + 1) * sizeof(char));
	if(buf == NULL)
	    return(NULL);

	/* Since reallocated buffer, need to calculate new start of
	 * substitution pointer.
	 */
	(*sub_start) = buf + start_offset;

	/* Replace string has length? */
	if(replace_len > 0)
	{
	    /* Shift to make room for incoming string. */
	    bp = buf + buf_len;
	    while((bp - replace_len) >= (*sub_start))
	    {
		(*bp) = (char)(*(bp - replace_len));
		bp--;
	    }

	    /* Put in replace buffer. */
	    memcpy(*sub_start, replace, replace_len * sizeof(char));
	}

	/* Remove tailing spaces after replaced string. */
	if(strip_tail > 0)
	{
	    for(i = 0; i < strip_tail; i++)
	    {
		bp = (char *)((*sub_start) + replace_len);
		if(!ISBLANK(*bp))
		    break;

		while((*bp) != '\0')
		{
		    (*bp) = (char)(*(bp + 1));
		    bp++;
		}
	    }
	}


	/* Move (*sub_start) pointer past the replaced substitution. */
	(*sub_start) = (char *)((*sub_start) + replace_len);

	return(buf);
}

/*
 *	Replaces all occurances of token in the given string buf with the
 *	value of replace. If replace is NULL then an empty string "" will
 *	be used as the replace string.
 *
 *	If strip_tail is positive then the number of spaces (if any or
 *	less) occuring after the replaced string will be stripped.
 *	If strip_tail is 2 then "hi there  baby" "there" "ya" becomes
 *	"hi yababy".
 *
 *	The returned buffer will be either buf, NULL or a reallocated
 *	buf, in any case you should always use the return value as
 *	the new buf (works like realloc()).
 */
static char *MPSubString(
	char *buf, const char *token, const char *replace,
	int strip_tail
)
{
	char *last_sub_ptr;


	if(buf == NULL)
	    return(NULL);

	if(token == NULL)
	    return(buf);
	else if((*token) == '\0')
	    return(buf);

	if(replace == NULL)
	    replace = "";

	/* Start last substitution pointer at buf. */
	last_sub_ptr = buf;
	do
	{
	    buf = MPSubStringIterate(
		buf, &last_sub_ptr,
		token, replace, strip_tail
	    );
	}
	while(last_sub_ptr != NULL);

	return(buf);
}


/*
 *	Loads the contents of the manual page file specified by filename
 *	or data into the given pointers to substructures which this
 *	function will allocate.
 *
 *	If the progress_cb function is not NULL and it returns non-zero
 *	then the loading will be aborted. In which case return will be
 *	success but returned data will be short.
 *
 *	The loaded data needs to be freed with a call to MPDeleteHeader()
 *	and MPDeleteAllSections().
 *
 *      Returns non-zero on error, where -1 is a general error and -2 is
 *      file not found/not allowed to read.
 */
int MPLoad(
        const char *filename, const char *data,
        mp_header_struct **mp_header_rtn,
        mp_section_struct ***mp_section_rtn, int *mp_total_sections_rtn,
        void *client_data, int (*progress_cb)(long, long, void *)
)
{
	int n, status;
	long data_len;
	mp_header_struct *mp_header;
	mp_section_struct **mp_section, *mp_section_ptr;
	int mp_total_sections;
	char *buf, *buf_end;	/* Entire loaded buffer and length. */
	char *cur_buf_ptr, *limit_buf_ptr;
	char *cur_section_name;
	int buf_len, cur_seg_len;


	/* Reset returns. */
	if(mp_header_rtn != NULL)
	    (*mp_header_rtn) = NULL;
	if(mp_section_rtn != NULL)
	    (*mp_section_rtn) = NULL;
	if(mp_total_sections_rtn != NULL)
	    (*mp_total_sections_rtn) = 0;


	/* Load from file? */
	if(filename != NULL)
	{
	    /* Call progress callback? */
	    if(progress_cb != NULL)
	    {
		/* Start progress at 0. */
		status = progress_cb(0, 1, client_data);
		if(status)
		{
		    return(0);
		}
	    }

	    /* Load data from file, this will return a dynamically
	     * allocated buffer containing the literal data loaded from
	     * file.  The buffer length buf_len will not include the null
	     * terminating byte but buf will contain it if it is not NULL.
	     */
	    buf = MPLoadFile(filename, &buf_len);
	    if((buf == NULL) || (buf_len <= 0))
	    {
		free(buf);
		return(-1);
	    }

	    data_len = buf_len;
	}
	/* Load from data? */
	else if(data != NULL)
	{
	    data_len = strlen(data);

            /* Call progress callback. */
            if(progress_cb != NULL)
            {
		/* Start progress at 0. */
                status = progress_cb(0, data_len, client_data);
                if(status)
                {
                    return(0);
                }
            }

            /* How much buffer do we need? */
            buf_len = data_len;
            if(buf_len < 0)
                buf_len = 0;

            /* Allocate buffer to load file into. */
            buf = (char *)malloc((buf_len + 1) * sizeof(char));
            if(buf == NULL)
                return(-1);

	    /* Copy data to new buf. */
	    memcpy(
		(void *)buf, (const void *)data,
		buf_len * sizeof(char)
	    );
	    buf[buf_len] = '\0';	/* Yes its allocated. */
	}
	else
	{
	    /* No given filename or data to load from. */
	    return(-1);
	}


	/* At this point the buffer buf is not NULL and should contain the
	 * given data or the data loaded from file.
	 */

	/* Begin parsing buffer, get buffer boundary pointers. */
	buf_end = buf + buf_len;
	cur_buf_ptr = buf;

	/* Parse header. */
	mp_header = (mp_header_struct *)calloc(1, sizeof(mp_header_struct));
	if(mp_header != NULL)
	{
	    /* Get pointer to start of first section heading (if any). */
	    limit_buf_ptr = strstr(cur_buf_ptr, ".SH");
	    if(limit_buf_ptr == NULL)
		limit_buf_ptr = buf_end;

	    cur_seg_len = MAX((int)limit_buf_ptr - (int)cur_buf_ptr, 0);

/* Simply load header as exploded newlines for now. */
	    MPLoadExplodeNewLines(
		&mp_header->line, &mp_header->total_lines,
		cur_buf_ptr, limit_buf_ptr
	    );

	    /* Set cur_buf_ptr to start of first section for section
	     * parsing which happens next.
	     */
            cur_buf_ptr = limit_buf_ptr;
	}
	if(mp_header_rtn == NULL)
	    MPDeleteHeader(mp_header);
	else
	    (*mp_header_rtn) = mp_header;
	mp_header = NULL;

	/* Call progress callback. */
	if(progress_cb != NULL)
	{
            status = progress_cb(
                CLIP((long)cur_buf_ptr - (long)buf, 0, data_len),
                data_len,
                client_data
            );
            if(status)
            {
		free(buf);
                return(0);
            }
        }


	/* Begin loading sections. */
	mp_section = NULL;
	mp_total_sections = 0;
	while(cur_buf_ptr < buf_end)
	{
	    /* Call progress callback? */
            if(progress_cb != NULL)
            {
                status = progress_cb(
		    CLIP((long)cur_buf_ptr - (long)buf, 0, data_len),
		    data_len, client_data
		);
                if(status)
                {
		    if((mp_section_rtn == NULL) || (mp_total_sections_rtn == NULL))
		    {
			MPDeleteAllSections(mp_section, mp_total_sections);
		    }
		    else 
		    {
			(*mp_section_rtn) = mp_section;
			(*mp_total_sections_rtn) = mp_total_sections;
		    }
		    free(buf);
                    return(0);
                }
            }


            /* Increment current buffer pointer past the ".SH" marking
	     * plus any spaces.
	     */
	    cur_buf_ptr += strlen(".SH");
            while(ISBLANK(*cur_buf_ptr))
                cur_buf_ptr++;

	    /* Now positioned at start of section name, check if
	     * section name is encapsulated withing double quotes.
	     */
	    cur_section_name = NULL;
	    if((*cur_buf_ptr) == '"')
	    {
		/* Need to seek to other end of '"'. */
		cur_buf_ptr++;
		limit_buf_ptr = (char *)MPLoadStringNextDoubleQuote(cur_buf_ptr);
		if(limit_buf_ptr == NULL)
		    limit_buf_ptr = buf_end;

                cur_seg_len = MAX((int)limit_buf_ptr - (int)cur_buf_ptr, 0);
		cur_section_name = (char *)malloc((cur_seg_len + 1) * sizeof(char));
		if(cur_section_name != NULL)
		{
		    strncpy(cur_section_name, cur_buf_ptr, cur_seg_len);
		    cur_section_name[cur_seg_len] = '\0';
		}

		/* Seek current buffer to start of section body. */
		cur_buf_ptr = limit_buf_ptr;
		while((cur_buf_ptr < buf_end) &&
                      (((*cur_buf_ptr) == '"') ||
                       ISBLANK(*cur_buf_ptr) ||
                       ISCR(*cur_buf_ptr)
                      )
                )
                    cur_buf_ptr++;
	    }
	    else
	    {
		/* Seek to next newline character. */
		limit_buf_ptr = cur_buf_ptr;
		while((limit_buf_ptr < buf_end) &&
/* Some older man pages need to have entire .SH line loaded as section name
		      !ISBLANK(*limit_buf_ptr) && */
                      !ISCR(*limit_buf_ptr)
		)
		    limit_buf_ptr++;

                cur_seg_len = MAX((int)limit_buf_ptr - (int)cur_buf_ptr, 0);
                cur_section_name = (char *)malloc((cur_seg_len + 1) * sizeof(char));
                if(cur_section_name != NULL)
                {
                    strncpy(cur_section_name, cur_buf_ptr, cur_seg_len);
                    cur_section_name[cur_seg_len] = '\0';
                }

                /* Seek current buffer to start of section body. */
                cur_buf_ptr = limit_buf_ptr;
                while((cur_buf_ptr < buf_end) &&
                      (ISBLANK(*cur_buf_ptr) || 
                       ISCR(*cur_buf_ptr)
                      )
                )
                    cur_buf_ptr++;
	    }

	    /* Current buffer position is now positioned at start of
	     * section body.
	     */

	    /* Allocate new section structure. */
	    n = mp_total_sections;
	    mp_total_sections = n + 1;
	    mp_section = (mp_section_struct **)realloc(
		mp_section, mp_total_sections * sizeof(mp_section_struct *)
	    );
	    if(mp_section == NULL)
	    {
		mp_total_sections = 0;
		cur_buf_ptr = buf_end;	/* Set current buffer to end. */
		break;
	    }
	    else
	    {
		mp_section_ptr = (mp_section_struct *)calloc(
		    1, sizeof(mp_section_struct)
		);
		mp_section[n] = mp_section_ptr;
		if(mp_section_ptr == NULL)
		{
                    cur_buf_ptr = buf_end;	/* Set current buffer to end. */
                    break;
                }
	    }

	    /* Set section name. */
	    mp_section_ptr->section = cur_section_name;
	    cur_section_name = NULL;

	    /* Fetch section's body. */
	    limit_buf_ptr = strstr(cur_buf_ptr, ".SH");
            if(limit_buf_ptr == NULL)
                limit_buf_ptr = buf_end;

	    /* Explode and parse the manual page format, storing the
	     * lines into the given lines array after parsing cur_buf_ptr
	     * to limit_buf_ptr.
	     */
	    MPLoadExplodeParseMPFmt(
		&mp_section_ptr->line, &mp_section_ptr->total_lines,
		cur_buf_ptr, limit_buf_ptr
	    );

	    /* Move current position to start of next section's
	     * deliminator.
	     */
	    cur_buf_ptr = limit_buf_ptr;
	}
        if((mp_section_rtn == NULL) || (mp_total_sections_rtn == NULL))
	{
            MPDeleteAllSections(mp_section, mp_total_sections);
	}
        else
	{
            (*mp_section_rtn) = mp_section;
	    (*mp_total_sections_rtn) = mp_total_sections;
	}
	mp_section = NULL;
	mp_total_sections = 0;


        /* Call progress callback. */
        if(progress_cb != NULL)
        {
            status = progress_cb(
                CLIP((long)cur_buf_ptr - (long)buf, 0, data_len),
                data_len,
                client_data
            );
            if(status)
            {  
                free(buf);
                return(0);
            }
        }   


	/* Deallocate local data buffer. */
	free(buf);


	return(0);
}

/*
 *	Saves the manual page data into either the specified filename
 *	or data_rtn (whichever is not NULL).
 *
 *      If the progress_cb function is not NULL and it returns non-zero
 *      then the saving will be aborted. In which case return will be
 *      success but saved data will be short.
 *
 *      Returns non-zero on error, where -1 is a general error and -2 is
 *      cannot write to file.
 */
int MPSave(
        const char *filename, char **data_rtn,
        mp_header_struct *mp_header,
        mp_section_struct **mp_section, int mp_total_sections,
        void *client_data, int (*progress_cb)(long, long, void *)
)
{
	char *buf = NULL;
	int buf_len = 0;
	const char *out_ptr = NULL;

	int i, section_num;
	mp_section_struct *section_ptr;
	const char *line_ptr;
	int tmp_line_len;
	char *tmp_line_ptr;


	/* Both filename and data return may not be both NULL. */
	if((filename == NULL) && (data_rtn == NULL))
	    return(-1);

/* Appends the string pointed to by out_ptr (which is assumed not
 * NULL) and appends it to the buf, reallocating buf as needed and
 * increasing the buf_len.
 */
#define DO_WRITE_OUT	\
{ \
 int cur_out_len = strlen(out_ptr); \
 int cur_out_pos = buf_len; \
\
 if(cur_out_len > 0) \
 { \
  buf_len += cur_out_len; \
  buf = (char *)realloc(buf, (buf_len + 1) * sizeof(char)); \
  if(buf == NULL) \
  { \
   buf_len = 0; \
  } \
  else \
  { \
   memcpy(&buf[cur_out_pos], out_ptr, cur_out_len); \
   buf[buf_len] = '\0'; \
  } \
 } \
}

	/* Write header. */
	if(mp_header != NULL)
	{
	    for(i = 0; i < mp_header->total_lines; i++)
	    {
		line_ptr = mp_header->line[i];
		if(line_ptr == NULL)
		    continue;

		/* Write out line. */
		out_ptr = line_ptr;
		DO_WRITE_OUT

                /* Add new line at the end. */
                out_ptr = "\n";
                DO_WRITE_OUT
	    }
	}

	/* Write each section. */
	if(mp_section != NULL)
	{
	    /* Go through each section. */
            for(section_num = 0; section_num < mp_total_sections; section_num++)
            {
		section_ptr = mp_section[section_num];
		if(section_ptr == NULL)
                    continue;

		/* Write section deliminator. */
		out_ptr = ".SH ";
		DO_WRITE_OUT

		/* Section name not available? */
		if(section_ptr->section == NULL)  
		{
		    out_ptr = "\"UNTITLED\"";
		    DO_WRITE_OUT
		}
		else
		{
		    /* Allocate tempory line for section name. */
		    tmp_line_len = strlen(section_ptr->section) + 4;
		    tmp_line_ptr = (char *)malloc(
			(tmp_line_len + 1) * sizeof(char)
		    );
		    if(tmp_line_ptr != NULL)
		    {
			/* Format then write section name. */
			sprintf(tmp_line_ptr, "\"%s\"",
			    section_ptr->section
                        );
                        out_ptr = tmp_line_ptr;
                        DO_WRITE_OUT

                        /* Free tempory line for section name. */
                        free(tmp_line_ptr);
                    }
                    tmp_line_ptr = NULL;
		}

		/* Add new line at the end. */
		out_ptr = "\n";
		DO_WRITE_OUT


		/* Go through each line on section. */
                for(i = 0; i < section_ptr->total_lines; i++)
                {
                    line_ptr = section_ptr->line[i];
                    if(line_ptr == NULL)
                        continue;

		    /* Make a copy of the line which we will make
		     * substitutions to and then deallocate after
		     * writing it out.
		     */
		    tmp_line_ptr = strdup(line_ptr);
		    if(tmp_line_ptr == NULL)
			continue;

		    /* Make substitutions and other formatting to the
		     * tmp_line_ptr before writing it out.
		     */
                    tmp_line_ptr = MPSaveFormatMP(tmp_line_ptr);

		    /* Write out tmp_line_ptr. */
                    out_ptr = tmp_line_ptr;
                    DO_WRITE_OUT

		    /* Add new line at the end. */
		    out_ptr = "\n";
                    DO_WRITE_OUT

		    /* Free coppied line. */
		    free(tmp_line_ptr);
		    tmp_line_ptr = NULL;
		}
            }
	}

#undef DO_WRITE_OUT


	/* At this point buf and buf_len specify the (null terminated)
	 * data to be written out.
	 */

	/* Write out to file? */
	if(filename != NULL)
	{
	    MPSaveFile(filename, buf, buf_len);
	}
	else if(data_rtn != NULL)
        {
/* Need to work on this. */
        }

	/* Deallocate the local buffer, it is no longer needed. */
	free(buf);
	buf = NULL;
	buf_len = 0;

	return(0);
}


/*
 *	Deallocates the given header structure and all its substructures.
 */
void MPDeleteHeader(mp_header_struct *mp_header)
{
	if(mp_header == NULL)
	    return;

	free(mp_header->title);

	strlistfree(mp_header->line, mp_header->total_lines);

	free(mp_header);
}

/*
 *      Deallocates the given section structures and the pointer
 *	array.
 */
void MPDeleteAllSections(
	mp_section_struct **mp_section,
	int mp_total_sections
)
{
	int i;
	mp_section_struct *mp_section_ptr;


	/* Go through each manual page section structure. */
	for(i = 0; i < mp_total_sections; i++)
	{
	    mp_section_ptr = mp_section[i];
	    if(mp_section_ptr == NULL)
		continue;

	    free(mp_section_ptr->section);

	    strlistfree(
		mp_section_ptr->line,
		mp_section_ptr->total_lines
	    );

	    free(mp_section_ptr);
	}

	/* Free pointer array. */
	free(mp_section);
}
