/*
** DTree.c 1.00
** Copyright (c) 1993 by Sam Yee.
** All Rights Reserved.
**
** Permission is here granted for the distribution of DTree, provided
** that files are intacted and in unmodified state.
*/

/*******************************************************************/
/* includes */
#include <clib/alib_protos.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <dos/dos.h>
#include <dos/dosasl.h>
#include <exec/memory.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <stdlib.h>
#include <string.h>

/*******************************************************************/
/* constants, macros, etc. */
/* passing arguments through registers sometimes yield smaller code */
#define REG(x)          register __ ## x
#define ASM             __asm

/* for ReadArgs() */
#define OPT_DIRS        0
#define OPT_SHOWFILES   1
#define OPT_IBMFONT     2
#define OPT_JOINCHARS   3
#define OPT_COLOR       4
#define OPT_COUNT       5

/* ansi control sequences */
#define ANSIOFF "\033[0m"
#define COLOR1  "\033[31m"
#define COLOR2  "\033[32m"
#define COLOR3  "\033[33m"
#define COLOR6  "\033[36m"

/* template for ReadArgs() */
#define TEMPLATE "DTree 1.00 -- Directory Tree Displayer\n\
Copyright (c) 1993 by Sam Yee.\n\
All Rights Reserved.  Freely Distributable.\n\
Compiled on "__DATE__ " (" __TIME__").\n\n\
DIR/M,FILES/S,IBMFONT/S,JOINCHARS/K,COLOR/S"

#define LEADING_SPACES  3

/* extract a byte from 32-bit branch display character mask */
#define GET_VBAR(x) ((x) & 0xff)
#define GET_L(x)    (((x) & 0xff00) >> 8)
#define GET_T90(x)  (((x) & 0xff0000) >> 16)
#define GET_DASH(x) (((x) & 0xff000000) >> 24)

/*******************************************************************/
/* need these for #pragmas */
extern struct DOSBase  *DOSBase;
extern struct SysBase  *SysBase;

/*******************************************************************/
/* when the FILES argument is given, store each file in a node, and
   display the whole linked list when the directory is done scanning
*/
typedef
struct
{
    struct List list;
    int         node_count;
} FileList;

/*******************************************************************/
/* prototypes */

/* disable SAS/C's ^C checking */
void __regargs __chkabort(void);
void __regargs __chkabort(){}
void __regargs _CXBRK(void);
void __regargs _CXBRK(){}

BOOL tree_scan(BPTR dir_lock, struct FileInfoBlock *fib, int depth,
               BOOL *nextdir_array, BOOL show_files,
               ULONG join_chars, BOOL color);
struct Node * ASM AddName(REG(a0) FileList *list,
                          REG(a1) char *name);
BOOL ASM do_dir(REG(a0) char *dirname, REG(d0) BOOL show_files,
                REG(d1) ULONG join_chars, REG(d2) BOOL color);
void ASM myqsort(REG(a0) char *v[], REG(d0) int left,
                 REG(d1) int right);
void ASM swap(REG(a0) char *v[], REG(d0) int i, REG(d1) int j);
char ** ASM SortFileList(REG(a0) FileList *file_list);
BOOL NextDirLock(BPTR this_lock, struct FileInfoBlock *fib,
                 FileList *list, BOOL *die);

/*******************************************************************/
/* some global (oh no!) data */
char *__stdiowin = NULL,
     *__stdiov37 = NULL,
     *version = "$VER: DTree 1.00 (14.7.93)";

/*******************************************************************/
/* main routine, what else? */
int
main(void)
{
    struct RDArgs   *myrda;
    UBYTE           **dirs;
                    /* IBM extended characters */
    ULONG           join_chars = 0x2d2b5c7c;
    LONG            result[OPT_COUNT];
    long            rc = 10,
                    i;

    if (myrda = (struct RDArgs *)AllocDosObject(DOS_RDARGS,NULL))
    {
            /* read from stdin instead */
        myrda->RDA_Source.CS_Buffer = NULL;
        myrda->RDA_Source.CS_Length = 0L;

        for (i = 0; i < OPT_COUNT; i++)
            result[i] = 0L;

        if (ReadArgs(TEMPLATE,result,myrda))
        {
            if (result[OPT_IBMFONT])
                join_chars = 0xc4c3c0b3;
            else if (i = result[OPT_JOINCHARS])
            {
                if (StrToLong((char *)i,&join_chars) < 0)
                    join_chars = *((ULONG *)i);
            }

            if (dirs = (UBYTE **)result[OPT_DIRS])
            {
                for (i = 0; dirs[i]; i++)
                {
                    if (!do_dir(dirs[i],result[OPT_SHOWFILES],
                                join_chars,
                                result[OPT_COLOR]) && dirs[i+1])
                        PutStr("\n");
                    else
                        break;
                }
            }
            else    /* current directory as default */
                do_dir("",result[OPT_SHOWFILES],join_chars,
                       result[OPT_COLOR]);

            if (result[OPT_COLOR])
                PutStr(ANSIOFF);

            FreeArgs(myrda);
            rc = 0;
        }
        else
            PrintFault(IoErr(),NULL);

        FreeDosObject(DOS_RDARGS,myrda);
    }

    return(rc);
}

/*******************************************************************/
/* display the tree of one directory */
BOOL ASM /* ^c break? */
do_dir(REG(a0) char *dirname,
       REG(d0) BOOL show_files,
       REG(d1) ULONG join_chars,
       REG(d2) BOOL color)
{
    struct FileInfoBlock    *fib;
    BPTR                    dir_lock;
    BOOL                    broke = FALSE,
                            nextdir_array[64];
    char                    fname[108];

    if (dir_lock = Lock(dirname,ACCESS_READ))
    {
        if (fib = (struct FileInfoBlock *)
                        AllocDosObject(DOS_FIB,NULL))
        {
            memset(nextdir_array,0,sizeof(nextdir_array));

                /* get real name */
            if (NameFromLock(dir_lock,fname,sizeof(fname)))
            {
                if (color)
                    PutStr(COLOR6);

                PutStr(fname);
                PutStr("\n");
            }

            if (broke = tree_scan(dir_lock,fib,0,nextdir_array,
                                  show_files,join_chars,color))
                PrintFault(ERROR_BREAK,NULL);

            FreeDosObject(DOS_FIB,fib);
        }

        UnLock(dir_lock);
    }
    else
        PrintFault(IoErr(),NULL);

    return(broke);
}

/*******************************************************************/
/* find the next directory lock, given the current one */
BOOL /* found next dir? */
NextDirLock(BPTR                    this_lock,
            struct FileInfoBlock    *fib,
            FileList                *list,
            BOOL                    *die)
{
        /* scan for next directory, or parent directory ends */
    while (ExNext(this_lock,fib))
    {
        if(CheckSignal(SIGBREAKF_CTRL_C))
        {
            *die = TRUE;
            break;
        }
        else if (fib->fib_DirEntryType > 0)  /* a dir */
            return(TRUE);
        else if (list)  /* put filename into link list? */
            AddName(list,fib->fib_FileName);
    }

    return(FALSE);
}

/*******************************************************************/
/* display a branch to parent */
void
DisplayBranch(char  *label,
              int   depth,
              BOOL  *nextdir_array,
              BOOL  show_files,
              ULONG join_chars,
              BOOL  is_dir,
              BOOL  color)
{
    BPTR    out_fh = Output();
    char    *col_str = COLOR1;  /* color for a file */
    int     i,
            j,
            c = 0;

    PutStr(color ? COLOR3 : "");

    for (i = 0; i < depth; i++)
    {
        FPutC(out_fh,nextdir_array[i] ? GET_VBAR(join_chars) : ' ');

        for(j = 0; j < LEADING_SPACES; j++)
            FPutC(out_fh,' ');
    }

    if (is_dir)
    {
        col_str = COLOR2;
        FPutC(out_fh,nextdir_array[depth] ?
                     GET_T90(join_chars) : GET_L(join_chars));
        c = GET_DASH(join_chars);
    }
    else if (show_files)
    {
        FPutC(out_fh,nextdir_array[depth] ?
                     GET_VBAR(join_chars) : ' ');
        c = ' ';
    }

    if (c)
        for (i = 0; i < LEADING_SPACES; i++)
            FPutC(out_fh,c);

    if (color)
        PutStr(col_str);

    PutStr(label);
    PutStr("\n");
}

/*******************************************************************/
/* return a sorted array of strings, given a linked list,
   use FreeVec() to free string array.
   note: link list scanning could have been optimized, but
         we want keep the nodes as ADT */
char ** ASM
SortFileList(REG(a0) FileList   *file_list)
{
    struct Node *node;
    struct List temp_list;
    char        **fnames;
    int         i = 0;

    if (fnames = (char **)AllocVec(file_list->node_count<<2,
                                   MEMF_PUBLIC))
    {
        NewList(&temp_list);

        while (node = RemHead((struct List *)file_list))
        {
            AddTail(&temp_list,node);   /* re-queue */
            fnames[i++] = node->ln_Name;
        }

            /* put things back */
        while (node = RemTail(&temp_list))
            AddHead((struct List *)file_list,node);

        myqsort(fnames,0,file_list->node_count-1);
    }

    return(fnames);
}

/*******************************************************************/
/* scan the directory given */
BOOL    /* use press ^C? */
tree_scan(BPTR                  dir_lock,
          struct FileInfoBlock  *fib,
          int                   depth,  /* how deep from parent? */
          BOOL                  *nextdir_array,
          BOOL                  show_files,
          ULONG                 join_chars,
          BOOL                  color)
{
    struct Node             *node;
    FileList                file_list;
    struct FileInfoBlock    *new_fib;
    BPTR                    new_lock;
    char                    fname[108];
    BOOL                    die = FALSE,
                            is_dir;

    NewList((struct List *)&file_list);
    file_list.node_count = 0;

    if (Examine(dir_lock,fib))
    {
        while (ExNext(dir_lock,fib) && !die)
        {
            if (show_files && (fib->fib_DirEntryType < 0))
                AddName(&file_list,fib->fib_FileName);

            while (TRUE)
            {
                if (CheckSignal(SIGBREAKF_CTRL_C))
                {
                    die = TRUE;
                    break;
                }

                is_dir = (fib->fib_DirEntryType > 0) ? TRUE : FALSE;
                NameFromLock(dir_lock,fname,108);
                AddPart(fname,fib->fib_FileName,108);
                nextdir_array[depth] =
                    NextDirLock(dir_lock,fib,show_files ? &file_list
                                                        : NULL,&die);

                if (is_dir && !die)
                {
                    DisplayBranch(FilePart(fname),depth,
                                  nextdir_array,show_files,join_chars,
                                  is_dir,color);

                    if (new_lock = Lock(fname,ACCESS_READ))
                    {
                        if (new_fib = (struct FileInfoBlock *)
                                        AllocDosObject(DOS_FIB,NULL))
                        {
                            /* recursively, go into the sub-dir */
                            die = tree_scan(new_lock,new_fib,depth+1,
                                            nextdir_array,show_files,
                                            join_chars,color);
                            FreeDosObject(DOS_FIB,new_fib);
                        }

                        UnLock(new_lock);
                    }
                }

                if(!nextdir_array[depth] || die)
                    break;
            }
        }

        if (file_list.node_count && !die)
        {
            int     i;
            char    **fnames;

            if (fnames = SortFileList(&file_list))
            {
                for (i = 0; i < file_list.node_count; i++)
                {
                    if(CheckSignal(SIGBREAKF_CTRL_C))
                    {
                        die = TRUE;
                        break;
                    }

                    DisplayBranch(fnames[i],depth,nextdir_array,
                                  show_files,join_chars,FALSE,color);
                }

                FreeVec(fnames);
            }

            if (depth && !die)
            {
                nextdir_array[depth] = FALSE;
                DisplayBranch("",depth,nextdir_array,show_files,
                              join_chars,FALSE,color);
            }
        }

        while (node = RemHead((struct List *)&file_list))
            FreeVec(node);
    }

    return(die);
}

/*******************************************************************/
/* copy a filename into filenames link list */
struct Node * ASM
AddName(REG(a0) FileList    *list,
        REG(a1) char        *name)
{
    struct Node *node;
    char        *copy;

    if (node = AllocVec(sizeof(struct Node) + strlen(name)+1,
                        MEMF_PUBLIC))
    {
        copy = ((char *)node) + sizeof(struct Node);
        strcpy(copy,name);
        node->ln_Name = copy;
        AddTail((struct List *)list,node);
        list->node_count++;
    }

    return(node);
}

/*******************************************************************/
/* a significantly smaller quicksort than qsort() */
void ASM
myqsort(REG(a0) char    *v[],
        REG(d0) int     left,
        REG(d1) int     right)
{
    int i,
        last;

    if (left < right)
    {
        swap(v,left,(left + right)>>1);
        last = left;

        for (i = left+1; i <= right; i++)
        {
            if (stricmp(v[i], v[left]) < 0)
                swap(v, ++last, i);
        }

        swap(v, left, last);
        myqsort(v, left, last-1);
        myqsort(v, last+1, right);
    }
}

/*******************************************************************/
/* swap two text pointers */
void ASM
swap(REG(a0) char   *v[],
     REG(d0) int    i,
     REG(d1) int    j)
{
    char *temp;

    temp = v[i];
    v[i] = v[j];
    v[j] = temp;
}

/*******************************************************************/
/* END */
