#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dos.h>
#include <ctype.h>
#include <mem.h>

#include "dosstruc.h"

/*
 * SORTDIR is the main routine of CSAP.  It is a recursive routine that will
 * walk the directory hierarchy, sorting all directories that it finds.  It
 * uses the quick sort or quicker sort algorithm provided by most C runtime
 * libraries to perform the actual sort.  It uses DOS Int 25H and 26H,
 * Absolute Disk Read and Absolute Disk Write, to read the directories into
 * memory for sorting and to write out the sorted directories.  It depends
 * upon information about the physical characteristics of the disk provided
 * by GETDPB.
 */

void SortDir () {
    void        PutQueue(), GetKey(), qsort();
    void        ReadSub(), WriteSub(), ReadRoot(), WriteRoot();
    char        *strrspn();
    int         strincmp(), SearchFirst();
    unsigned    al2sec(), NextCl();

    extern char      Disk, *Fat, Parent[67], Element[13];
    extern char      Line[80], Level, RSwt, VerSwt, Packed;
    extern struct    DpbStruct Dpb;
    extern struct    DirEntry *DirBuff;
    extern unsigned  Cluster, Sector, NumSec;
    extern int		 Lim, j, l;
    extern struct    ClusterEntry *p, *t;
    extern int       OutSectors, OutClusters, BytesPerCluster, ECount;
    extern struct    ExtFcb Fcb;
    extern struct    ExtendedEntry Dir;
    extern struct    ClusterQueue CluQ;
    extern int       Is12Bit, *CluArray;
	extern unsigned  MinMem;

    char      **DirVector, *r, Reply;
    int       i, k, DirCount, Root;
	unsigned  Mem;


    BytesPerCluster = (Dpb.ClusterSize + 1) * Dpb.SectorSize;

    if (strlen(Element) != 0) {  /* Sorting a sub-directory */
        Root = 0;
        bdos(0x3B, (int) Parent, 0);             /* Set Current Directory */
        if (Parent[strlen(Parent)-1] != '\\') strcat(Parent, "\\");
        setdta((char *) &Dir);
        parsfnm(Element, (struct fcb *) &Fcb.DriveId, 0);
        Fcb.FcbHdr.Header = 0xFF;
        Fcb.DriveId = Disk - '@';
        Fcb.FcbHdr.Attrib = 0xFF;
        if (SearchFirst(&Fcb) != 0) {
            fprintf(stderr, "Not found: %s%s\n", Parent, Element);
            AbortProgram();
            }
        }
    else Root = 1;				/* Sorting the Root directory */

    printf("Sorting: %s%s", Parent, Element);
	if (!isdevice(1)) fprintf(stderr, "Sorting: %s%s", Parent, Element);

/* Read directory to be sorted into memory */

    if (Root) ReadRoot();
    else ReadSub();

/* Count sub-directories, skipping "current" and "parent" entries */

    for (DirCount=0, i=0; i < Lim / sizeof(struct DirEntry); i++) {
        if (DirBuff[i].Name[0] == 0) break;
        if ((DirBuff[i].Attribute & 0x10)
              && (DirBuff[i].Name[0] != '.')
              && (DirBuff[i].Name[0] != 0xE5)) DirCount++;
        }
	k = i;
	if (DirCount != 0) {
	    if ((DirVector = malloc(DirCount * sizeof(DirVector))) == NULL) {
			fprintf(stderr, "Insufficient memory (2).\n");
			return;
			}
		}

/*
 * Compute the number of directory sectors to write out - don't write sectors
 * that don't contain active entries.
 */

    OutSectors = ((i * 32) + Dpb.SectorSize - 1) / Dpb.SectorSize;
    OutClusters = (OutSectors + Dpb.ClusterSize) >> Dpb.ClusterShift;

    j = 0;
    if (Root) {

/* If sorting Root - skip 1st two files if "System" & "Hidden" */

		if (i > 1) {
	        if (DirBuff[j].Attribute & 0x06) {
	            j++; i--;
	            if (DirBuff[j].Attribute & 0x06) {
	                j++; i--;
	                }
	            }
			}
        }
	else {

/* If sorting subdirectory - skip 1st two entries, "current" and "parent" */

		j += 2; i -= 2;
		}
	if (i == 0) {
		printf(" Empty.\n");
		r = &Parent[strlen(Parent)-1];
	    if (r[-1] == ':') r++;
	    *r = 0x00;
		return;
		}

/* If VerSwt ON - request operator confirmation BEFORE sorting */

	if (VerSwt != 0) {
		if (!isdevice(1)) {
			fprintf(stderr, "   Sort (Y or N)? "); fflush(stderr);
			}
		else {
			printf("   Sort (Y or N)? "); fflush(stdout);
			}
		if (toupper(getche()) != 'Y') {
			if (!isdevice(1)) fprintf(stderr, "n");
			printf("\n");
			return;
			}
		}

	if (!isdevice(1)) fprintf(stderr, "\n");
	printf("\n");

/* Sort directory */

    qsort(&DirBuff[j], i, sizeof(struct DirEntry), strincmp);

	ECount = 0;

/*
 * If Packed OFF, remove "erased" entries from directory (mark them "unused)
 */

	if (Packed != 0) {
	    for (i=k; i >= 0; i--) {
	        if ((DirBuff[i].Name[0] != 0xE5) && (DirBuff[i].Name[0] != 0x00)) break;
	        if (DirBuff[i].Name[0] == 0xE5) {
				DirBuff[i].Name[0] = 0x00;
				++ECount;
				}
	        }
		}

/* Build list of subdirectories - skipping "current" and "parent" entries */

    for (l=0, i=0; DirBuff[i].Name[0]; i++) {
        if ((DirBuff[i].Attribute & 0x10) && (DirBuff[i].Name[0] != '.')) {
			for (k=0, j=0; j<8; ++j) {
				if (DirBuff[i].Name[j] == ' ') break;
				else Line[k++] = DirBuff[i].Name[j];
				}
			if (DirBuff[i].Ext[0] != ' ') {
				Line[k++] = '.';
				for (j=0; j<3; ++j) {
					if (DirBuff[i].Ext[j] == ' ') break;
					else Line[k++] = DirBuff[i].Ext[j];
					}
				}
			Line[k++] = '\0';
            if ((DirVector[l] = malloc(k)) == NULL) {
				fprintf(stderr, "Insufficient memory.\n");
				return;
				}
			strcpy(DirVector[l++], Line);
            }
        }

/* Write out sorted directory */

    if (Root) {
		WriteRoot();
		if (RSwt) printf("    Location: %04XH-%04XH\n", Dpb.DirStart, Dpb.DirStart + NumSec - 1);
		}
    else {
		WriteSub();
		if (RSwt) {
			printf("    Location:");
			for (i=0; i<CluQ.Count; ++i) {
				if (i == 0) printf(" %04XH", Alu2Sec(&Dpb, CluArray[i]));
				if ((i > 0) && (CluArray[i] != CluArray[i-1] + 1)) {
					printf("-%04XH %04XH",
						   Alu2Sec(&Dpb, CluArray[i-1] + 1) - 1,
						   Alu2Sec(&Dpb, CluArray[i])
						   );
					}
				}
			printf("-%04XH\n", Alu2Sec(&Dpb, CluArray[i-1] + 1) - 1);
			}
		}
	if (RSwt) printf("    %d Erased entries removed\n", ECount);

	Mem = coreleft();
	if (MinMem < Mem) MinMem = Mem;

/* Release dynamically acquired space for this directory */

    free(DirBuff);
    for (p = CluQ.Head; p != NULL; p = t) {
        t = p->Next; free(p);
        }
    free(CluArray);

/* If Recursive sort - build Parent and Element for sub directories & sort */

	if (!Level) {
	    strcat(Parent, Element);
	    for (i=0; i < DirCount; i++) {
	        strcpy(Element, DirVector[i]);

	        SortDir();

	        }
	    r = strrspn(Parent, "\\/");
	    if (r[-1] == ':') r++;
	    *r = 0x00;
		}
    }

void ReadSub() {

    extern unsigned Cluster;
    extern unsigned  LastCluster;
    extern char *Fat;
    extern struct    ExtendedEntry Dir;
    extern struct    ClusterQueue CluQ;
    extern int       Is12Bit, *CluArray, BytesPerCluster, Lim;
    extern char Disk, *Fat;
    extern struct    DirEntry *DirBuff;
    extern struct    DpbStruct Dpb;

    int i;
    struct ClusterEntry *p;
    void PutQueue();
    unsigned NextCl();

    Cluster = Dir.Body.FirstCluster;
    CluQ.Head = CluQ.Current = NULL; CluQ.Count = 0;
    while (Cluster < LastCluster) {
        PutQueue(&CluQ, Cluster);
        Cluster = NextCl(Is12Bit, Cluster, Fat);
        }
    if ((CluArray = malloc(CluQ.Count * sizeof(int))) == NULL) {
        fprintf(stderr, "Insufficient memory (3).\n");
        return;
        }
    for (i=0, p=CluQ.Head; p != NULL; i++, p=p->Next) {
        CluArray[i] = p->Cluster;
        }
    Lim = CluQ.Count * BytesPerCluster;
    if ((DirBuff = malloc(Lim)) == NULL) {
        fprintf(stderr, "Insufficient memory for directory buffer.\n");
        return;
        }
    for (i=0; i < CluQ.Count; i++) {
        if (absread(Disk-'A', Dpb.ClusterSize + 1,
                          Alu2Sec(&Dpb, CluArray[i]),
                          (char *) &DirBuff[(i * BytesPerCluster) / 32])) {
            fprintf(stderr, "Error reading directory.\n"); exit(1);
            }
        }
    }

void WriteSub () {

    extern struct    ClusterQueue CluQ;
    extern char      Disk, *Fat, Parent[67], Element[13];
    extern struct    DpbStruct Dpb;
    extern int       Is12Bit, *CluArray, BytesPerCluster;
    extern struct    DirEntry *DirBuff;

    int i;

    for (i=0; i < CluQ.Count; i++) {
        if (abswrite(Disk-'A', Dpb.ClusterSize + 1,
                           Alu2Sec(&Dpb, CluArray[i]),
                           (char *) &DirBuff[(i * BytesPerCluster) / 32])) {
            fprintf(stderr, "Error writing directory.\n"); exit(1);
            }
        }
    }

void ReadRoot () {

    extern struct    DpbStruct Dpb;
    extern unsigned  NumSec;
    extern struct    DirEntry *DirBuff;
    extern int       Is12Bit, *CluArray, Lim;
    extern char      Disk;

    int i;
    int Error;

    Lim = Dpb.MaxEntries * 32;
    NumSec = (Lim + Dpb.SectorSize - 1) / Dpb.SectorSize;
    if ((DirBuff = malloc(Lim)) == NULL) {
        fprintf(stderr, "Insufficient memory for cluster buffer.\n");
        return;
        }
    if ((CluArray = malloc(sizeof(int))) == NULL) {
        fprintf(stderr, "Insufficient memory (4).\n");
        return;
        }
    CluArray[0] = 0;
    if ((Error = absread(Disk-'A', NumSec, Dpb.DirStart, (char *) DirBuff)) != 0) {
        fprintf(stderr, "Error reading root:  %04X.\n", Error); exit(1);
        }
    }


void WriteRoot () {

    extern char Disk;
    extern unsigned  Cluster, Sector, NumSec;
    extern struct    DpbStruct Dpb;
    extern struct    DirEntry *DirBuff;

    if (abswrite(Disk-'A', NumSec, Dpb.DirStart, (char *) DirBuff)) {
        fprintf(stderr, "Error writing Root.\n"); exit(1);
        }
    }
