/****************************************************************************/
/*                                                                          */
/*                 S O R T  -  Callable Sort Facility                       */
/*                             (c) 1987 Ken Harris                          */
/*                                                                          */
/****************************************************************************/
/*                                                                          */
/*      This software is made available on an AS-IS basis. Unrestricted     */
/*      use is granted provided that the copywrite notice remains in tack.  */
/*      The author makes no warranties expressed or implied.                */
/*                                                                          */
/****************************************************************************/

char *copywrite = "sort v1.0 (c) 1987 Ken Harris";

#define  LINT_ARGS
#include <stdio.h>
#ifdef MSC
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <ctype.h>
#endif

#define SORT_CLOSED          0
#define SORT_OPEN	     1
#define SORT_DONE	     2
#define SORT_EOF             3
#define MIN_BUFFER_SIZE   1000
#define MAX_BUFFER_SIZE  32000

struct sort_buffer
{	int   sb_file;			/* file descriptor		*/
	long  sb_fcnt;			/* file block count		*/
	long  sb_fpos;			/* file position		*/
	char *sb_badr;			/* buffer address		*/
	long  sb_bsiz;			/* buffer size in bytes 	*/
	long  sb_rsiz;			/* buffer size in records	*/
	long  sb_rcnt;			/* current record count 	*/
	char *sb_radr;			/* current record address	*/
};

struct key_field
{	struct key_field *kf_next;	/* next key field		*/
	char   kf_type; 		/* field type  'A' = Ascii	*/
					/*	       'I' = Integer	*/
					/*	       'U' = Unsigned	*/
	char   kf_seq;			/* sort order  'A' = Ascending	*/
					/*	       'D' = Descending */
	int    kf_pos;			/* starting position		*/
	int    kf_size; 		/* field size			*/
};

static int  sfile1;			/* temp file #1 		*/
static int  sfile2;			/* temp file #2 		*/
static int  sfile3;			/* temp file #3 		*/

static int  sort_state = 0;		/* current state of the sort	*/  
					/* 0 = Sort not initialized	*/
					/* 1 = Sort is	initialized	*/
					/* 2 = Sort has been done	*/

static int  sort_rec_size;		/* Size of user data record	*/
static long sort_rec_cnt;		/* Record Count 		*/
static int  sort_key_size;		/* Size of user key		*/
static int  sort_krec_size;		/* Size of key + control info	*/   
static long sort_merge_cnt;		/* count records in merge pass	*/
static long sort_next_fpos;		/* position of next file block	*/
static struct key_field *key_spec=NULL; /* sort key specs		*/
static char *sort_key_rec;

static struct sort_buffer sbuf1;	/* Sort buffer #1		*/
static struct sort_buffer sbuf2;	/* Sort buffer #2		*/
static struct sort_buffer sbuf3;	/* Sort buffer #3		*/

long lseek();

/*
 *	sort_init  -  Initialize the sort data
 */

sort_init(rec_size, spec_str)
 int   rec_size;
 char *spec_str;
{
#ifdef MSC
        unsigned _memavl();
#endif
#ifdef CI86
	unsigned coreleft();
#endif
        long  buf_size;
        char *calloc();

	if (sort_state != SORT_CLOSED)
		sort_error("Sort Already Initialized");

	sort_rec_size	= rec_size;
	bld_key_spec(spec_str);

        sort_krec_size  = sort_key_size + sizeof(long);
	sort_krec_size += sort_krec_size % 2;
	sort_rec_cnt	= 0;

        sort_key_rec = (char *) calloc(1,sort_key_size);

#ifdef MSC
        sfile1 = open("SORT1.TMP", O_CREAT|O_RDWR|O_BINARY, S_IREAD|S_IWRITE);
        sfile2 = open("SORT2.TMP", O_CREAT|O_RDWR|O_BINARY, S_IREAD|S_IWRITE);
        sfile3 = open("SORT3.TMP", O_CREAT|O_RDWR|O_BINARY, S_IREAD|S_IWRITE);
#endif
#ifdef CI86
	sfile1 = creat("SORT1.TMP",BUPDATE);
	sfile2 = creat("SORT2.TMP",BUPDATE);
	sfile3 = creat("SORT3.TMP",BUPDATE);
#endif

	if (sfile1 < 0 || sfile2 < 0 || sfile3 < 0)
		sort_error("Can't Open Temp Files");
 
#ifdef MSC
        buf_size  = _memavl();
#endif
#ifdef CI86
	buf_size  = coreleft();
#endif
	if (buf_size > MAX_BUFFER_SIZE) buf_size = MAX_BUFFER_SIZE;
	buf_size  = (buf_size * 3)/4;
	buf_size -= buf_size % sort_krec_size;

	if (buf_size < MIN_BUFFER_SIZE)
		sort_error("Insufficient Memory");

        sbuf1.sb_badr = (char *) calloc(1,buf_size);
	sbuf1.sb_bsiz = buf_size;
	sbuf1.sb_rsiz = buf_size/sort_krec_size;
	sbuf1.sb_radr = sbuf1.sb_badr;
	sbuf1.sb_rcnt = 0;

	sort_state = SORT_OPEN;
}				      

/*
 *	bld_key_spec  -  Build Sort Key Spec
 */
bld_key_spec(s)
 char *s;
{
        struct  key_field *f;
        char   *calloc();

	sort_key_size = 0;
	f = key_spec  = NULL;
	while (*s)
	{
		if (!key_spec)
                        key_spec = f = (struct key_field *) calloc(1,sizeof(struct key_field));
		else
                {       f->kf_next = (struct key_field *) calloc(1,sizeof(struct key_field));
			f = f->kf_next;
		}

		f->kf_next = NULL;
		if (islower(*s)) *s = _toupper(*s);
		if (*s=='A' || *s=='D')
			f->kf_seq = *s++;
		else
			sort_error("Invalid Key Spec - Unknown Sequence");

		if (islower(*s)) *s = _toupper(*s);
		if (*s=='A' || *s=='I' || *s=='U' || *s=='R')
			f->kf_type = *s++;
		else
			sort_error("Invalid Key Spec - Unknown Field Type");

		f->kf_pos=0;
		while (isdigit(*s))
			f->kf_pos = 10 * f->kf_pos + (*s++ - '0');

		if (f->kf_pos < 1)
			sort_error("Invalid Sort Spec - Invalid Starting Position");

		if (*s++ != '.')
			sort_error("Invalid Sort Spec - Missing Field Size");

		f->kf_size=0;
		while (isdigit(*s))
			f->kf_size = 10 * f->kf_size + (*s++ - '0');
		     
		sort_key_size += f->kf_size;

		if (f->kf_pos + f->kf_size - 1 > sort_rec_size)
			sort_error("Invalid Sort Spec - Field Exceeds Record");

		if (*s==',') s++;
	}
}

/*
 *	sort_release  -  Release a record to the sort
 */

sort_release(data)
 char *data;
{
	int  sort_cmp();
	long *k_cnt;
	char *k_dat;


	if (sort_state != SORT_OPEN)
		sort_error("Improper Record Release");

	bld_sort_key(data);

	sort_rec_cnt++;
	sort_write(sfile1, data, sort_rec_size);

	k_cnt  = (long *) sbuf1.sb_radr;
	k_dat  = sbuf1.sb_radr + sizeof(long);
	*k_cnt = sort_rec_cnt;
	memcpy(k_dat, sort_key_rec, sort_key_size);

	sbuf1.sb_rcnt++;
        if (sbuf1.sb_rcnt < sbuf1.sb_rsiz)
		sbuf1.sb_radr += sort_krec_size;
	else
	{	qsort(sbuf1.sb_badr, (int)sbuf1.sb_rcnt, sort_krec_size, sort_cmp);
		sort_write(sfile2, &sbuf1.sb_rcnt, sizeof(long));
		sort_write(sfile2, sbuf1.sb_badr, sbuf1.sb_rcnt*sort_krec_size);
		sbuf1.sb_rcnt = 0;
		sbuf1.sb_radr = sbuf1.sb_badr;
	}
}




/*
 *	sort_cmp  -  sort compare function
 */
 sort_cmp(s1,s2)
  char *s1, *s2;
{
	return(memcmp(s1+sizeof(long),s2+sizeof(long),sort_key_size));
}

/*
 *	bld_sort_key  -  Build the sort key record
 */

bld_sort_key(data)
 char *data;
{
	struct key_field *f;
	char *s,*d;
	int  i, tmp, cmpl;

	d = sort_key_rec;
	f = key_spec;	 

	while (f)
	{
		s = data + f->kf_pos - 1;

		switch(f->kf_type)
		{	case 'A':      
				for (i=0; i < f->kf_size; i++)
					if (f->kf_seq == 'A')
						*d++ = *s++;
					else
						*d++ = ~*s++;
				break;

                        case 'U':
				s += f->kf_size - 1;

                                for (i=0; i < f->kf_size; i++)
					if (f->kf_seq == 'A')
						*d++ = *s--;
					else
						*d++ = ~*s--;
				break;

			case 'I':
				s += f->kf_size - 1;

                                if (f->kf_seq == 'A')
					*d++ = (*s--)^0200;
				else
					*d++ = ~((*s--)^0200);

                                for (i=1; i < f->kf_size; i++)
					if (f->kf_seq == 'A')
						*d++ = *s--;
					else
						*d++ = ~*s--;

                                break;

                        case 'R':
				s += f->kf_size - 1;

				if (*s & 0200)
				{	cmpl = 1;
					tmp  = ~(*s--);
				}		      
				else
				{	cmpl = 0;
					tmp  = (*s--) ^ 0200;
				}

				if (f->kf_seq == 'D') 
				{	cmpl ^= 1;
					*d++  = ~tmp;
				}
				else
					*d++ = tmp;

				for (i=1; i < f->kf_size; i++)
				{	if (cmpl)
						*d++ = ~*s--;
					else
						*d++ = *s--;
				}
                                break;

                        default: break;
		}

		f = f->kf_next;
	}
}

/*
 *	sort_merge  -  Do the merge
 */

sort_merge()
{
	if (sort_state != SORT_OPEN)
		sort_error("Merge Called Out of Sequence");

        if (!sort_rec_cnt)
        {       sort_state = SORT_EOF;
                return;
        }

	merge_init();

        while (sort_state != SORT_DONE)
	{	lseek(sfile2, 0L, 0);
		lseek(sfile3, 0L, 0);

		sort_merge_cnt = 0;
		sort_next_fpos = 0;

		sbuf3.sb_radr = sbuf3.sb_badr;
		sbuf3.sb_rsiz = sbuf3.sb_bsiz / sort_krec_size;
		sbuf3.sb_rcnt = 0;

                one_merge_pass();

		if (sort_merge_cnt != sort_rec_cnt)
			sort_error("Merge Count Error");

		if (sort_state != SORT_DONE)
		{	sbuf1.sb_file = sbuf3.sb_file;
			sbuf3.sb_file = sbuf2.sb_file;
			sbuf2.sb_file = sbuf1.sb_file;
		}
	}

	sort_merge_cnt = 0;
	sort_next_fpos = 0;
        sbuf1.sb_bsiz += sbuf2.sb_bsiz + sbuf3.sb_bsiz;
        sbuf1.sb_rsiz =  sbuf1.sb_rcnt = 0;
        sbuf1.sb_radr = NULL;
	lseek(sbuf1.sb_file, 0L, 0);
        get_merge_block(&sbuf1);
}

static one_merge_pass()
{
        char *r1, *r2, *sort_read();
	long block_size;

	while (sort_merge_cnt < sort_rec_cnt)
	{	get_merge_block(&sbuf1);
		get_merge_block(&sbuf2);

		if (sbuf1.sb_fcnt >= sort_rec_cnt)
		{	sort_state = SORT_DONE;
			return;
		}

		block_size = sbuf1.sb_fcnt + sbuf2.sb_fcnt;
		sort_write(sbuf3.sb_file, &block_size, sizeof(long));

		r1 = sort_read(&sbuf1);
                r2 = sort_read(&sbuf2);

		while (r1 && r2)
		{	switch (sort_cmp(r1,r2))
			{	case -1: merge_write(&sbuf1);
					 r1 = sort_read(&sbuf1);
					 break;

				case  0: merge_write(&sbuf1);
					 merge_write(&sbuf2);
					 r1 = sort_read(&sbuf1);
					 r2 = sort_read(&sbuf2);
					 break;

				case  1: merge_write(&sbuf2);
					 r2 = sort_read(&sbuf2);
					 break;
			}
		}
		while (r1)
		{	merge_write(&sbuf1);
			r1 = sort_read(&sbuf1);
		}

		while (r2)
		{	merge_write(&sbuf2);
			r2 = sort_read(&sbuf2);
		}

		if (sbuf3.sb_rcnt)
		{	sort_write(sbuf3.sb_file, sbuf3.sb_badr, sbuf3.sb_rcnt*sort_krec_size);
			sbuf3.sb_rcnt = 0;
			sbuf3.sb_radr = sbuf3.sb_badr;
		}
        }
}

/*
 *	merge_init  -  Merge Initialization
 */

merge_init()
{
	unsigned int buf_size;


	if (sbuf1.sb_rcnt)
	{	qsort(sbuf1.sb_badr, (int)sbuf1.sb_rcnt, sort_krec_size, sort_cmp);
		sort_write(sfile2, &sbuf1.sb_rcnt, sizeof(long));
		sort_write(sfile2, sbuf1.sb_badr, sbuf1.sb_rcnt*sort_krec_size);
		sbuf1.sb_rcnt = 0;
		sbuf1.sb_radr = sbuf1.sb_badr;
	}

	buf_size  = sbuf1.sb_bsiz / 3;
	buf_size -= buf_size % sort_krec_size;		 

	sbuf1.sb_bsiz = buf_size;
	sbuf2.sb_bsiz = buf_size;
	sbuf3.sb_bsiz = buf_size;

	sbuf2.sb_badr = sbuf1.sb_badr + buf_size;
	sbuf3.sb_badr = sbuf2.sb_badr + buf_size;

        sbuf1.sb_rsiz = sbuf1.sb_rcnt = 0;
        sbuf2.sb_rsiz = sbuf2.sb_rcnt = 0;
        sbuf3.sb_rsiz = sbuf3.sb_rcnt = 0;

        sbuf1.sb_radr = NULL;
        sbuf2.sb_radr = NULL;
        sbuf3.sb_radr = NULL;

        sbuf1.sb_file = sfile2;
	sbuf1.sb_fcnt = 0;
	sbuf1.sb_fpos = 0L;

	sbuf2.sb_file = sfile2;
	sbuf2.sb_fcnt = 0;
	sbuf2.sb_fpos = 0L;

	sbuf3.sb_file = sfile3;
	sbuf3.sb_fcnt = 0;
	sbuf3.sb_fpos = 0L;
}

/*
 *	sort_return  -	return a record from the sort
 */

char *sort_return(buf)
 char *buf;
{      
	long *key;
        char *sort_read();

        if (sort_state == SORT_EOF)
	{	sort_close();
		return(NULL);
	}

	if (sort_state != SORT_DONE)
		sort_error("Sort Return Sequence Error");

        key = (long *) sort_read(&sbuf1);

	if (!key)
	{	sort_close();
		return(NULL);
	}

        lseek(sfile1, (long)((*key-1)*sort_rec_size), 0);
        if (read(sfile1, buf, sort_rec_size) != sort_rec_size)
		sort_error("Return Read Error");

        return(buf);
}

/*
 *	sort_read  -  Read record from merge buffer
 */

static char *sort_read(buf)
 struct sort_buffer *buf;
{			   
	int cnt;

	if (buf->sb_rcnt < buf->sb_rsiz)
	{	buf->sb_rcnt++;
		buf->sb_radr += sort_krec_size;
		return(buf->sb_radr);
	}

	if (!buf->sb_fcnt) return(NULL);

	lseek(buf->sb_file, buf->sb_fpos, 0);

	buf->sb_rsiz = buf->sb_bsiz / sort_krec_size;
	if (buf->sb_rsiz > buf->sb_fcnt)
		buf->sb_rsiz = buf->sb_fcnt;

	if (read(buf->sb_file, buf->sb_badr, buf->sb_rsiz * sort_krec_size) <= 0)
		sort_error("Temp File Read Error");
	
	buf->sb_fcnt   -= buf->sb_rsiz;
	buf->sb_fpos   += buf->sb_rsiz * sort_krec_size;
	buf->sb_rcnt	= 1;
	buf->sb_radr	= buf->sb_badr;

	return(buf->sb_radr);
}

/*
 *	get_merge_block  -  Read up the next merge block
 */
get_merge_block(buf)
 struct sort_buffer *buf;
{
	buf->sb_rsiz = 0;
	buf->sb_rcnt = 0;
	buf->sb_radr = 0;

        if (sort_merge_cnt >= sort_rec_cnt)
	{	buf->sb_fcnt = 0;
		return;
	}

	lseek(buf->sb_file, sort_next_fpos, 0);
	if (read(buf->sb_file, &buf->sb_fcnt, sizeof(long)) <= 0)
		sort_error("Merge Read Error");

	buf->sb_fpos	= sort_next_fpos + sizeof(long);
	sort_merge_cnt += buf->sb_fcnt;
	sort_next_fpos += sizeof(long) + buf->sb_fcnt * sort_krec_size;
}
                                         
/*
 *	merge_write  -	merge output
 */

static merge_write(buf)
 struct sort_buffer *buf;
{
	memcpy(sbuf3.sb_radr, buf->sb_radr, sort_krec_size);

	sbuf3.sb_rcnt++;
	if (sbuf3.sb_rcnt < sbuf3.sb_rsiz)
		sbuf3.sb_radr += sort_krec_size;
	else
	{	sort_write(sbuf3.sb_file, sbuf3.sb_badr, sbuf3.sb_rcnt*sort_krec_size);
		sbuf3.sb_rcnt = 0;
		sbuf3.sb_radr = sbuf3.sb_badr;
	}
}

/*
 *	sort_close  -  Cleanup after a sort
 */

static sort_close()
{
	struct key_field *f, *t;

        if (sort_state == SORT_CLOSED) return;

	close(sfile1);
	close(sfile2);
	close(sfile3);

	unlink("SORT1.TMP");
	unlink("SORT2.TMP");
	unlink("SORT3.TMP");

        f = key_spec;
	while (f)
	{	t = f->kf_next;
		free(f);
		f = t;
	}

        free(sort_key_rec);
        free(sbuf1.sb_badr);

	sort_state = SORT_CLOSED;
}

/*
 *	sort_write  -  Write a reord to temp file
 */

static sort_write(fd, buf, cnt)
 int   fd, cnt;
 char *buf;    
{
	if (write(fd, buf, cnt) < cnt)
		sort_error("Write Error, Check Disk Space");
}

/*
 *	sort_error  -	Sort Error Exit
 */

static sort_error(text)
 char *text;
{
	fprintf(stderr,"\n\nSort Error: %s\7\n\n",text);
	exit(1);
}
