/*---------------------------------------------------------------------------
 * AVLSORT From,To,COLSTART/k,WIDTH/k,CASE/s,REVERSE/s
 *
 * AMIGA Usage: AvlSort <infile> [TO outfile] [starting at column] \
 *			[CASE-sensitive] [REVERSE order]
 *
 * MSDOS Usage: AVLSORT [infile] [outfile] [/COL n] [/WID n] [/U] [/R]
 *	infile	default is stdin
 *	outfile	default is stdout
 *	/COL n	start column; default is 1 (may use /C:n)
 *	/WID n	column width; default is all (may use /W:n)
 *	/U	unique case (case-sensitive)
 *	/R	reverse order
 *
 * Copyright (C) 1990 by Robert L. Pyron
 *
 * "This software may be used at will, provided that all credits
 * and style be left in place, and that its distribution is not
 * restricted."
 *
 * Robert L. Pyron, 24 June 90
 * BIX: rpyron
 *---------------------------------------------------------------------------
 */

#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "avl.h"

#if	defined(LATTICE) && defined(AMIGA)	/* We link with ARP */
 #include <Arp/ArpBase.h>
#else
 #include <malloc.h>
 #define ArpAlloc(n) malloc( (unsigned short) (n) )
#endif

/*---------------------------------------------------------------------------
 * Typedefs and defines
 *---------------------------------------------------------------------------
 */

#define FALSE	0
#define TRUE	1
#define	TEXTMAX	32768

typedef	unsigned char	UCHAR;
typedef	unsigned short	USHORT;
typedef	unsigned long	ULONG;
typedef	UCHAR *		PCHAR;
typedef void *		PVOID;

typedef	struct _MYNODE {
	AVLNODE	avlnode;
	PCHAR	text;
	ULONG	textlen;
	ULONG	lineno;
	} MYNODE;
typedef	MYNODE		*PNODE;
#define	SZOF_MYNODE	sizeof(MYNODE)

typedef	int  (*PFNCOMPARE) __ARGS((const char *, const char *, unsigned int));


/*---------------------------------------------------------------------------
 * Prototypes
 *---------------------------------------------------------------------------
 */

int	cmdparse __ARGS(( int argc, char **argv ));
PVOID	myalloc __ARGS(( ULONG size ));
PNODE	newnode __ARGS(( PCHAR text, ULONG textlen, ULONG lineno ));
void	printtree __ARGS(( PNODE p ));

int	cmprtc  __ARGS(( void *keyP, AVLNODE *nodeP ));
AVLNODE *mknode __ARGS(( AVLTREE *treeP, void *keyP, void *dataP, AVLNODE *enodeP ));
void	rmnode  __ARGS(( AVLTREE *treeP, AVLNODE *nodeP ));

/*---------------------------------------------------------------------------
 * Global data
 *---------------------------------------------------------------------------
 */

#if	defined(LATTICE) && defined(AMIGA)

	/* Template and Extended Help for GADS(), from ARP */

extern	struct ArpBase	*ArpBase;

char	*CLI_Template	= "From,To,COLSTART/k,WIDTH/k,CASE/s,REVERSE/s";
char	*CLI_Help	= "Usage: AvlSort <infile> [TO outfile] "
			  "[starting at column] [CASE-sensitive]";

#else	/* Microsoft, MSDOS or OS2 */

char	*program	= "AVLSORT";
char	*usage		= "Usage: %s [infile] [outfile] [/COL n] [/WID n] [/U] [/R]";

#endif

	/* User may specify the following parameters on command line: */

char	*infile		= NULL;		/* input file name, default stdin */
char	*outfile	= NULL;		/* output file name, default stdout */
long	colstart	= 0;		/* start column, default 1 */
long	colwidth	= TEXTMAX-1;	/* width, default full line */
int	caseflag	= 0;		/* case sensitivity, default off */
int	reverseflag	= 0;		/* reverse sort, default off */

	/* This is our AVL tree. */

AVLTREE	avlTree = {
	NULL,
	(PFNCMPRTC)cmprtc,
	(PFNMKNODE)mknode,
	(PFNRMNODE)rmnode
	};
#define	avlRoot	((PNODE)avlTree.t_rootP)

	/*-----------------------------------------------------------
	 * Pointer to string comparison function. If case sensitivity
	 * is off (default), use strnicmp().  Otherwise use strncmp().
	 *------------------------------------------------------------
	 */

PFNCOMPARE	pfnCompare = (PFNCOMPARE) strnicmp;


/*---------------------------------------------------------------------------
 * Notice int return type, we are returning a value to Arp startup code.
 *---------------------------------------------------------------------------
 */

int	main( int argc, char **argv )
{
register int	byte;
	int	lastch;
	long	line_number	= 0;
	PNODE	pnew;
	int	status		= 10;	/* assume the worst */
register PCHAR	textbuf;
	ULONG	textlen;

	if ( (textbuf = ArpAlloc(TEXTMAX)) == NULL)
	{
		fprintf( stderr, "?? Out of memory\n" );
		goto hell;
	}

	/*-------------------------------
	 * Get options from command line.
	 *-------------------------------
	 */

	if (cmdparse(argc,argv))
		goto hell;

	if (--colstart < 0)	colstart = 0;	/* convert to zero-based */
	if (colwidth <= 0)	colwidth = TEXTMAX - 1;
	if (caseflag)		pfnCompare = (PFNCOMPARE) strncmp;

	/*--------------------------------------------
	 * Redirect stdin to named file, if specified.
	 *--------------------------------------------
	 */

	if (infile && *infile)
	{
		/* redirect stdin to specified file */

		FILE	*fp = freopen( infile, "r", stdin );

		if (fp == NULL)
		{
			fprintf( stderr,
				 "?? Cannot open file %s for input\n",
				 infile );
			goto hell;
		}
		else if (fp != stdin)
		{
			fprintf( stderr, "?? freopen() error\n" );
			goto hell;
		}
	}

	/*-----------------------------------------------------
	 * Read file, putting each line in the appropriate bin.
	 *-----------------------------------------------------
	 */

	lastch = (int) ' ';
	while ( lastch != EOF )
	{
		/*----------------------------------------------------------
		 * Read a line.  We assume that file is opened in text mode.
		 *----------------------------------------------------------
		 */

		textlen = 0;
		while ( byte = getchar() )
		{
			if (byte == EOF || byte == '\n' || byte == '\r')
				break;

			textbuf[textlen++] = (UCHAR) byte;
			if (textlen == TEXTMAX)
			{
				fprintf( stderr,
					"?? Fatal error: line exceeds %lu bytes.\n",
					TEXTMAX-1 );
				goto hell;
			}
		}
		if (byte == EOF && textlen == 0)
			break;

		lastch = byte;
		textbuf[textlen] = (UCHAR) '\0';

		/*---------------------------------------------
		 * Allocate space for line, and insert in tree.
		 *---------------------------------------------
		 */

		if ( (pnew = newnode(textbuf,textlen,line_number)) == NULL )
			goto hell;

		switch (avlinsert(&avlTree, pnew, NULL) )
		{
		case -1:	/* error */
			fprintf( stderr, "?? Cannot insert line (%lu): %s\n",
				pnew->lineno, pnew->text );
			break;

		case  0:	/* success */
			break;

		case  1:	/* duplicate key */
			fprintf( stderr, "?? Duplicate key (%lu): %s\n",
				pnew->lineno, pnew->text );
			break;
		}

		line_number++;
	}				

	/*---------------------------------------------
	 * Redirect stdout to named file, if specified.
	 * Close stdin because may be same file.
	 *---------------------------------------------
	 */

	if (outfile && *outfile)
	{
		/* redirect stdout to specified file */

		FILE	*fp = freopen( outfile, "w", stdout );
		if (fp == NULL)
		{
			fprintf( stderr,
				 "?? Cannot open file %s for output\n",
				 outfile );
			goto hell;
		}
		else if (fp != stdout)
		{
			fprintf( stderr, "?? freopen() error\n" );
			goto hell;
		}
	}

	/*------------
	 * Print file.
	 *------------
	 */

	printtree( avlRoot );

	status = 0;	/* ok */

hell:
	return( status );
}

#if	defined(LATTICE) && defined(AMIGA)	/* Amiga; we link with ARP */

/*--------------------------------------------------------------------
 * We link with S. Vigna's ARP startup code, which calls GADS() for us
 * and returns the result as argv[].  We can ignore argc, which is the
 * number of arguments actually supplied on the command line.
 *--------------------------------------------------------------------
 */

int	cmdparse( int argc, char **argv )
{
	char	*junk = NULL;
	int	error = 0;

	if (argc <= 0)
	{
		/* Workbench */
		return( -1 );
	}
	else if (argc > 7)
	{
		fprintf( stderr,
			 "?? Too many arguments\n%s\n%s\n",
			 CLI_Help, CLI_Template );
		return( -1 );
	}

	infile = argv[1];
	outfile = argv[2];
	if (argv[3])
	{
		colstart = strtol( argv[3], &junk, 10 );
		if (junk && *junk)
		{
			fprintf( stderr,
				 "?? Invalid digit \"%c\" for COLSTART\n",
				 *junk );
			error = -1;
		}
	}
	if (argv[4])
	{
		colwidth = strtol( argv[4], &junk, 10 );
		if (junk && *junk)
		{
			fprintf( stderr,
				 "?? Invalid digit \"%c\" for WIDTH\n",
				 *junk );
			error = -1;
		}
	}
	caseflag = (argv[5] != NULL);
	reverseflag = (argv[6] != NULL);

	return( error );
}

#else		/* Microsoft MSDOS or OS2; we have to roll our own parser */

/*-------------------------------------------------------------------
 *	Usage: AVLSORT [infile] [outfile] [/COL n] [/WID n] [/U] [/R]
 *	    infile	default is stdin
 *	    outfile	default is stdout
 *	    /COL n	start column; default is 1 (may use /C:n)
 *	    /WID n	column width; default is all (may use /W:n)
 *	    /U		unique case (case-sensitive)
 *	    /R		reverse order
 *-------------------------------------------------------------------
 */

#define	MATCHn(s,t,n)	(strnicmp(s,t,n) == 0)

int	cmdparse( int argc, char **argv )
{
	char	c, *p, *colon, *junk = NULL;
	int	i, helpflag = FALSE;
	int	error = 0;

	/*------------------
	 * Get program name.
	 *------------------
	 */

	if (argc > 0)
	{
		if   (!argv[0] || !*argv[0])	    ;  /* use default */
		else if (p = strrchr(argv[0],'\\')) program = p+1;
		else if (p = strrchr(argv[0],':'))  program = p+1;
		else				    program = argv[0];

		strupr(program);
	}

	/*------------------------------
	 * First pass: look for options.
	 *------------------------------
	 */

	for (i = 1; (i < argc) && !helpflag ; i++)
	{
		junk = NULL;

		if ( !(p = argv[i]) || !(c = *argv[i]) )
			continue;		/* no argument */

		else if ( c == '?')		/* user requested help */
			helpflag = TRUE;

		else if (c != '/' && c != '-')	/* not a switch */
			continue;

		else if (MATCHn(p+1,"COL",3) && (i+1) < argc)
		{
			colstart = strtol( argv[i+1], &junk, 0 );
			*argv[i+1] = 0;		/* ignore on second pass */
		}

		else if ( MATCHn(p+1,"C",1) && (colon = strchr(p+1,':')) )
			colstart = strtol( colon+1, &junk, 0 );

		else if (MATCHn(p+1,"WID",3) && (i+1) < argc)
		{
			colwidth = strtol( argv[i+1], &junk, 0 );
			*argv[i+1] = 0;		/* ignore on second pass */
		}

		else if ( MATCHn(p+1,"W",1) && (colon = strchr(p+1,':')) )
			colwidth = strtol( colon+1, &junk, 0 );

		else if ( MATCHn(p+1,"U",1) )
			caseflag = TRUE;

		else if ( MATCHn(p+1,"R",1) )
			reverseflag = TRUE;

		else
			helpflag = 1;

		if ( junk && *junk )		/* numeric field error */
			helpflag = TRUE;
	}

	/*----------------------------------
	 * Second pass: look for file names.
	 *----------------------------------
	 */

	for (i = 1; (i < argc) && !helpflag ; i++)
	{
		if (!argv[i] || !*argv[i])	/* no argument here */
			continue;

		else if (*argv[i] == '-' || *argv[i] == '/')	/* switch */
			continue;

		else if (!infile)
			infile = strupr(argv[i]);

		else if (!outfile)
			outfile = strupr(argv[i]);

		else
			helpflag = 1;
	}

	/*-----------------------
	 * Help requested? Error?
	 *-----------------------
	 */

	if (helpflag)
	{
		fprintf( stderr, usage, program );
		return( -1 );
	}

	if (outfile && strcmp(outfile,"=") == 0)
		outfile = infile;

	return( 0 );
}

#endif	/* cmdparse() */

/*---------------------------------------------------------------------
 * We request relatively large chunks of memory, and hand out pieces as
 * necessary.  We let Arp keep track, so we need not bother to free the
 * memory when we are done.
 *---------------------------------------------------------------------
 */

#define	ALLOCMAX 0xFF00		/* largest size we can handle */
#define	ALLOCMIN 1024		/* minimum size to allocate */

PVOID	myalloc( ULONG size )
{
	static	PCHAR	pointer	= NULL;
	static	ULONG	offset	= 0;
	PVOID	p	= NULL;

	/*-------------------------------------------------------
	 * Round requested size up to a multiple of 4.
	 * This should be adequate for alignment of most objects.
	 *-------------------------------------------------------
	 */

	size = (size + 3) & 0xFFFFFFFCL;

	/*----------------------------------------------------------
	 * There is a limit to how much we can allocate at one time.
	 *----------------------------------------------------------
	 */

	if (size > ALLOCMAX)
		return( NULL );

	/*------------------------------------------------------
	 * For objects above a certain minimum size, we allocate
	 * a separate block.  This should help when we have a
	 * mixture of large and small objects.
	 *------------------------------------------------------
	 */

	if ( (size >= ALLOCMIN) && (p = ArpAlloc(size)) )
		return( p );

	/*-------------------------------------------------
	 * If there is no room for new object in previously
	 * allocated block, we allocate another block.
	 *-------------------------------------------------
	 */

	if ( !pointer || (size > ALLOCMAX-offset) )	/* prevent overflow */
	{
		pointer = ArpAlloc(ALLOCMAX);
	 	offset = 0;
	}

	/*-----------------------------------------------
	 * If there is a block hanging around, it should
	 * have room for this object.  Carve off a piece.
	 *-----------------------------------------------
	 */

	if (pointer)
	{
		p = (PVOID) (pointer + offset);
		offset += size;
		return( p );
	}
	else
		return( NULL );
}

/*---------------------------------------------------------------------------
 * Initialize a new node to be inserted in the tree.
 *---------------------------------------------------------------------------
 */

PNODE	newnode( PCHAR text, ULONG textlen, ULONG lineno )
{
	PCHAR	ptext;
	PNODE	pnew	 = NULL;
static	AVLNODE	avldummy = { NULL, NULL, 0 };

	if ( (ptext = myalloc(textlen+1)) && (pnew = myalloc(SZOF_MYNODE)) )
	{
		strcpy( ptext, text );
		pnew->avlnode	= avldummy;
		pnew->text	= ptext;
		pnew->textlen	= textlen;
		pnew->lineno	= lineno;
	}
	return( pnew );
}

/*---------------------------------------------------------------------------
 *
 *	int cmprtc( keyP, nodeP )
 *		void	*keyP;
 *		AVLNODE	*nodeP;
 *	
 *	CMPRTC compares a given key against a key associated
 *	with a node.
 *
 *	keyP	the address of a key (interpreted in
 *		whatever way is applicable)
 *	nodeP	the address of an AVLNODE structure
 *		to which the key should be compared.
 *
 *	It shall return
 *		-1 if keyP is less than the key associated with nodeP key;
 *		 0 if they match;
 *		+1 if keyP is greater than the key associated with nodeP.
 *
 *	IMPLEMENTATION NOTE:
 *	For this application, keyP points to a previously-allocated
 *	AVL node.
 *---------------------------------------------------------------------------
 */

int	cmprtc( void *keyP, AVLNODE *nodeP )
{
	PNODE	kp	= (PNODE) keyP;
	PNODE	np	= (PNODE) nodeP;
	PCHAR	ktext	= kp->text + colstart;
	PCHAR	ntext	= np->text + colstart;
	int	khastext = (kp->textlen > colstart);
	int	nhastext = (np->textlen > colstart);
	int	cmp;

	/*---------------------------------------------------------------*/
	/* If both lines are long enough, apply the comparison function. */
	/* If only one line is long enough, it should precede the other. */
	/* If neither line is long enough, call them equal (for now).	 */
	/*---------------------------------------------------------------*/

	if (khastext && nhastext)
		cmp = (*pfnCompare)( ktext, ntext, colwidth );
	else
	{
		/*--------------------------------------*/
		/* k n -> cmp				*/
		/* ----------				*/
		/* 0 0     0	equal			*/
		/* 1 0    -1	key has precedence	*/
		/* 0 1     1	node has precedence	*/
		/* 1 1    n/a	handled by if(), above	*/
		/*--------------------------------------*/

		cmp = nhastext - khastext;
	}

	/*-------------------------------------------------------*/
	/* If the lines are equal, then the line that came first */
	/*	in the original file should have precedence.	 */
	/* Otherwise, make sure we return 1 or -1.		 */
	/*-------------------------------------------------------*/

	if (cmp == 0)
	{
		/*------------------------------------------------------*/
		/* k < n  : (0 - 1) = -1	key has precedence	*/
		/* k > n  : (1 - 0) = 1		node has precedence	*/
		/* k == n : (0 - 0) = 0		error, should not match	*/
		/*------------------------------------------------------*/

		return (kp->lineno > np->lineno) - (kp->lineno < np->lineno);
	}
	else
	{
		/*------------------------------------------------------*/
		/* cmp == 0 : n/a		handled by if(), above	*/
		/* cmp < 0  : (0 - 1) = -1	key has precedence	*/
		/* cmp > 0  : (1 - 0) = 1	node has precedence	*/
		/*------------------------------------------------------*/

		return (cmp > 0) - (cmp < 0);
	}
}

/*---------------------------------------------------------------------------
 *	AVLNODE *mknode( treeP, keyP, dataP, enodeP )
 *		AVLTREE	*treeP;
 *		void	*keyP;
 *		void	*dataP;
 *		AVLNODE	*enodeP;
 *	
 *	MKNODE creates a node on behalf of tree insertion.
 *	
 *	treeP	the address of the tree description structure
 *	keyP	the address of any key associated with the node
 *	dataP	the address of any data associated with the node
 *	enodeP	the address of any node that already exists in
 *		the tree for keyP.
 *
 *	If enodeP is not NULL, then this routine is being called
 *	to replace the data in an existing node.  It can signal
 *	its unwillingness to do this by returning NULL as its
 *	return value, otherwise it must return the address of the
 *	existing node.  Otherwise (if enodeP is NULL), this
 *	routine should allocate (or otherwise create) a new node
 *	and fill it in.
 *	
 *	MKNODE shall return the address of the node constructed,
 *	or NULL if no node was made.
 *
 *	IMPLEMENTATION NOTE:
 *	For this application, keyP points to a previously-allocated
 *	AVL node.
 *---------------------------------------------------------------------------
 */

AVLNODE *mknode( AVLTREE *treeP, void *keyP, void *dataP, AVLNODE *enodeP )
{
	if (enodeP)
		return( (AVLNODE *)NULL );
	else
		return( (AVLNODE *)keyP );
}

/*---------------------------------------------------------------------------
 *	void rmnode( treeP, nodeP )
 *		AVLTREE	*treeP;
 *		AVLNODE	*nodeP;
 *	
 *	RMNODE is called to delete a node and its information.
 *	
 *	treeP is the address of the tree description structure;
 *	nodeP is the address of the node to delete.
 *	
 *	It is called when a node is removed from a tree; it may
 *	do what it likes and does not return any information.
 *
 *	IMPLEMENTATION NOTE:
 *	For this application, we cannot do anything.
 *---------------------------------------------------------------------------
 */

void	rmnode( AVLTREE *treeP, AVLNODE *nodeP )
{
}

/*---------------------------------------------------------------------------
 * Print the tree to stdout (which may have been redirected).
 *---------------------------------------------------------------------------
 */

void	printtree( PNODE p )
{
	if (!p)
		return;

	else if (reverseflag)
	{
		printtree( (PNODE)(p->avlnode.n_rightP) );
		puts( p->text );
		printtree( (PNODE)(p->avlnode.n_leftP) );
	}

	else
	{
		printtree( (PNODE)(p->avlnode.n_leftP) );
		puts( p->text );
		printtree( (PNODE)(p->avlnode.n_rightP) );
	}
}

