/*
 * fgetmfs - read an arbitrarily long, possibly continued line;
 * return a pointer to it, in malloced memory.
 */

#include <stdio.h>
#include <ctype.h>

#ifndef AMIGA
#  include <sys/types.h>
#endif /* AMIGA */

#include "fgetmfs.h"
#include "libc.h"

#ifndef max
# define max(a,b) ((a) > (b)? (a): (b))
# define min(a,b) ((a) < (b)? (a): (b))
#endif /* max */

/* One could make these arguments, with defaults. */
#define INITLN 90		/* initial allocation per line */
#define GROWLN 200		/* additional allocation size */

/* getseg returns */
#define FAILED 0
#define HITLIMIT 1
#define OKAY 2

static unsigned sz;		/* bytes currently allocated (in line) */
static int incr;		/* for sz */
static char *line;		/* current allocation */
static char *segment;	/* start of line segment in "line" */
static char *morep;		/* last byte possibly containing input */

/*
 *	`fget malloced, flagged string' with continuations and limit on bytes.
 *	The limit is like fgets's; limit-1 bytes can be read.
 *	-1 means "no limit".
 */

char *fgetmfs(fp, limit, cont)
FILE *fp;
register int limit, cont;		/* honour \ continuations? */
{
	/* allocate room for an initial segment of a line */
	sz = INITLN;
	incr = GROWLN;
	if (limit >= 0 && sz > limit)
		sz = limit;
	line = malloc(sz);
	if (line == NULL)
		return NULL;		/* no memory, can't go on */
	segment = line;
	morep = line + sz - 2;

	/* read all lines, including continuations */
	do {
		/* read the first segment of a line */
		*morep = '\0';			/* mark end of segment */
		if (fgets(segment, (int)sz-(segment-line), fp) == NULL) {
			fprintf(stderr, "bytes read: '%.*s'\n",
				(int)sz - (segment - line), segment);
			free(line);		/* EOF: give up */
			return NULL;
		}
		/* read more of this line, if it didn't fit */
		while (*morep != '\0' && *morep != '\n') {
			register int code = getseg(fp, limit);

			if (code == FAILED)
				return NULL;
			else if (code == HITLIMIT)
				break;
		}
	} while (cont && ismore(fp, cont));
	return realloc(line, (unsigned)(strlen(line)+1));	/* save space */
}

static int getseg(fp, limit)
FILE *fp;
register int limit;
{
	register int oldsz = sz;

	/* extend the allocation, within limit */
	incr = GROWLN;
	sz += incr;
	if (limit >= 0 && sz > limit) {
		sz = limit;
		incr = sz - oldsz;
	}
	if (incr <= 0)			/* hit the limit? */
		return HITLIMIT;
	line = realloc(line, sz);
	if (line == NULL)
		return FAILED;		/* no memory, can't go on */
	/* -1 starts on the terminating NUL of the prev. segment */
	segment = line + oldsz - 1;
	morep = line + sz - 2;		/* recompute for new line, sz */

	/* read the next segment */
	*morep = '\0';
	/* +1 because segment includes terminating NUL of the prev. segment */
	if (fgets(segment, incr+1, fp) == NULL) {
		free(line);		/* EOF: give up */
		return FAILED;
	}
	return OKAY;
}

static int
ismore(fp, cont)
register FILE *fp;
int cont;
{
	register char *nlp;

	/* got a whole line: is it to be continued? */
	if (incr > 0 && cont && (nlp = rindex(line, '\n')) != NULL &&
	    nlp > line && *--nlp == '\\') {
		*nlp = '\0';			/* delete "\\\n" */
		segment = nlp;
	    	if (cont == CONT_NOSPC) {
			register int c;

			/* discard leading whitespace */
			while ((c = getc(fp)) != EOF && c != '\n' &&
			   isascii(c) && isspace(c))
				;
			if (c != EOF)
				(void) ungetc(c, fp);
	    	}
		return 1;			/* read next line */
	} else
		return 0;
}
