#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <ctype.h>
#include <dir.h>
#include <mem.h>

#include "dosstruc.h"
 
#define MAX12BIT 0x0FF6
#if defined(__TINY__)
#define MODEL "Tiny"
#endif
#if defined(__SMALL__)
#define MODEL "Small"
#endif
#if defined(__MEDIUM__)
#define MODEL "Medium"
#endif
#if defined(__COMPACT__)
#define	MODEL "Compact"
#endif
#if defined(__LARGE__)
#define MODEL "Large"
#endif
#if defined(__HUGE__)
#define MODEL "Huge"
#endif
 
struct    DpbStruct Dpb;		/* Disk Parameter Blcok (see dosstruc.h)	*/
struct    ClusterQueue CluQ;	/* Queue of cluster for directory			*/
struct    DirEntry *DirBuff;	/* Buffer for directory to be sorted		*/
unsigned  LastCluster;			/* Value for end of cluster chain			*/
int       Is12Bit;				/* 12 / 16 bit cluster indicator			*/
int		  *CluArray;			/* Cluster Array ptr, dynamically allocated	*/
char      Disk;					/* Alpha working disk ('A', 'B', .... )		*/
char	  CurDir[67];			/* Storage for Current Directory of disk	*/
char	  Path[67];				/* Storage for Path to sort					*/
char	  Parent[67];			/* Storage for Parent part of Path			*/
char	  Element[13];			/* Storage for Child part of Path			*/
char	  *Fat;					/* Pointer to FAT buffer (dynamic)			*/
char      Line[80];				/* Working storage for strings				*/
unsigned  Cluster, Sector, NumSec;
unsigned  MinMem;				/* Minimum available memory					*/
struct    ExtendedEntry Dir;
int       Lim, i, j, k, l;
struct    ClusterEntry *p, *t;
int       OutSectors, OutClusters, BytesPerCluster, ECount;
struct    ExtFcb Fcb;
char      Order = 'N';			/* Sort key indicator (Default=Name/Ext)	*/
char      Inverse = 0;			/* Ascend/Descend indic. (Default=Ascend)	*/
char      Level = 0;			/* Recursive sort indic. (default=Recursic)	*/
char      RSwt = 0;				/* Report switch (Default=No Report)		*/
char      VerSwt = 0;			/* Pause for operator verify (Default=off)	*/
char      Packed = 1;			/* Elim. "erased" entries (Default=on)		*/


main (argc, argv)
int argc;
char **argv;
{
    char   *strrspn();
    void   SortDir(), ShowRoot(), Usage();
 
    char      *p;
    union REGS      Reg;
    unsigned  Cluster, Sector;
	int       i, j, AbortProgram();

    fputs("C-Sort And Pack [CSAP]: Version 2.08b: Date: 07-30-1987", stderr);
	fputs(" [", stderr);
	fputs(MODEL, stderr);
	fputs(" Model]\n", stderr);

	fputs("    use \"CSAP -H\" or \"CSAP ?\" for help.\n\n", stderr);
	Disk = getdisk() + 'A';
	Line[0] = '\\'; getcurdir(Disk - '@', &Line[1]);

	ctrlbrk(AbortProgram);		/* Install "wrap-up" in Control Break vec.	*/

/* Interpret command line arguments, if any      */

	for (i=1; i<argc; ++i) {
		if (argv[i][0] == '-') {
			for (j=1; j<strlen(argv[i]); ++j) {
				switch (toupper(argv[i][j])) {
					case 'N':			/* Sort Key = Name/Ext (default)	*/
						Order = 'N';
						break;
					case 'D':			/* Sort Key = Date/Time   			*/
						Order = 'D';
						break;
					case 'E':			/* Sort Key = Ext/Name				*/
						Order = 'E';
						break;
					case 'S':			/* Sort Key = File Size				*/
						Order = 'S';
						break;
					case 'R':			/* Report Dir loc. & "erased"		*/
						RSwt = 1;
						break;
					case 'I':			/* Sort order inverse				*/
						Inverse = 1;
						break;
					case 'P':			/* Do NOT remove "erased" entries	*/
						Packed = 0;
						break;
					case 'L':			/* Limit sort to one level			*/
						Level = 1;
						break;
					case 'V':			/* Request approval before sort		*/
						VerSwt = 1;
						break;
					case 'H':
						Usage();
					default:			/* Illegal option					*/
						fprintf(stderr, "Invalid option %s.\n", argv[i]);
						Usage();
						break;
					}
				}
			}
		else {		/* Not switch, assume directory name or '?'		*/
			if (argv[i][0] == '?') Usage();
		    if (argv[i][1] == ':') {	/* Check for disk specified	*/
  		        Disk = toupper(argv[i][0]); p = &argv[i][2];
				}
			else p = &argv[i][0];
			if (p[0] != '\\') {
				Line[0] = '\\'; getcurdir(Disk - '@', &Line[1]);
				strcat(Line, "\\"); strcat(Line, p);
				}
			else strcpy(Line,p);
			}
		}

/*
	Get disk information - uses un-documented DOS call, Int 21H, Func. 32H
		This function has been verified to work correctly in PC/MS-DOS
		versions 2.0 through 3.3.  It is heavily used by DOS programs such
		as CHKDSK.
*/

    GetDPB(Disk, &Dpb);

/* Establish whether disk has 16-bit or 12-bit clusters  */

    Is12Bit = (Dpb.LastCluster > MAX12BIT) ? 0 : 1;
    LastCluster = (Is12Bit) ? 0x0FF8 : 0xFFF8;

/*
	Get & save current directory of working disk.  We have to change to sort
	and must restore on termination
*/
    CurDir[0] = Disk; CurDir[1] = ':'; CurDir[2] = '\\';
    getcurdir(Disk - '@', (char *) &CurDir[3]);

/* Allocate space to hold entire FAT in memory and read it in  */

    if ((Fat = malloc(Dpb.FatSize * Dpb.SectorSize)) == NULL) {
        fprintf(stderr, "Insufficient memory for FAT.\n");
        exit(1);
        }
    if (absread(Disk-'A', Dpb.FatSize, Dpb.FatStart, Fat) != 0) {
        fprintf(stderr, "Error reading FAT.\n");
        exit(1);
        }

/*
	Develop full path name for directory to be sorted and separate into
		Parent and Child portions
*/

    Path[0] = Parent[0] = Element[0] = '\0';
    Path[0] = Disk; Path[1] = ':';
    Path[2] = 0x00;
    if (Line[0] != '\\') {
        strcat(Path, "\\"); strcpy(&Path[3], &CurDir[3]);
        if ((Path[strlen(Path)-1] != '\\') && (Path[strlen(Path)-1] != '/'))
            strcat(Path, "\\");
        }
    strcat(Path, Line);
    p = strrspn(Path, "\\/");
    strcpy(Element, &p[1]);
    if (p[-1] ==  ':') p++;
    strncpy(Parent, Path, p - Path); Parent[p - Path] = 0x00;

	MinMem = coreleft();		/* Initialize minimum available memory		*/

/*
	Perform sort.  SortDir is recursive and, if Level is not on, will sort
	sort all levels of the hierarchy from the starting level down
*/

    SortDir();

	printf("Minimum memory= %u\n", MinMem);

    bdos(0x3B, (int) CurDir, 0);	/* Restore input "current" directory	*/
    }  /* end Main */


/*
	STRRSPN is simply a reverse version of STRSPN.  It finds the LAST
	occurance in S1 of any member of S2.  Fo some reason, none of the C
	compilers that I use provide this although they all provide STRSPN
*/

char *strrspn (s1, s2)
char *s1, *s2;
{
    char *p2start = s2;
    char *p1;
 
    p1 = s1 + strlen(s1) - 1;
    while (p1 >= s1) {
        while (*s2) {
            if (*p1 == *s2) return(p1);
            s2++;
            }
        s2 = p2start;
        p1--;
        }
    return( (char *) NULL);
    }
 

/*  SearchFirst --  Search for First Directory Entry.
 *                  On entry fcb contains an extended File Control Block
 *                  with file name and attribute bits set.  On exit, fcb
 *                  contains matched entry unless return code is 255, in
 *                  which case no match was found.  This routine is used
 *					instead of the ones provided by the caller so that the
 *					cluster information for the directory can be obtained.
 */
SearchFirst(Fcb)
struct ExtFcb *Fcb;
{
    union REGS regs;
 
    regs.x.ax = 0x1100;
    regs.x.dx = (unsigned) Fcb;
    intdos(&regs, &regs);
    return((int) (regs.x.ax & 0xFF));
    }

/*  Alu2Sec -- Converts an input cluster number [ALU] into the disk-relative
 *             sector for use with DOS Absolute Disk Read [interrupt 25H] or
 *             Absolute Disk Write [interrupt 26H].  Requires access to the
 *             undocumented DOS Disk Parameter Block [use funtion GetDPB].
 */
unsigned int Alu2Sec (Dpb, Alu)
struct DpbStruct *Dpb;
unsigned Alu;
{
    return( (Alu - 2) * (Dpb->ClusterSize + 1) + Dpb->DataStart);
    }
 
/*  NextCl -- This function calculates the logical "chaining" of cluster
 *            numbers in a File Allocation Table [FAT].  Given an entry
 *            cluster number it calculates the next cluster using the
 *            array Fat[].
 *
 *            If Is12Bit is TRUE then Fat[] is assumed to contain 12 bit
 *            entries, otherwise Fat[] is assumed to contain 16 bit entries.
 */
unsigned NextCl(Is12Bit, Cluster, Fat)
int Is12Bit;
unsigned Cluster;
unsigned char Fat[];
{
    unsigned ClWord, ClOffset;

    if (Is12Bit) {                                    /* 12 bit FAT lookup */
        ClOffset = 3 * Cluster / 2;
        ClWord = Fat[ClOffset] + (Fat[ClOffset + 1] << 8);
        if (Cluster & 1) return (ClWord >> 4);        /* odd cluster  */
        else return (ClWord & 0x0FFF);                /* even cluster */
        }
    else return (((unsigned int *) Fat)[Cluster]);   /* 16 bit FAT lookup */
    }


/* PutQueue -- Builds a simple FIFO linked list using dynamically acquired
 *             memory.
 */
void PutQueue (Q, Cluster)
struct ClusterQueue *Q;
unsigned Cluster;
{
    struct ClusterEntry *p;
 
    if ((p = malloc(sizeof(struct ClusterEntry))) == NULL) {
        fprintf(stderr, "Insufficient memory(1).\n");
        AbortProgram();
        }
    p->Next = NULL; p->Cluster = Cluster;
    if (Q->Head == NULL) Q->Head = p;
    else Q->Current->Next = p;
    Q->Current = p; Q->Count++;
    }

/* AbortProgram -- Aborts the program, resetting the current directory,
 *                 with an error code of 1.
 */
int AbortProgram () {

    bdos(0x3B, (int) CurDir, 0);       /* Reset input Current Directory */
    exit(1);
    }



/* strincmp -- The comparsion routine for the qsort algorithm.
 */
int strincmp (a, b)
struct DirEntry *a, *b;
{
    int  i;
	long t;

/* Ensure that "erased" entries sort high no matter what the sort key is.	*/

	if ((a->Name[0] == 0xE5)  && (b->Name[0] != 0xE5)) return(1);
	if (b->Name[0] == 0xE5) return(-1);

/* Ensure that directories sort lower that files no matter what sort key	*/

    if ((a->Name[0] != 0xE5) && (b->Name[0] != 0xE5)) {
        if ((a->Attribute & 0x10) ^ (b->Attribute & 0x10)) {
            if (a->Attribute & 0x10) return(-1);
            else return(1);
            }
        }

/* Actual sort key compare routines  */

	switch (Order) {
		case 'D':			/* Sort key is Date/Time			*/
			if ((t = a->ModifyDate - b->ModifyDate) == 0) {
				t = a->ModifyTime - b->ModifyTime;
				}
			break;
		case 'N':			/* Sort key is Name/Ext (default)	*/
			t = strncmp(a->Name, b->Name, 11);
			break;
		case 'E':			/* Sort key is Ext/Name				*/
			if ((t = strncmp(a->Ext, b->Ext, 3)) == 0) {
				t = strncmp(a->Name, b->Name, 8);
				}
			break;
		case 'S':			/* Sort key is File Size			*/
			t = a->FileSize - b->FileSize;
			break;
		default:
			t = strncmp(a->Name, b->Name, 11);
			break;
	    }
	if (Inverse) t = -t;	/* Sort order is inverse	*/
	return((t < 0) ? -1 : ((t > 0) ? 1 : 0));
	}

void Usage () {

	printf("USAGE:    CSAP [options] [[d:]directory_name]\n");
	printf("                        or\n");
	printf("          CSAP [[d:]directory_name] [options]\n");
	printf("\n");
	printf("Options:\n");
	printf("    -N    Sort on Name/Ext (default).\n");
	printf("    -D    Sort on Date/Time.\n");
	printf("    -E    Sort on Ext/Name.\n");
	printf("    -S    Sort on File Size.\n");
	printf("\n");
	printf("    -R    Report number of \"erased\" entries and directory location.\n");
	printf("    -I    Inverse sort order, i.e. descending.\n");
	printf("    -P    Do NOT remove \"erased\" entries.\n");
	printf("    -L    Limit sort to a single level.\n");
	printf("    -V    Request confirmation before sorting.\n");
	printf("    -H    This message.\n");
	exit(0);
	}
