/* dirutil.c - MS-DOS directory reading routines
 *
 * Bdale Garbee, N3EUA, Dave Trulli, NN2Z, and Phil Karn, KA9Q
 * Directory sorting by Mike Chepponis, K3MC
 * adapted for ATARI ST & cleaned up by Rob Janssen, PE1CHL
 * added more shell commands by PE1CHL
 */

#include <stdio.h>
#include <ctype.h>

#ifdef MSDOS
# define IS_ERROR(x)	(x) == -1	/* error return from MSDOS */
# ifdef	MSC
#  include <sys/types.h>
#  include <sys/stat.h>
#  define ST_HIDDEN	_A_HIDDEN	/* hidden from search */
#  define ST_DIRECT	_A_SUBDIR	/* directory */
# else
#  ifdef __TURBOC__
#   define ST_HIDDEN	FA_HIDDEN	/* Hidden from search */
#   define ST_DIRECT	FA_DIREC	/* Directory */
#  else	/* AZTEC */
#   include <stat.h>
#   define st_mode	st_attr		/* what's in a name? */
#  endif
# endif
#endif

#ifdef ATARI_ST
# ifdef MWC
#  include <stat.h>
#  include <osbind.h>
#  define bdos		gemdos		/* Atari OS call */
#  define dos(a,b,c,d,e,f) gemdos(a,d,c) /* only valid for FIND func */
#  define ST_HIDDEN	S_IJHID		/* Hidden from search */
#  define ST_DIRECT	S_IJDIR		/* Directory */
# endif
# ifdef __TURBOC__
#  include <tos.h>
#  include <ext.h>
#  define ST_HIDDEN	FA_HIDDEN	/* Hidden from search */
#  define ST_DIRECT	FA_DIREC	/* Directory */
# endif
# define IS_ERROR(x)	(x) < 0		/* error return from gemdos */
#endif

#include "global.h"

#define SET_DTA		0x1a
#define FIND_FIRST	0x4e
#define FIND_NEXT	0x4f

struct dirent {
	char rsvd[21];
	char attr;
	short ftime;
	short fdate;
	long fsize;
	char fname[13];
};
#define NULLENT (struct dirent *)0

struct dirsort {
	struct dirsort *prev;
	struct dirsort *next;
	struct dirent *direntry;
};
#define NULLSORT (struct dirsort *)0

#ifdef ATARI_ST
static char *buffer;			/* buffer for filecopy */
static int  bufsiz;			/* size of buffer */
#endif

static char nofile[] = "No file \"%s\"\n";
extern char cantopen[];

static int cpfile(),cptodir(),filedelete(),dirremove(),
	   wildcard(),getarcdir();
static void dir_sort(),format_dir(),format_fname(),diskfree(),free_clist();
static void commas();
int iswild(),isdir();
char *basename();

/* Change working directory */
docd(argc,argv)
int argc;
char *argv[];
{
	char dirname[128];
#ifdef MSDOS
	char *getcwd();
#endif

	if(argc > 1){
		if(IS_ERROR(chdir(argv[1]))){
			printf("Can't change directory\n");
			return 1;
		}
	}

#ifdef MSDOS
# if (defined(MSC) || defined(__TURBOC__))
	if(getcwd(dirname,sizeof(dirname)-1) != NULLCHAR){
# else
	dirname[0] = '\\';
	if(getcwd(dirname + 1,sizeof(dirname)-2) != NULLCHAR){
# endif
#endif
#ifdef ATARI_ST
	dirname[0] = Dgetdrv() + 'A';
	dirname[1] = ':';
	if(Dgetpath(dirname + 2,0) == 0){
		if(dirname[2] == '\0'){
			dirname[2] = '\\';
			dirname[3] = '\0';
		}
#endif
		cleanup_fname(dirname);
		printf("%s\n",dirname);
	}
	return 0;
}

/* copy files */
docopy(argc,argv)
int argc;
char *argv[];
{
    int stat;
    int i;
#ifdef ATARI_ST
    long maxbuf;
#endif

    for (i = 1; i < argc; i++)
	cleanup_fname(argv[i]);

    if (iswild(argv[argc - 1])){
	printf("Last arg cannot be wildcard\n");
	return 1;
    }

#ifdef ATARI_ST
    /* On Atari ST, use a big buffer allocated from GEMDOS */

    if ((maxbuf = (long) Malloc(-1L)) < 512L)	/* ask maximum buffer size */
    {
	printf("Cannot allocate buffer\n");
	return(1);
    }

    bufsiz = maxbuf & 32767;		/* limit bufsize to safe value */
    bufsiz &= ~512L;			/* round down to 512-byte multiple */

    if ((long) (buffer = (char *) Malloc((long) bufsiz)) < 0) {
	printf("GEMDOS error during buffer allocation\n");
	return(1);
    }
#endif

    if (!isdir(argv[argc - 1]))		/* check type of destination */
    {
	if (argc == 3 && !iswild(argv[1])){ /* file-to-file copy */
	    stat = cpfile(argv[1],argv[2]);
	} else {
	    printf("Last arg must be a directory\n");
	    stat = 1;
	}
    } else {
	stat = wildcard(argc - 1,argv,cptodir,argv[argc - 1]);
    }

#ifdef ATARI_ST
    Mfree(buffer);			/* free the buffer */
#endif

    return(stat);
}

/* delete files */
dodelete(argc,argv)
int argc;
char *argv[];
{
    return wildcard(argc,argv,filedelete,NULL);
}

/* List directory to console. [-/]w option selects "wide" format */
dodir(argc,argv)
int argc;
char *argv[];
{
    char *path;
    int full = 1;

    if (argc > 1 &&
	(argv[1][0] == '-' || argv[1][0] == '/') && argv[1][1] == 'w') {
	full = -1;
	argv++;
	argc--;
    }

    if(argc >= 2){
	path = argv[1];
    } else {
	path = "*.*";
    }
    getdir(path,full,0,stdout);
    return 0;
}

/* make directories */
domkdir(argc,argv)
int argc;
char *argv[];
{
    int i;
    int st = 0;
    char force = 0;

    for (i = 1; i < argc; i++){
	if (!strcmp(argv[i],"-f")){		/* -f flag = force */
	    force = 1;
	    continue;
	}

	cleanup_fname(argv[i]);

	if (iswild(argv[i]) ||(mkdir(argv[i]) && !force)){
	    printf("Cannot make directory \"%s\"\n",argv[i]);
	    st = 1;
	}
    }

    return st;
}

/* rename a file */
dorename(argc,argv)
int argc;
char *argv[];
{
    cleanup_fname(argv[1]);
    cleanup_fname(argv[2]);

    if (iswild(argv[1]) || iswild(argv[2])){
	printf("No wildcard file rename\n");
	return 1;
    }

    if (rename(argv[1],argv[2])){
	printf("Cannot rename \"%s\" to \"%s\"\n",argv[1],argv[2]);
	return 1;
    }
    return 0;
}

/* remove directories */
dormdir(argc,argv)
int argc;
char *argv[];
{
    return wildcard(argc,argv,dirremove,NULL);
}

/* type a file on the console */
/* number of lines can be given as second parameter, negative value */
/* will be taken as tail lines. */

dotype(argc,argv)
int argc;
char *argv[];
{
    FILE *fp;
    int c;
    int lines = 0;

    cleanup_fname(argv[1]);

    if (iswild(argv[1])){
	printf("No wildcard file type\n");
	return 1;
    }

    if (argc > 2)
	if ((lines = atoi(argv[2])) == 0)
	    lines = -24;

    if ((fp = fopen(argv[1],binmode[READ_BINARY])) == NULL){
	printf(cantopen,argv[1]);
	return 1;
    }

    if (lines < 0){			/* tail of file */
	fseek(fp,0L,2);			/* seek to EOF */

	while (fseek(fp,-2L,1) != -1){	/* backup one character (+getc) */
	    if ((c = getc(fp)) == '\n'){
		if (++lines == 0)
		    break;		/* done enough, start printing */

#if (defined(MSDOS) || defined(ATARI_ST))
		fseek(fp,-1L,1);	/* seek past \r, too */
#endif
	    }
	}

	if (lines != 0)			/* could not find so many? */
	    fseek(fp,0L,0);		/* then do from start of file */
    }

    while ((c = getc(fp)) != EOF){	/* type the (head of the) file */
#if (defined(MSDOS) || defined(ATARI_ST))
	if (c == '\r'){			/* it is a CR, probably CR/LF */
	    if ((c = getc(fp)) != '\n'){/* read next and verify it is LF */
		ungetc(c,fp);		/* no, put it back */
		c = '\r';		/* and process the CR */
	    }
	}
#endif

	if (c >= ' ' || c == '\n' || c == '\r' || c == '\b' || c == '\t')
	    putchar(c);
	else
	    putchar('.');

	if (lines && c == '\n' && --lines == 0 && getc(fp) != EOF){
	    fclose(fp);
	    return 0;
	}
    }

    printf("EOF\n");
    fclose(fp);
    return 0;
}

/* Create a directory listing in a temp file and return the resulting file
 * descriptor. If full == 1, give a full listing; else return just a list
 * of names.
 */
FILE *
dir(path,full)
char *path;
int full;
{
	FILE *fp,*tmpfile();

	if ((fp = tmpfile()) != NULLFILE) {
		getdir(path,full,0,fp);
		/* This should be rewind(), but Aztec doesn't have it */
		fseek(fp,0L,0);
	}
	return fp;
}

/* wildcard filename lookup */
filedir(name,times,ret_str)
char *name;
int times;
char *ret_str;
{
	register char *cp,*cp1;
	static struct dirent sbuf;

#ifdef	MSC
	if (times == 0) {
		if (_dos_findfirst(name,ST_HIDDEN|ST_DIRECT,&sbuf))
			sbuf.fname[0] = '\0';
	} else {
		if (_dos_findnext(&sbuf))
			sbuf.fname[0] = '\0';
	}
#else
#ifdef __TURBOC__
	if (times == 0) {
		if (findfirst(name,&sbuf,ST_HIDDEN|ST_DIRECT))
			sbuf.fname[0] = '\0';
	} else {
		if (findnext(&sbuf))
			sbuf.fname[0] = '\0';
	}
#else
	bdos(SET_DTA,&sbuf,0);		/* Set disk transfer address */

	/* Find matching file (HIDDEN and DIRECTORY too, but not SYSTEM) */
	if(IS_ERROR(dos(times == 0 ? FIND_FIRST:FIND_NEXT,
			0,ST_HIDDEN|ST_DIRECT,name,0,0)))
		sbuf.fname[0] = '\0';
#endif
#endif

	/* Copy result to output, forcing to lower case */
	for(cp = ret_str,cp1 = sbuf.fname; cp1 < &sbuf.fname[13] && *cp1 != '\0';cp1++)
		*cp++ = tolower(*cp1);
	*cp = '\0';
}

/************* internally used funtions *************/

static char force;				/* set by -f flag */

/* call a specified function for each file argument */
static int
wildcard(argc,argv,function,argument)
    int argc;
    char *argv[];
    int (*function)();
    char *argument;

{
    int a;
    int st = 0;
    char verbose = 0;
    char name[13];
    char fullname[100];

    force = 0;					/* by default, no force */

    for (a = 1; a < argc; a++){
	if (argv[a][0] == '-'){			/* option? */
	    switch (argv[a][1])
	    {
	    case 'f':				/* -f option */
	       force = 1;
	       continue;

	    case 'v':				/* -v option */
	       verbose = 1;
	       continue;

	    default:
	       printf("bad flag -%c\n",argv[a][1]);
	       return -1;
	    }
	}

	strcpy(fullname,argv[a]);
	cleanup_fname(fullname);		/* make proper filename */
	filedir(fullname,0,name);		/* search first */

	while (name[0] == '.')			/* skip . and .. */
	    filedir(fullname,1,name);

	if (name[0] == '\0' && !force){		/* no (real) match at all */
	    printf(nofile,fullname);
	    st = 1;
	    continue;
	}

	while (name[0] != '\0'){
	    strcpy(basename(fullname),name);	/* append name */
	    if (verbose) {
		printf("%s %s\n",argv[0],fullname);
		fflush(stdout);
	    }
	    st |= (*function)(fullname,argument);
	    filedir(fullname,1,name);		/* search next */
	}
    }

    return st;					/* accumulated status */
}

/* delete a file, with diagnostic */
static int
filedelete(name)
    char *name;

{
    if (unlink(name) && !force){
	printf("Cannot delete \"%s\"\n",name);
	return 1;
    }

    return 0;
}

/* remove a directory, with diagnostic */
static int
dirremove(name)
    char *name;

{
    if (rmdir(name) && !force){
	printf("Cannot remove directory \"%s\"\n",name);
	return 1;
    }

    return 0;
}

/* copy a file to a directory */

static int
cptodir (from,dir)
    char *from,*dir;

{
    char destname[100];

    sprintf(destname,"%s\\%s",dir,basename(from));
    return (cpfile(from,destname));
}

/* copy a file to a destination file */

static int
cpfile (from,to)
    char *from,*to;

{
    int src,dst;
    int nbytes;
    int datime[2];
#ifdef MSDOS
    char buffer[BUFSIZ];
# define bufsiz BUFSIZ
# define BIN	0x8000
#endif
#ifdef ATARI_ST
# define BIN	0
#endif

    if ((src = open(from,BIN)) < 0)	/* open source file */
    {
	if (isdir(from))
	    printf("\"%s\" is a directory\n",from);
	else
	    printf(cantopen,from);

	return (1);
    }

#ifdef ATARI_ST
    Fdatime(datime,src,0);		/* get source file date/time */
#endif

    if (isalpha(to[0]) && isalpha(to[1]) && isalpha(to[2]) && to[3] == ':') {
	if ((dst = open(to,BIN+1)) < 0) /* open dest device */
	{
	    printf("Cannot open \"%s\" for write\n",to);
	    close(src);
	    return (1);
	}
    } else {
	if ((dst = creat(to,0666)) < 0) /* create dest file */
	{
	    printf("Cannot create \"%s\"\n",to);
	    close(src);
	    return (1);
	}
#if (defined(MSC) || defined(__TURBOC__))
	setmode(dst,BIN);
#endif
    }

    while ((nbytes = read(src,buffer,bufsiz)) > 0) {
	if (write(dst,buffer,nbytes) != nbytes) {
	    printf("Write error in \"%s\"\n",to);
	    close(dst);
#ifdef ATARI_ST
	    Fdelete(to);
#else
	    unlink(to);
#endif
	    close(src);
	    return (1);
	}
    }

    close(src);				/* close source file */
    close(dst);				/* must close after create */

#ifdef ATARI_ST
    if ((dst = Fopen(to,2)) > 0)	/* re-open for date/time set */
    {
	Fdatime(datime,dst,1);		/* set dest date/time */
	Fclose(dst);			/* finally close dest file */
    }
#endif

    if (nbytes < 0) {
	printf("Read error on \"%s\"\n",from);
	return (1);
    }

    return (0);
}

/* do a directory list to the stream
 * full = 0 -> short form, 1 is long, -1 is multi-column short
 */
static int
getdir(dirpath,full,nosort,file)
char *dirpath;
int full,nosort;
FILE *file;
{
	struct dirent sbuf;
	register char *cp,*cp1;
	int command = FIND_FIRST;
	int i = 0;
	int n = 0;
	struct dirsort *head, *here, *new;
	struct dirent *de;
	int malloc_lost = 0;
	char path[100];
	char dirtmp[20];
	char line_buf[50];		/* for long dirlist */

	strcpy(path,dirpath);		/* copy path */
	cleanup_fname(path);

	/* check for magic file extensions (.ARC, ...) */
	if(strlen(path) > 4 &&
	   index(path,'*') == NULLCHAR && index(path,'?') == NULLCHAR) {
	    /* When path argument exactly specifies a .ARC file, list it's contents */
	    if (!strncmp(path + strlen(path) - 4,".arc",4))
		return getarcdir(path,full,file);
	}

	/* Root directory is a special case */
	if(*path == '\0' || strcmp(path,"\\") == 0){
		strcpy(path,"\\*.*");
	} else {
	    /* If arg is a directory, append "\*.*" to it. */
	    if (isdir(path))
		strcat(path,"\\*.*");
	}
	head = NULLSORT;		/* No head of chain yet... */
	for(;;){
#ifdef MSC
		if (command == FIND_FIRST) {
			if (_dos_findfirst(path,ST_DIRECT,&sbuf))
				break;
		} else {
			if (_dos_findnext(&sbuf))
				break;
		}
#else
# ifdef __TURBOC__
		if (command == FIND_FIRST) {
			if (findfirst(path,&sbuf,ST_DIRECT))
				break;
		} else {
			if (findnext(&sbuf))
				break;
		}
# else
		bdos(SET_DTA,&sbuf,0);	/* Set disk transfer address */
		if(IS_ERROR(dos(command,0,ST_DIRECT,path,0,0)))
			break;
# endif
#endif

		command = FIND_NEXT;	/* Got first one already... */
		if(sbuf.fname[0] != '.'){  /* nuke "." and ".." */
			if(nosort){	/* unsorted dir listing */
				if(full > 0){
					n++;	/* Count 'em */
					format_dir(line_buf,&sbuf);
					fprintf(file,"%s%s",line_buf,(i^=1) ? "   " : "\n");
				} else	{	/* is short form */
					format_fname(dirtmp,sbuf.fname,sbuf.attr);
					fprintf(file,(full? "%-15s%s":"%s\n"),dirtmp,
						((++i % 5)?"":"\n"));
				}
				continue; /* next file */
			}
			n++;	/* One more entry */
			new = (struct dirsort *) malloc(sizeof(struct dirsort));
			if(new == NULLSORT)
				malloc_lost = 1;
			de  = (struct dirent *)malloc(sizeof(struct dirent));
			if(de == NULLENT)
				malloc_lost = 1;
			if(malloc_lost){
				/* Clean up and call unsorted */
				if(new)
					free(new);
				free_clist(head);
				return getdir(path,full,1,file);
			}
			*de = sbuf;	/* Copy contents of directory entry struct */

			/* Fix up names for easier sorting... pain! */
			strcpy(de->fname,"           ");	/* 11 blanks */
			cp  = sbuf.fname;
			cp1 = de->fname;
			do *cp1++ = *cp++; while (*cp && *cp != '.');
			if(*cp++){
				/* If there is an extension */
				cp1 = &(de->fname[8]);
				do *cp1++ = *cp++; while (*cp);
			}
			if(!(int)head){
				/* Make the first one */
				here = head = new;
				head->prev = head->next = NULLSORT;
			} else {
				/* Link on next one */
				new->next = NULLSORT;
				new->prev = here;
				here->next = new;
				here = new;
			}
			new->direntry = de;
		} /* IF on "." */
	} /* infinite FOR loop */

	if(head)
		dir_sort(head);		/* Make a nice, sorted list */

	here = head;
	if(here)
	    if(full > 0){
		do {
			format_dir(line_buf,here->direntry);
			fprintf(file,"%s%s",line_buf,(i^=1) ? "   " : "\n");
		} while ((here = here->next) != NULLSORT);
		if(i & 1)
			fprintf(file,"\n");
	    }
	    else {
		/* This is the short form */
		do {
			format_fname(dirtmp,here->direntry->fname,
					    here->direntry->attr);
			fprintf(file,(full? "%-15s%s":"%s\n"),dirtmp,
				((++i % 5)?"":"\n"));
		} while ((here = here->next) != NULLSORT);
		if(full && i % 5)
			fprintf(file,"\n");
	    }

	/* Give back all the memory we temporarily needed... */
	free_clist(head);

	if(full > 0){
		/* Provide additional information only on DIR */

		if (isalpha(*path) && path[1] == ':')	/* detect A: drivespec */
			diskfree(file,*path & 0x1f,n);
		else
			diskfree(file,0,n);
	}
	return 0;
}

/* show contents of a .ARC file */

static char badarc[] = "\nARC file incomplete\n";

#define getword(buf,ofs) ((unsigned)(buf[ofs+1]<<8) + buf[ofs])
#if (defined(ATARI_ST) && defined(MWC) && (MWC < 306))
# define getlong(buf,ofs) (((int32)getword(buf,ofs+2)<<Sixteen) + getword(buf,ofs))
#else
# define getlong(buf,ofs) (((int32)getword(buf,ofs+2)<<16) + getword(buf,ofs))
#endif

static int
getarcdir(path,full,file)
char *path;
int full;
FILE *file;
{
	FILE *arc;
	int16 date,time;
	int32 length,size;
	int32 t_length = 0;
	int32 t_size = 0;
	int i = 0;
	unsigned char buf[30];
	char s_length[20],s_size[20];

	if((arc = fopen(path,binmode[READ_BINARY])) == NULL){
		fprintf(file,cantopen,path);
		return 1;
	}
	if(full > 0)
		fprintf(file,"Name              Length  Stowage    Size Now   Date     Time  CRC\n");
	while(!feof(arc)){
		if(fread(buf,2,1,arc) != 1 || buf[0] != 0x1a){
			fprintf(file,"\nProbably not an ARC file\n");
			fclose(arc);
			return 2;
		}
		if (buf[1] == 0)	/* end-of-archive mark */
			break;
		if(fread(buf+2,(buf[1] == 1)? 23 : 27,1,arc) != 1){
			fprintf(file,badarc);
			fclose(arc);
			return 2;
		}
		size = getlong(buf,15);
		i++;
		if (full > 0){
			if (buf[1] == 1)
				length = size;
			else
				length = getlong(buf,25);
			sprintf(s_length,"%ld",length); commas(s_length);
			sprintf(s_size,"%ld",size);	commas(s_size);
			t_length += length;
			t_size += size;
			date = getword(buf,19);
			time = getword(buf,21);
			fprintf(file,"%-12s %11s  %2d %3d%% %11s  %2d/%02d/%02d %2d:%02d  %04x\n",
				buf + 2,s_length,buf[1],
				(length)? (100 - (int) ((100L * size) / length)) : 0,
				s_size,
				(date >> 5) & 0xf,	/* month */
				(date	  ) & 0x1f,	/* day */
				(date >> 9) + 80,	/* year */
				(time >> 11) & 0x1f,	/* hour */
				(time >> 5) & 0x3f,	/* minute */
				getword(buf,23));
		} else {
			fprintf(file,(full? "%-15s%s":"%s\n"),buf + 2,
				((i % 5)?"":"\n"));
		}
		if(fseek(arc,size,1) != 0){
			fprintf(file,badarc);
			fclose(arc);
			return 2;
		}
	}
	if(full > 0){
		sprintf(s_length,"%ld",t_length);	commas(s_length);
		sprintf(s_size,"%ld",t_size);		commas(s_size);
		fprintf(file,"Total: %5d %11s     %3d%% %11s\n",
			i,s_length,
			(t_length)? (100 - (int) ((100L * t_size) / t_length)) : 0,
			s_size);
	}
	if(full < 0 && i % 5)
		fprintf(file,"\n");
	fclose(arc);
	return 0;
}

static void
diskfree (file,drv,nfiles)
FILE *file;
int drv;
int nfiles;

{
#ifdef MSDOS
	unsigned short ax,bx,cx,dx;
#endif
#ifdef ATARI_ST
	struct { unsigned long di_free,di_many,di_ssize,di_spau; } disk_info;
#endif
	unsigned long free_bytes = 0, total_bytes = 0;
	char s_free[20], s_total[20];

	fflush(stdout);			/* function takes a short while... */

#ifdef MSDOS
	/* Provide additional information  */
	ax = 0x3600;			/* AH = 36h, AL = 0 (AL not used) */
	bx = cx = 0;			/* BX & CX not used */
	dx = drv;			/* Default drive */
	isfree(&ax,&bx,&cx,&dx);

	if (ax != 0xffff) {
		free_bytes  = (unsigned long)ax * (unsigned long)cx;
		total_bytes = free_bytes * (unsigned long)dx;
		free_bytes *= (unsigned long)bx;
	}
#endif
#ifdef ATARI_ST
	if (Dfree(&disk_info,drv) == 0) {
		free_bytes  = disk_info.di_spau * disk_info.di_ssize;
		total_bytes = free_bytes * disk_info.di_many;
		free_bytes *= disk_info.di_free;
	}
#endif

	sprintf(s_free,"%ld",free_bytes);	commas(s_free);
	sprintf(s_total,"%ld",total_bytes);	commas(s_total);

	if(nfiles)
		fprintf(file,"%d",nfiles);
	else
		fprintf(file,"No");

	fprintf(file," file%s. %s bytes free. Disk size %s bytes.\n",
		      (nfiles==1? "":"s"),s_free,s_total);
}


/*
 * Return a string with commas every 3 positions.
 * The original string is replaced with the string with commas.
 *
 * The caller must be sure that there is enough room for the resultant
 * string. (max 40 characters input anyway)
 *
 *
 * k3mc 4 Dec 87
 * pe1chl 3 Feb 88     europeans would use periods...
 */

#ifndef THSEP
#define THSEP '.'
#endif
static void
commas(dest)
register char *dest;
{
	register char *src;
	char buf[40];		/* Temp buf for copy of source string */
	unsigned cc;		/* The comma counter */

	strcpy(src = buf,dest); /* Make a copy, so we can muck around */
	cc = (strlen(dest)-1)%3 + 1; /* Tells us when to insert a comma */

	while(*src != '\0'){
		*dest++ = *src++;
		if( ((--cc) == 0) && *src ){
			*dest++ = THSEP; cc = 3;
		}
	}
	*dest = '\0';
}
/*
 * This insertion sort adapted from "Writing Efficient Programs" by Jon Louis
 * Bentley, Prentice-Hall 1982, ISBN 0-13-070244-X (paperback) p. 65
 *
 * Run Time (sec) = K * N^2, where K = 21e-6 on my turbo XT clone (SI=2.6).
 * This could be improved to perhaps K * N * log2(N) using Quicksort, but,
 * as Bentley points out, this insertion sort is actually faster for small
 * values of N.	 His "best" sorting algorithm uses an insertion sort/Quicksort
 * hybrid, with the "cutoff" value being about 30 elements.
 *
 * I have opted for the straight insertion sort because it is quite simple,
 * provably correct, and not a terrible performer when N < 1000, which is the
 * case for most directories.
 */
static void
dir_sort(head)
struct dirsort *head;
{
	register struct dirsort *here, *backtrack;
	struct dirent *de_temp;

	for(here = head->next; here != NULLSORT; here = here->next){
		backtrack = here;
		de_temp = here->direntry;
		while(backtrack->prev
		 && strcmp(de_temp->fname,backtrack->prev->direntry->fname)<0){
			backtrack->direntry = backtrack->prev->direntry;
			backtrack = backtrack->prev;
		}
		backtrack->direntry = de_temp;
	}
}

static void
format_dir (line_buf,sbuf)
	char *line_buf;
	register struct dirent *sbuf;

{
	char dirtmp[20];
	char cbuf[20],cbuf1[20];

	format_fname(dirtmp,sbuf->fname,sbuf->attr);

	sprintf(line_buf,"%-13s",dirtmp);
	if(sbuf->attr & ST_DIRECT)
		strcat(line_buf,"           ");/* 11 spaces */
	else {
		sprintf(cbuf,"%ld",sbuf->fsize);
		commas(cbuf);
		sprintf(cbuf1,"%10s ",cbuf);
		strcat(line_buf,cbuf1); /* Do filesize */
	}

	sprintf(cbuf,"%2d:%02d %2d/%02d/%02d",
	  (sbuf->ftime >> 11) & 0x1f,	 /* hour */
	  (sbuf->ftime >> 5) & 0x3f,	 /* minute */
	  (sbuf->fdate >> 5) & 0xf,	 /* month */
	  (sbuf->fdate ) & 0x1f,	 /* day */
	  (sbuf->fdate >> 9) + 80);	 /* year */

	strcat(line_buf,cbuf);
}

static void
format_fname(dest,src,attr)
register char *dest, *src;
char	attr;
{
	char	*cp = src+8;
	register int loop_counter;

	for(loop_counter=0; loop_counter<8; loop_counter++){
		*dest++ = tolower(*src);
		if(*++src == ' ')
			break;
	}
	if(cp[0] != ' ' || cp[1] != ' ' || cp[2] != ' '){ /* There is an extension */
		*dest++ = '.';
		for(loop_counter=0; loop_counter<3; loop_counter++){
			*dest++ = tolower(*cp);
			if(*++cp == ' ')
				break;
		}
	}
	if(attr & ST_DIRECT)
		*dest++ = '\\';

	*dest = '\0';
}

static void
free_clist(head)
struct dirsort *head;
{
	register struct dirsort *here;

	here = head;
	if(here)do{
		free(here->direntry);
		if(head != here){
			free(head);
			head = here;
		}
	} while ((here = here->next) != NULLSORT);
	if(head != here){
		free(head);
		head = here;
	}
}

/* test if arg has wildcard characters */

int
iswild(name)
    register char *name;

{
    while (*name){
	if (*name == '*' || *name == '?')	/* MS-DOS wildcard chars */
	    return 1;

	name++;
    }
    return 0;
}

/* test if arg is a directory */

int
isdir(name)
    register char *name;

{
    register char *p;
    struct dirent dta,*orgdta;
    int rcode;

    for (p = name; *p != '\0'; p++)	/* find end-of-name */
	;

    if (p == name)			/* empty name? */
	return (0);			/* it's not a directory */

    if (p[-1] == '\\')			/* trailing \? */
	*--p = '\0';			/* remove it */

    if (p == name)			/* if that was all */
	return (1);			/* it is root dir */

    if (isalpha(name[0]) && name[1] == ':' && name[2] == '\0')
	return (1);			/* it is a drive name */

    if (iswild(name))			/* contains wildcard chars? */
	return (0);			/* then it isn't a dirname */

    p = basename(name);			/* point to basename part */

    if (p[0] == '.' &&			/* . and .. are directories */
	(p[1] == '\0' ||
	 p[1] == '.' && p[2] == '\0'))
	return (1);

#if (defined(ATARI_ST) && !defined(__TURBOC__))
    orgdta = (struct dirent *) Fgetdta(); /* save caller's DTA */
    Fsetdta(&dta);			/* set our's */
    rcode = Fsfirst(name,ST_DIRECT);	/* get files and directories */
    Fsetdta(orgdta);			/* restore DTA */
#else
# ifdef MSC
    rcode = _dos_findfirst(name,ST_DIRECT,&dta);
# else
#  ifdef __TURBOC__
    rcode = findfirst(name,&dta,ST_DIRECT);
#  else
    bdos(SET_DTA,&dta,0);		/* Set disk transfer address */
    rcode = dos(FIND_FIRST,0,ST_DIRECT,name,0,0);
#  endif
# endif
#endif

    if (rcode == 0 && (dta.attr & ST_DIRECT)) /* found it, and ST_DIRECT set? */
	return (1);			/* it is a subdir */

    return (0);				/* not a directory */
}

/* return pointer to basename of arg */

char *
basename(filename)
    register char *filename;

{
    char *rv = filename;

    while (*filename) {
	if (*filename == ':' || *filename == '\\')
	    rv = filename + 1;

	filename++;
    }

    return (rv);
}

/* cleanup a filename to conform to MS-DOS/gemdos standards */
/* also make it lower-case for better appearance */

cleanup_fname (name)
    register char *name;

{
    while (*name) {
	if (!isascii(*name))
	    *name = '@';

	if (isupper(*name))
	    *name = tolower(*name);

	if (*name == '/')
	    *name = '\\';

	name++;
    }
}
