/* Copyright (c) 1992 Torsten Poulin
 * 23-Feb-92 Version 1
 *
 * The bit-map code in this program was picked up from Dr. Dobb's
 * I think, but I don't remember which volume ;-(
 */

/* Define LOCKALL to ensure that directories being examined
 * don't disappear before we're done with them.
 * The disadvantage is that we use more memory.
 */
#define LOCKALL


/****** English:DIRTREE ***************************************************
*
*   FORMAT
*       DirTree [DIR] {<directory>} [NOHEAD]
*
*   TEMPLATE
*       DIR/A/M,NOHEAD/S
*
*   PURPOSE
*       To print sub-directory trees.
*
*   SPECIFICATION
*       DirTree displays the sub-directory trees for the directories
*       or volumes specified in the DIR argument.  The NOHEAD switch
*       suppresses the printing of the header information.
*
*   EXAMPLE
*           1> DIRTREE ram:
*           Directory tree for "ram:" on Saturday 22-Feb-92
*
*           Ram Disk
*           +---Clipboards
*           +---env
*           |   `---sys
*           |  
*           `---T
*
*   SEE ALSO
*       DIR, LIST
*
***************************************************************************
*
*/
/****** dansk:DIRTREE *****************************************************
*
*   FORMAT
*       DirTree [DIR] {<katalog>} [NOHEAD]
*
*   SKABELON
*       DIR/A/M,NOHEAD/S
*
*   FORMÅL
*       At udskrive underkatalogtræer.
*
*   SPECIFIKATION
*       DirTree viser underkatalogtræer for de kataloger eller diske
*       som er givet ved argumentet DIR.  Kontakten NOHEAD
*       undertrykker udskrivningen af overskriftinformationen.
*
*   EKSEMPEL
*           1> DIRTREE ram:
*           Directory tree for "ram:" on Saturday 22-Feb-92
*
*           Ram Disk
*           +---Clipboards
*           +---env
*           |   `---sys
*           |  
*           `---T
*
*   SE OGSÅ
*       DIR, LIST
*
***************************************************************************
*
*/


#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <dos/datetime.h>
#include <string.h>

#include <proto/exec.h>
#include <proto/dos.h>

/* my include files are for early V36, so ... */
#pragma libcall UtilityBase Stricmp A2 9802
LONG Stricmp(char *, char *);

#define BAR "|   "
#define ELL "`---"
#define TEE "+---"

struct nameList {
    struct nameList *next;
    char *name;
#ifdef LOCKALL
    BPTR lock;
#endif
};

typedef struct global {
    struct Library      *SysBase;
    struct DosLibrary   *DOSBase;
    struct Library      *UtilityBase;

    char Map[64 / 8];
} Global;

char const *version = "\0$VER: DirTree 37.1 (23.2.92) ©1992 Torsten Poulin";

static VOID setbit(Global *global, LONG c, LONG val);
static VOID print_bars(Global *global, LONG depth, LONG terminate);
static LONG printdtree(Global *global, UBYTE *dname, LONG others);
LONG dirtree(Global *global, char *dirname, BOOL nohead);


LONG entrypoint(VOID)
{
    struct Library      *SysBase;
    struct DosLibrary   *DOSBase;
    struct Library      *UtilityBase;
    Global *global;
        
    struct RDArgs *args;
    LONG   arg[2];
    LONG   rc = RETURN_OK;
    UBYTE  **dir;

    arg[0] = arg[1] = 0L;
    
    SysBase = *(struct Library **) 4L;
    
    if(!(global = AllocMem(sizeof(Global), MEMF_PUBLIC | MEMF_CLEAR)))
        return RETURN_FAIL;

    global->SysBase = SysBase;
    if(!(global->DOSBase = DOSBase = (struct DosLibrary *)
                        OpenLibrary("dos.library", 37L)))
    {
        rc = RETURN_FAIL;
        goto noDOS;
    }
    if(!(global->UtilityBase = UtilityBase = 
                        OpenLibrary("utility.library", 37L)))
    {
        rc = RETURN_FAIL;
        goto noUtility;
    }
                                                                        
    if(args = ReadArgs("DIR/A/M,NOHEAD/S", arg, NULL))
    {
        for(dir = (UBYTE **) *arg; *dir && rc == RETURN_OK; dir++)
            rc = dirtree(global, *dir, (BOOL) arg[1]);
        FreeArgs(args);
    }
    else
    {
        LONG err = IoErr();
        PrintFault(err, "DirTree");
        rc = RETURN_ERROR;
    }
    
    CloseLibrary(UtilityBase);
 noUtility:
    CloseLibrary((struct DosBase *) DOSBase);
 noDOS:
    FreeMem(global, sizeof(Global));
    return rc;
}


#define testbit(x)  ( global->Map[x >> 3] & (1 << (x & 0x07)) )

static VOID setbit(Global *global, LONG c, LONG val)
{
    if(val)
        global->Map[c >> 3] |= 1 << (c & 0x07);
    else
        global->Map[c >> 3] &= ~(1 << (c & 0x07));
}


static VOID print_bars(Global *global, LONG depth, LONG terminate)
{
    struct DosLibrary *DOSBase = global->DOSBase;
    LONG i;
    
    for(i = 0; i < depth - 1; i++)
        PutStr(testbit(i) ? BAR : "    ");
        
    if(terminate)
        PutStr("\n");
}


static LONG printdtree(Global *global, UBYTE *dname, LONG others)
{
    struct Library    *SysBase      = global->SysBase;
    struct DosLibrary *DOSBase      = global->DOSBase;
    struct Library    *UtilityBase  = global->UtilityBase;
    
    struct FileInfoBlock *m;
    BPTR   lock, oldlock;
    static LONG depth = -1;
    LONG   count = 0;
    LONG   rc = RETURN_OK;
    ULONG  ADOtag = TAG_DONE;
    struct nameList *list, *p;
    BOOL   OutOfMemory = FALSE;

    list = NULL;

    m = AllocDosObject(DOS_FIB, (struct TagItem *) &ADOtag);
    
    if(lock = Lock(dname, SHARED_LOCK))
    {
        oldlock = CurrentDir(lock);
        if(Examine(lock, m))
        {
            if(m->fib_DirEntryType < 0)
            {
                PrintFault(ERROR_OBJECT_WRONG_TYPE, dname);
                CurrentDir(oldlock);
                UnLock(lock);
                FreeDosObject(DOS_FIB, m);
                return RETURN_WARN;
            }

            if(++depth)
            {
                print_bars(global, depth, 0);
                PutStr(others ? TEE : ELL);
            }
            PutStr(m->fib_FileName /*dname*/);
            PutStr("\n");

            while(ExNext(lock, m))
                if(m->fib_DirEntryType > 0)
                {
                    struct nameList *newnode;

                    /* Store the directory name */
                    if(!(newnode = AllocMem(sizeof(struct nameList),
                                            MEMF_PUBLIC)))
                    {
                        PrintFault(ERROR_NO_FREE_STORE, "DirTree");
                        rc = RETURN_FAIL;
                        OutOfMemory = TRUE;
                        break;
                    }
                    if(!(newnode->name = AllocMem(strlen(m->fib_FileName) + 1,
                                                  MEMF_PUBLIC)))
                    {
                        PrintFault(ERROR_NO_FREE_STORE, "DirTree");
                        rc = RETURN_FAIL;
                        OutOfMemory = TRUE;
                        break;
                    }
                    strcpy(newnode->name, m->fib_FileName);
#ifdef LOCKALL
                    newnode->lock = Lock(m->fib_FileName, SHARED_LOCK);
#endif
                    /* Insert */
                    newnode->next = NULL;
                
                    if(list == NULL)    /* insert into empty list */
                        list = newnode;
                    else
                    {
                        /* inserting into nonempty list */
                        p = list;

                        if(Stricmp(m->fib_FileName, p->name) < 0)
                        {
                            /* insert before first node */
                            newnode->next = list;
	                    list = newnode;
                        }
                        else
                        {
                            /* general case */
	                    for(; p->next; p = p->next)
	                        if(Stricmp(m->fib_FileName,p->next->name) < 0)
	                            break;
	                    newnode->next = p->next;
	                    p->next = newnode;
                        }
                    }
                    ++count;
                }
        }

        /* Traverse the list and free up memory */
        for(p = list; p; )
        {
            struct nameList *remember;
                
            remember = p;
            if(!OutOfMemory)
            {
                --count;
                setbit(global, depth, count);
                rc = printdtree(global, p->name, count);
            }
            p = p->next;
#ifdef LOCKALL
            UnLock(remember->lock);
#endif
            FreeMem(remember->name, strlen(remember->name) + 1);
            FreeMem(remember, sizeof(struct nameList));
        }
        CurrentDir(oldlock);
        UnLock(lock);
    }
    else
    {
        LONG err = IoErr();
        PrintFault(err, dname);
        rc = RETURN_ERROR;
    }
    if(!others)
        print_bars(global, depth, 1);
    --depth;
  
    FreeDosObject(DOS_FIB, m);  
    return rc;
}


LONG dirtree(Global *global, char *dirname, BOOL nohead)
{
    struct DosLibrary *DOSBase = global->DOSBase;
    struct DateTime dt;
    char day[LEN_DATSTRING], date[LEN_DATSTRING];
    
    if(!nohead)
    {
        DateStamp(&dt.dat_Stamp);
        dt.dat_Format  = FORMAT_DOS;
        dt.dat_Flags   = NULL;
        dt.dat_StrDay  = day;
        dt.dat_StrDate = date;
        dt.dat_StrTime = NULL;
        DateToStr(&dt);

        PutStr("Directory tree for \"");
        PutStr(dirname);
        PutStr("\" on ");
        PutStr(day);
        PutStr(" ");
        PutStr(date);
        PutStr("\n\n");
    }
    return printdtree(global, dirname, 0);
}
