/* tree.c for !DiscEx */


#include "main.h"
#include "alloc.h"
#include "discmacros.h"
#include "dinfo.h"
#include "ansctr.h"
#include "tree.h"
#include "setup.h"


#define  TIME_SLICE  10       /* default time slice in centiseconds */


/* the structure of a RISCOS directory entry as returned by OS_GBPB 11 */

typedef struct {
    unsigned int load;
    unsigned int exec;
    unsigned int length;
    unsigned int attributes;
    int type;                   /* 1 => File, 2 => Dir, 3 => Image file */
    IDA sin;
    char timestamp[5];
    char name[11];              /* max 10 chars for FileCore */
} DirEntryRec, *DirEntryPtr;


#define  DIR_BUFF_SIZE  1024  /* buffer space for each OS_GBPB 11 call */

typedef struct _dirbuffrec {
    int index;                    /* for next call of OS_GBPB 11;
                                       -1 => all done */
    int count;                    /* number of entries remaining in buffer,
                                       including the current one */
    DirEntryPtr entry;            /* points to current entry in buffer;
                                       NULL => no more in buffer */
    char buff [DIR_BUFF_SIZE];    /* filled up by OS_GBPB */
} DirBuffRec, *DirBuffPtr;


/*
 * This structure defines the state of a scan of the directory and file
 *  hierarchy for a particular drive.
 *
 * It is used to preserve necessary information between calls to Wimp_Poll
 *  while "gathering" information in preparation for analysis.
 */

typedef struct _TreeScanState {
    ForDPtr root;       /* addresses the root of the dir/file structure */
    char currdir[257];  /* path name of directory currently being scanned */
    DirBuffPtr dirbuff; /* identifies current scan position */
    ForDPtr preventry;  /* addresses most recently stored entry */

    /*
     * The next two fields define the current position in the scan:
     *  currentry[0] ... currentry[depth] address the ForDRecs for each of
     *  the components of the directory about to be scanned.
     * On entry to tree_continue_scan(), currdir will contain the
     *  corresponding path name.
     */
    int depth;
    ForDPtr currentry[MAXCOMPONENTS];
} TreeScanStateRec, *TreeScanStatePtr;



/*
 * On entry, *entry addresses an ForDRec 'r'.
 * On exit, *entry addresses the ForDRec following 'r', if any: the result
 *  is TRUE; if 'r' is the last ForDRec in a directory, then the result is
 *  FALSE.
 *
 * Any inter-record links are traversed transparently.
 */

static Bool tree_next_entry (ForDPtr *entry)
{
    ForDPtr p = *entry;
    char *next = (char *) p + sizeof (ForDRec) + WORD_SIZE (p->name);

    if (p->v.islast)
        return FALSE;

    if (p->v.haslink)
        *entry = *((ForDPtr *) next);
    else
        *entry = (ForDPtr) next;

    return TRUE;
}


/*
 * Returns the address of the object's parent - or NULL if the object is $
 */

ForDPtr tree_get_parent (ForDPtr object)
{
    char *p;

    while (tree_next_entry (&object));
    p = (char *) object + sizeof (ForDRec) + WORD_SIZE (object->name);
    return *((ForDPtr *) p);

}


static error * tree_walk_dir (
    ForDPtr direntry,
    error * (*f) (ForDPtr entry, void *handle),
    void *handle
)
{
    ForDPtr entry = direntry->u.son;

    if (entry == NULL)
        return NULL;

    do
    {
        ER ( f (entry, handle) );

        if (entry->v.isdir)
        {
            ER ( tree_walk_dir (entry, f, handle) );
        }
    } while (tree_next_entry (&entry));

    return NULL;
}


error * tree_walk
(
    ScanListPtr p,
    error * (*f) (ForDPtr entry, void *handle),
    void *handle
)
{
    TreeScanStatePtr scan = p->tree;

    ER ( f (scan->root, handle) );
    ER ( tree_walk_dir (scan->root, f, handle) );

    return NULL;
}


/*
 * Creates a new ForDRec, allocating space as necessary; its address is
 *  returned in 'entry'.
 *
 * Always ensures that there is at least space for one pointer following the
 *  record (might be used as a link, or as a parent pointer).
 */

static error * store_entry (ScanListPtr pp, char *name, ForDPtr *entry)
{
    TreeScanStatePtr p = pp->tree;
    BigBlockDefPtr a = pp->alloc;
    int size = sizeof (ForDRec) + WORD_SIZE (name);

    if (a->nextfree + size + sizeof (ForDPtr) >= a->endfree)
    {
        ForDPtr *link = (ForDPtr *) a->nextfree;

        ER ( alloc_big_block (a) );

        if (!p->preventry->v.islast)
        {
            p->preventry->v.haslink = TRUE;
            *link = (ForDPtr) a->nextfree;
        }
    }

    memset (a->nextfree, 0, sizeof (ForDRec));

    p->preventry = *entry = (ForDPtr) a->nextfree;
    strcpy (p->preventry->name, name);

    a->nextfree += size;
   
    return NULL;
}


/*
 * This is called immediately after the last ForDRec for a directory has been
 *  stored; a pointer to the directory's parent is stored, and the record is
 *  marked as the last one in that directory.
 *
 * Note that store_entry(..) always ensures that there is sufficient space
 *  for this pointer.
 */

static void store_parent_pointer (ScanListPtr pp, ForDPtr parent)
{
    TreeScanStatePtr p = pp->tree;
    BigBlockDefPtr a = pp->alloc;

    p->preventry->v.islast = TRUE;

    *((ForDPtr *) a->nextfree) = parent;
    a->nextfree += sizeof (ForDPtr);

    return;
}


/*
 * Try to (re)fill the directory buffer.
 *
 * On entry:
 *    dirbuff->index gives the value to pass to OS_GBPB 11 (may be -1)
 *
 * On successful exit:
 *    *more is TRUE (to indicate that the buffer contains some entries)
 *    dirbuff->index gives the next value to pass to OS_GBPB 11 (may be -1)
 *    dirbuff->count is the number of entries read into the buffer
 *    dirbuff->entry addresses the first entry in the buffer
 *
 * On unsuccessful exit:
 *    *more is is FALSE
 */

static error * refill_buffer (ScanListPtr pp, Bool *more)
{
    TreeScanStatePtr p = pp->tree;

    if (p->dirbuff->index == -1)
    {
        *more = FALSE;
        return NULL;
    }

    ER ( _swix (OS_GBPB, I0|I1|I2|I3|I4|I5|I6|O3|O4,
                       11,
                       p->currdir,
                       p->dirbuff->buff,
                       1000,
                       p->dirbuff->index,
                       DIR_BUFF_SIZE,
                       0,
                       &p->dirbuff->count,
                       &p->dirbuff->index) );

    if (p->dirbuff->count == 0 && p->dirbuff->index != -1)
        return error_lookup ("FunnyOSGBPB");
                          
    p->dirbuff->entry = (DirEntryPtr) p->dirbuff->buff;

    *more = (p->dirbuff->count != 0);
    return NULL;
}


/*
 * Updates dirbuff->entry to address the next entry, if any; returns TRUE iff
 *  there is another entry.
 */

static Bool next_directory_entry (ScanListPtr pp)
{
    TreeScanStatePtr p = pp->tree;

    p->dirbuff->count--;
    if (p->dirbuff->count == 0)
        return FALSE;
    else
    {
        char *q = (char *) p->dirbuff->entry;
        char *name = p->dirbuff->entry->name;

        q += offsetof (DirEntryRec, name);
        q += strlen (name) + 1;
        p->dirbuff->entry = (DirEntryPtr) ( ((unsigned int)q + 3) & ~3 );
    }

    return TRUE;
}


/*
 * Constructs ForDRec's for each of the entries in the directory 'currdir'.
 * On exit, *entry addresses the first one - or is NULL if the directory is
 *  empty.
 * Links are created where necessary (by store_entry(..)), but it is left to
 *  the caller to call store_parent_pointer(..).
 */

static error * read_this_directory (ScanListPtr pp, ForDPtr *entry)
{
    TreeScanStatePtr p = pp->tree;
    Bool firsttime = TRUE;
    Bool more = FALSE;

    *entry = NULL;

    p->dirbuff->index = 0;
    while (TRUE)
    {
        ER ( refill_buffer (pp, &more) );
        if (!more)
            break;

        do
        {
            ForDPtr ford;

            ER ( store_entry (pp, p->dirbuff->entry->name, &ford) );
            ford->v.sector = p->dirbuff->entry->sin.sector;
            ford->v.fragid = p->dirbuff->entry->sin.fragid;
            ford->v.isdir = p->dirbuff->entry->type == 2;
            ford->u.len = p->dirbuff->entry->length;

            if (firsttime)
            {
                firsttime = FALSE;
                *entry = p->preventry;
            }
        } while (next_directory_entry (pp));
    }

    return NULL;
}


/*
 * Adds a leaf name to 'currdir'.
 */

static void add_component (char *currdir, char *name)
{
    char *s = currdir + strlen (currdir);

    s[0] = '.';
    strcpy (s+1, name);

    if (strlen (s) > 256)
        EE ( error_lookup ("CurrdirOV") );

    return;
}


/*
 * Removes the leaf name from 'currdir'.
 */

static void remove_component (char *currdir)
{
    int n = strlen (currdir) - 1;

    while (currdir[n] != '.')
        n--;

    if (n < 0)
        EE ( error_lookup ("CurrdirUV") );

    currdir[n] = 0;

    return;
}


/*
 * Resumes the specified tree scan, until the current timeslice runs out
 *  (or the scan is complete).
 */

error * tree_continue_scan (ScanListPtr pp, Bool *complete)
{
    TreeScanStatePtr p = pp->tree;
    unsigned int timetostop;
    unsigned int timenow;
    ForDPtr entry = p->currentry[p->depth];
    ForDPtr son;

    _swix (OS_ReadMonotonicTime, O0, &timenow);
    timetostop = timenow + TIME_SLICE;

  L_readdir:
    ER ( read_this_directory (pp, &son) );
    entry->u.son = son;
    if (son == NULL)
        goto L_back1;
    store_parent_pointer (pp, entry);
    entry = son;
    p->depth++;

    if (p->depth >= MAXCOMPONENTS)
        EE ( error_lookup ("CurrentOV") );

  L_examine:
    if (entry->v.isdir)
    {
        add_component (p->currdir, entry->name);
        p->currentry[p->depth] = entry;

        /* is it time to let someone else have a go? */
        _swix (OS_ReadMonotonicTime, O0, &timenow);
        if (timenow >= timetostop)
        {
            *complete = FALSE;
            return NULL;
        }

        goto L_readdir;
    }

  L_movetonext:
    if (tree_next_entry (&entry))
        goto L_examine;

    if (p->depth == 0)
    {
        *complete = TRUE;
        return NULL;
    }

    p->depth--;
    entry = p->currentry[p->depth];

  L_back1:
    remove_component (p->currdir);
    goto L_movetonext;
}


/*
 * Returns name of directory about to be scanned.
 */

char * tree_scan_position (ScanListPtr pp)
{
    return pp->tree->currdir;
}


/*
 * Frees all space associated with the specified tree scan.
 */

error * tree_delete_scan (ScanListPtr pp)
{
    free (pp->tree->dirbuff);
    free (pp->tree);

    return NULL;
}


/*
 * Allocates and initialises a TreeStateScanRec ready to be resumed by a
 *  call of tree_continue_scan(..).
 */

error * tree_init_scan (ScanListPtr pp)
{
    DiscRecordPtr dr = &pp->di->dr;
    TreeScanStatePtr p;

    /* allocate a tree scan state record */
    ER ( check_alloc ((void **) &p, sizeof (TreeScanStateRec)) );
    pp->tree = p;

    /* allocate a dirbuff record */
    ER ( check_alloc ((void **) &p->dirbuff, sizeof (DirBuffRec)) );

    /* create first ForDRec for the root directory */
    sprintf (p->currdir, "%s::%d.$",
                           (pp->FS == FS_ADFS) ? "ADFS" : "SCSI", pp->drive);
    ER ( store_entry (pp, "$", &p->root) );
    p->root->v.isdir = TRUE;
    p->root->v.islast = TRUE;
    p->root->v.fragid = ((IDA *) &dr->root)->fragid;
    p->root->v.sector = ((IDA *) &dr->root)->sector;

    store_parent_pointer (pp, NULL);

    p->depth = 0;
    p->currentry[0] = p->root;

    return NULL;
}


/*
 * Reads the next entry from the specified directory.
 *
 * For the first call, set item = 0; leave this variable "as-is" for future
 *  calls.
 *
 * The result variables are valid only if 'found' is TRUE.
 */

error * tree_next_dir_entry
(
    char *dir,        /* directory name */
    int *item,        /* set this to zero on first call */
    Bool *found,      /* set TRUE iff an entry was found */
    char *name,       /* the object's name is copied here */
    unsigned *size,   /* the object's length is copied here */
    IDA *sin,         /* the object's internal disc address is copied here */
    int *type         /* and its type (1 or 2) is placed here */
)
{
    DirEntryRec buf;
    int numread;

    /* return immediately if we read the last entry last time */
    if (*item == -1)
    {
        *found = FALSE;
        return NULL;
    }

    /* read the next entry */
    ER ( _swix (OS_GBPB, I0|I1|I2|I3|I4|I5|I6|O3|O4,
                       11,
                       (int) dir,
                       (int) &buf,
                       1,
                       *item,
                       sizeof (DirEntryRec),
                       0,
                       (int) &numread,
                       (int) item) );

    /* we hope just one entry will have been read */
    if (numread == 1)
    {
        *found = TRUE;
        strcpy (name, buf.name);
        *size = buf.length;
        *sin = buf.sin;
        *type = (buf.type == 2 ? 2 : 1);
        return NULL;
    }

    /* if not, we must be at the end of an empty directory */
    if (*item == -1)
    {
        *found = FALSE;
        return NULL;
    }

    /* otherwise something strange is happening! */
    return error_lookup ("BadOSGBPB");
}


