/*
	Sort/Merge from Software Tools

	Last Modified : 21 September 1982

	Converted from Software Tools RATFOR to BDS C by Leor Zolman
	Sep 2, 1982

	Usage: sort <infile> <outfile>

	Main variables have been made external; this is pretty much in
	line with the RATFOR call-by-name convention anyway.

	Requires lots of disk space, up to around three times the length
	of the file file being sorted. This program is intended for files
	bigger than memory; simpler, faster sorts can be implemented for
	really short files (like ALPH.C)
		-leor

	Compile & Link:
		A>cc sort.c -e2800 -o
		A>l2 sort
	(or...)
		A>cc sort.c -e2900 -o
		A>clink sort

*/


#include <bdscio.h>

#define VERBOSE 1		/* give running account of file activity */

#define MAXLINE 200		/* longest line we want to deal with */
#define NBUFS 7			/* Max number of open buffered files */
#define MAXPTR  3000		/* Max number of lines (set for dict) */
#define MERGEORDER (NBUFS-1)	/* Max # of intermediate files to merge */
#define NAMESIZE 20		/* Max Filename size */
#define LOGPTR 13		/* smallest value >= log (base 2) of  MAXPTR */
#define EOS '\0'		/* string termination character */
#define FILE struct _buf

#define stderr 4
#define fputc putc

char name[NAMESIZE], name2[NAMESIZE + 10];
FILE buffers[NBUFS + 1];	/* up to NBUFS general-purp. buffered files */
FILE *infil[MERGEORDER + 1];	/* tmp file ptrs for sort operation */
unsigned linptr[MAXPTR + 1], nlines;
int temp;
unsigned maxtext;		/* max # of chars in main text buffer */
char *linbuf;			/* text area starts after this variable */

main(argc,argv)
char **argv;
{

	FILE *infile, *outfile;		/* main input and output streams */
	FILE *tmpfile;

	int makfil(), min(), gopen(), gremov();
	int gtext();
	unsigned high, lim, low, t;

	linbuf = endext();		/* start of text buffer area */
	maxtext = topofmem() - endext() - 500;

	tmpfile = buffers[0];

	if (argc != 3)
		exit(puts("Usage: sort <infile> <outfile>\n"));

	infile = buffers[1];
	if (fopen(argv[1], infile) == ERROR)
	{
		puts("Can't open "); puts(argv[1]); exit(-1);
	}

#if VERBOSE
	fputs("Beginning initial formation run\n",stderr);
#endif

	high = 0;		/* Initial formation of runs:	*/
	do {
		t = gtext(infile);
		quick(nlines);		
		high++;
		makfil(high,tmpfile);
		ptext(tmpfile);
		fclout(tmpfile);
	} while (t != NULL);

	fclose(infile);		/* free up the input file buffer */

#if VERBOSE
	fputs("Beginning merge operation\n",stderr);
#endif

	for (low = 1; low < high; low += MERGEORDER)	/* merge */
	{
		lim = min(low + MERGEORDER - 1, high);
		gopen(low, lim);		/* open files */
		high++;
		makfil(high, tmpfile);
		merge(lim - low + 1, tmpfile);
		fclout(tmpfile);	/* terminate, flush and close file */
		gremov(low, lim);
	}

			/* Now write the sorted output file: */

#if VERBOSE
	fputs("Merge complete. Writing output file.\n",stderr);
#endif

	gname(high, name);	/* create name of result file */
	infile = buffers[0];

	if (fopen(name,infile) == ERROR)
	{
		puts("Something's screwy; I can't open ");
		puts(name);
		exit(-1);
	}

	outfile = buffers[1];
	while (fcreat(argv[2],outfile) == ERROR)
	{
		puts("Can't create "); puts(argv[2]);
		puts("\nEnter another name to call the output: ");
		gets(name2);
		argv[2] = &name2;
	}

	while (fgets(linbuf,infile))
	{
		fputs(linbuf,outfile);
		fputs("\r\n",outfile);
	}
	fclout(outfile);
	fabort(infile->_fd);
	unlink(name);
}


fclout(obuf)
FILE *obuf;
{
	putc(CPMEOF,obuf);
	fflush(obuf);
	fclose(obuf);
}


/*
 * Quick: Quicksort for character lines
 */

quick(nlines)
unsigned nlines;
{
	unsigned i,j, lv[LOGPTR + 1], p, pivlin, uv[LOGPTR + 1];
	int compar();

	lv[1] = 1;
	uv[1] = nlines;
	p = 1;
	while (p > 0)
		if (lv[p] >= uv[p])	/* only 1 element in this subset */
			p--;		/* pop stack			*/
		else
		{
			i = lv[p] - 1;
			j = uv[p];
			pivlin = linptr[j];	/* pivot line		*/
			while (i < j)
			{
			   for (i++; compar(linptr[i],pivlin) < 0; i++)
				;
			   for (j--; j > i; j--)
				if (compar(linptr[j], pivlin) <= 0)
					break;
			   if (i < j)	/* out of order pair		*/
			   {
				temp = linptr[i];
				linptr[i] = linptr[j];
				linptr[j] = temp;
			   }
			}
		j = uv[p];		/* move pivot to position 1 	*/

		temp = linptr[i];
		linptr[i] = linptr[j];
		linptr[j] = temp;

		if ( (i - lv[p]) < (uv[p] - i))
		  {
			lv[p + 1] = lv[p];
			uv[p + 1] = i - 1;
			lv[p] = i + 1;
		  }
		else
		  {
			lv[p + 1] = i + 1;
			uv[p + 1] = uv[p];
			uv[p] = i - 1;
		  }
		p++;
		}			
	return;
}			


/*
 * Compar: Compare two strings; return 0 if equal, -1 if first is
 *	   lexically smaller, or 1 if first is bigger
 */

int compar(lp1, lp2)
unsigned lp1, lp2;
{
	unsigned i, j;

	for (i = lp1, j = lp2; linbuf[i] == linbuf[j]; i++,j++)
	{
		if (linbuf[i] == EOS)
			return 0;
	}

	return (linbuf[i] < linbuf[j]) ? -1 : 1;
}

/*
 * Ptext: output text lines from linbuf onto the buffered output file given
 */

ptext(outfil)
FILE *outfil;
{
	int i;

	for (i = 1; i <= nlines; i++) {
		if (fputs(&linbuf[linptr[i]], outfil) == ERROR) {
			fputs("Error writing output file..disk full?\n",
					stderr);
			exit(-1);
		}
		fputc('\0', outfil);	/* terminate the line */
	}
	return 0;
}


/*
 * Gtext: Get text lines from the buffered input file provided, and
 * 	  place them into linbuf
 */

int gtext(infile)
FILE *infile;
{
	unsigned lbp, len;

	nlines = 0;
	lbp = 1;
	do {
		if ( (len = fgets(&linbuf[lbp], infile)) == NULL)
			break;
		len = strlen(&linbuf[lbp]);
		linbuf[lbp + len - 1] = '\0';
		nlines++;
		linptr[nlines] = lbp;
		lbp += len;	/* drop '\n', but keep NULL at end of string */
	} while ( lbp < (maxtext - MAXLINE) && nlines < MAXPTR);

	return len;		/* return 0 if done with file */
}


/*
 * Makfil: Make a temporary file having suffix 'n' and open it for
 * 	   output via the supplied buffer
 */

FILE *makfil(n,obuf)	/* make temp file having suffix 'n' */
int n;
FILE *obuf;
{
	FILE *fp;
	char name[20];
	gname(n,name);
	if (fcreat(name,obuf) == ERROR) {
		puts("Can't create "); puts(name); exit(-1);
	}
	return 0;
}


/*
 * Gname: Make temporary filename having suffix 'n'
 */

char *gname(n,name)
char *name;
int n;
{
	char tmptext[10];

	strcpy(name,"TEMP");		/* create "TEMPn.$$$"	*/
	strcat(name,itoa(tmptext,n));
	strcat(name,".$$$");
	return name;		/* return a pointer to it	*/
}


/*
 * Itoa: convert integer value n into ASCII representation at strptr,
 * 	 and return a pointer to it
 */

char *itoa(strptr,n)
char *strptr;
int n;
{
	int length;

	if (n < 0)
	{
		*strptr++ = '-';
		strptr++;
		n = -n;
	}

	if (n < 10)
	{
		*strptr++ = (n + '0');
		*strptr = '\0';
		return strptr - 1;
	}
	else
	{
		length = strlen(itoa(strptr, n/10));
		itoa(&strptr[length], n % 10);
		return strptr;
	}
}


/*
 * Gopen: open group of files low...lim
 */

gopen(low, lim)
int lim, low;
{
	int i;

#if VERBOSE
	fprintf(stderr,"Opening temp files %d-%d\n",low,lim);
#endif

	for (i = 1; i <= (lim - low + 1); i++)
	{
		gname(low + i - 1, name);
		if (fopen(name, buffers[i]) == ERROR)
		{
			puts("Can't open: "); puts(name); exit(-1);;
		}
		infil[i] = &buffers[i];
	}
}


/*
 * Remove group of files low...lim
 *		(should use "close" instead of "fabort" for MP/M II)
 */

gremov(low, lim)
int lim, low;
{
	int i;

#if VERBOSE
	fprintf(stderr,"Removing temp files %d-%d\n",low,lim);
#endif
	for (i = 1; i <= (lim - low + 1); i++)
	{
		fabort(infil[i]->_fd);		/* forget about the file */
		gname(low + i - 1, name);
		unlink(name);			/* and physically remove it */
	}
}

/*
 * Fputs: special version that doesn't epand LF's and aborts on error
 */
 
fputs(s,iobuf)
char *s;
FILE *iobuf;
{
	char c;
	while (c = *s++)
		if (putc(c,iobuf) == ERROR)
		{
			fputs("Error on file output\n",stderr);
			exit(-1);
		}
	return OK;
}


/*
 * Merge: merge infil[1]...infil[nfiles] onto outfil
 */

merge(nfiles, outfil)
FILE *outfil;
{
	char *fgets();
	int i, inf, lbp, lp1, nf;

	lbp = 1;
	nf = 0;
	for (i = 1; i <= nfiles; i++)  /* get one line from each file */
		if (fgets(&linbuf[lbp], infil[i]) != NULL)
		{
			nf++;
			linptr[nf] = lbp;
			lbp += MAXLINE;	/* leave room for largest line */
		}

	quick(nf);		/* make initial heap */

	while (nf > 0) {
		lp1 = linptr[1];
		fputs(&linbuf[lp1], outfil);				
		fputc('\0', outfil);
		inf = lp1 / MAXLINE + 1;	/* compute file index */
		if (fgets(&linbuf[lp1],infil[inf]) == NULL)
		{
			linptr[1] = linptr[nf];
			nf--;
		}
		reheap(nf);

	}
	return;
}

		
/*
 * Reheap: propogate linbuf[linptr[1]] to proper place in heap
 */

reheap(nf)
unsigned nf;
{
	unsigned i,j;

	for (i = 1; (i + i) <= nf; i = j)
	{
		j = i + i;
		if (j < nf && compar(linptr[j], linptr[j + 1]) > 0)
			j++;
		if (compar(linptr[i], linptr[j]) <= 0)
			break;

		temp = linptr[i];
		linptr[i] = linptr[j];
		linptr[j] = temp;
	}
	return;
}

/*
 * Just like regular library version, except that NULL is also
 * taken as a string terminator.
 */

char *fgets(s,iobuf)
char *s;
struct _buf *iobuf;
{
	int count, c;
	char *cptr;
	count = MAXLINE;
	cptr = s;
	if ( (c = getc(iobuf)) == CPMEOF || c == EOF) return NULL;

	do {
		if ((*cptr++ = c) == '\n') {
		  if (cptr>s+1 && *(cptr-2) == '\r')
			*(--cptr - 1) = '\n';
		  break;
		}
		if (!c) break;
	 } while (count-- && (c=getc(iobuf)) != EOF && c != CPMEOF);

	if (c == CPMEOF) ungetc(c,iobuf);	/* push back control-Z */
	*cptr = '\0';
	return s;
}

