/* locate.c for !DiscEx */


#include "main.h"
#include "locate.h"
#include "setup.h"
#include "alloc.h"
#include "discmacros.h"
#include "dinfo.h"
#include "map.h"
#include "ansctr.h"
#include "scan.h"
#include "tree.h"
#include "auxui.h"


#define  LOCATE  "Locate"         /* name of Locate dbox template */


/* component ids in Locate dialogue boxes */

#define  ID_FS           31
#define  ID_Drive        30
#define  ID_Name          4
#define  ID_Type          7
#define  ID_Size          8
#define  ID_Fragid       10
#define  ID_Shared       11
#define  ID_Offset       13
#define  ID_Numfrags     15
#define  ID_Fragnum      17
#define  ID_Fragaddr     19
#define  ID_Fragsize     21
#define  ID_Previous     22
#define  ID_Next         23
#define  ID_Parent       24
#define  ID_Children     25
#define  ID_Locate       26


/* each Locate dbox has its own LocateRec */

typedef struct
{
    unsigned sector;     /* fragment address in sectors */
    unsigned size;       /* fragment size in sectors */
} FragLocRec, *FragLocPtr;

#define  MAX_FRAGS  20

typedef struct
{
    int FS;
    int drive;
    DiscInfoPtr di;
    ObjectId dbox;        /* associated Locate dbox */
    char objname[256];
    int type;             /* 0 => not found, 1 => file, 2 => directory */
    unsigned size;        /* file size in bytes */
    IDA sin;              /* internal disc address */
    int numfrags;         /* will be 1 unless it's a multi-fragment object */
    FragLocRec frags[MAX_FRAGS];
                          /* assume at most 20 fragments per object ... */
} LocateRec, *LocatePtr;



/*
 * Callback function used when scanning the map to locate all the fragments
 *  belonging to an object - see locate_fragments(..) below.
 */

static error * check_for_frag
(
    DiscInfoPtr di, /* address of disc information record */
    int zone,       /* zone number */
    int fragid,     /* fragment id: -1 => free */
    int pos,        /* position as offset in alloc units from zone start */
    int len,        /* length in allocation units */
    void *handle    /* a LocatePtr value */
)
{
    LocatePtr state = (LocatePtr) handle;

    /* see if we're interested */
    if (state->sin.fragid != fragid)
        return NULL;

    {
        int numfrags = state->numfrags;

        if (numfrags == MAX_FRAGS)
            return error_lookup ("TooManyBits", MAX_FRAGS);

        pos += ZONE_START (di, zone);

        pos = SECS_IN_ALLOCS (di, pos);
        len = SECS_IN_ALLOCS (di, len);

        state->frags[numfrags].sector = pos; 
        state->frags[numfrags].size = len;
        state->numfrags++;
    }

    return NULL;
}


/*
 * Updates the 'state' record to contain details of every fragment which
 *  contains part of the object.
 */

static error * locate_fragments (LocatePtr state)
{
    int zone, homezone;
    int fragid = state->sin.fragid;
    DiscInfoPtr di = state->di;
    DiscRecordPtr dr = &di->dr;

    /* clear numfrags field */
    state->numfrags = 0;

    /* determine home zone for this object */
    homezone = HOME_ZONE (di, fragid);

    /* scan each zone map starting with the home zone */
    zone = homezone;
    do
    {
        char *map;

        ER ( map_find_zone (di, zone, &map) );

        ER ( map_scan_block (di,                 /* disc information */
                             map,                /* address of map ... */
                             zone,               /* ... for this zone */
                             check_for_frag,     /* call for each fragment */
                             (void *) state      /* with this parameter */
                            ) );
        zone++;
        if (zone == dr->nzones)         /* zones scanned cyclically */
            zone = 0;
    } while (zone != homezone);

    return NULL;
}


/*
 * The object name is checked, and split up into directory and leaf name.
 *  The directory name is copied to 'dir', and 'leaf' is made to point to the
 *  leaf part.
 *
 * The object name must start with '$' and must not finish with '.'; if it
 *  consists just of '$', then nothing is copied to dir, and leaf is set to
 *  point to the zero terminator following '$'.
 */

static error * locate_analyse_name
(
    char *name,          /* addresses object name */
    char *dir,           /* parent part of name is copied here */
    char **leaf          /* will point to the leaf part of the name */
)
{
    char *s;

    /* must start with '$' */
    if (name[0] != '$')
        return error_lookup ("BadName");

    /* '$' alone is a special case */
    if (name[1] == 0)
    {
        *leaf = name + 1;
        return NULL;
    }

    /* '$' must be followed by a separator */
    if (name[1] != '.')
        return error_lookup ("BadName");

    /* find the right-most separator */
    s = name + strlen (name) - 1;
    while (*s != '.')
        s--;

    /* must not be the final character of the name */
    if (s[1] == 0)
        return error_lookup ("BadName");

    /* copy directory portion */
    *s = 0;
    strcpy (dir, name);
    *s = '.';

    *leaf = s + 1;
    return NULL;
}


/*
 * Check object name format, read appropriate directory entry, and update
 *  status fields objname, type, size and sin accordingly.
 */

static error * locate_object (LocatePtr state, int how)
{
    char dir[300];
    char *leaf;
    int item;
    Bool found;
    Bool firsttime;
    unsigned int size;
    IDA sin;
    int type;
    char s[11];
    char origleaf[11];

    state->type = 0;   /* which means "not found": in case of error */

    /* object name must start with <FS>::<drive>. */
    sprintf (dir, "%s::%d.", state->FS == FS_ADFS ? "ADFS" : "SCSI",
                             state->drive);

    ER ( locate_analyse_name (state->objname, dir + strlen (dir), &leaf) );

    if (*leaf == 0)       /* special case of '$' */
    {
        switch (how)
        {
        case ID_Previous:
        case ID_Next:
        case ID_Parent:
            return error_lookup ("BadReq");

        case ID_Locate:
            state->type = 2;      /* directory */
            state->size = 2048;   /* which is always 2k long */
            state->sin = *((IDA *) &state->di->dr.root);
            return NULL;

        case ID_Children:
            item = 0;
            *leaf = '.';
            leaf++;
            strcpy (dir + strlen (dir), "$");
            ER ( tree_next_dir_entry (dir, &item, &found, leaf,
                                 &state->size, &state->sin, &state->type) );
            if (!found)
                return error_lookup ("EmptyDir");
            else
                return NULL;
        }
    }
    else
    {
        switch (how)
        {
        case ID_Previous:
            firsttime = TRUE;
            strcpy (origleaf, leaf);
            item = 0;
            while (TRUE)
            {
                ER ( tree_next_dir_entry (dir, &item, &found, s,
                                                 &size, &sin, &type) );
                if (!found)
                {
                    strcpy (leaf, origleaf);
                    state->type = 0;
                    return error_lookup ("LostObject");
                }

                if (equalstr_ci (s, origleaf))
                {
                    if (firsttime)
                    {
                        strcpy (leaf, origleaf);
                        state->type = 0;
                        return error_lookup ("NoPrev");
                    }
                    return NULL;
                }

                firsttime = FALSE;
                strcpy (leaf, s);
                state->size = size;
                state->sin = sin;
                state->type = type;
            }

        case ID_Next:
            item = 0;
            while (TRUE)
            {
                ER ( tree_next_dir_entry (dir, &item, &found, s,
                                                 &size, &sin, &type) );
                if (!found)
                    return error_lookup ("LostObject");

                if (equalstr_ci (s, leaf))
                {
                    ER ( tree_next_dir_entry (dir, &item, &found, s,
                                                     &size, &sin, &type) );
                    if (!found)
                        return error_lookup ("NoNext");

                    strcpy (leaf, s);
                    state->size = size;
                    state->sin = sin;
                    state->type = type;
                    return NULL;
                }
            }

        case ID_Locate:
            item = 0;
            while (TRUE)
            {
                ER ( tree_next_dir_entry (dir, &item, &found, s,
                                                 &size, &sin, &type) );
                if (!found)
                    return error_lookup ("LostObject");

                if (equalstr_ci (s, leaf))
                {
                    state->size = size;
                    state->sin = sin;
                    state->type = type;
                    return NULL;
                }
            }

        case ID_Children:
            strcpy (dir + strlen (dir), leaf - 1);
            leaf += strlen (leaf);
            item = 0;
            ER ( tree_next_dir_entry (dir, &item, &found, s,
                                             &size, &sin, &type) );
            if (!found)
                return error_lookup ("EmptyDir");

            *leaf = '.';
            strcpy (leaf + 1, s);
            state->size = size;
            state->sin = sin;
            state->type = type;
            return NULL;

        case ID_Parent:
            *(leaf - 1) = 0;
            return locate_object (state, ID_Locate);
        }
    }

    return error_lookup ("UnexpRet");
}


/*
 * Updates the state record according to 'how', which can take one of the
 *  values ID_Previous, ID_Next, ID_Parent, ID_Children, ID_Locate; any
 *  other value is ignored.
 *
 * Returns suitable error message if the request cannot be satisfied.
 */

static error * locate_update_state (LocatePtr state, int how)
{
    /* check valid request */
    switch (how)
    {
        case ID_Previous:
        case ID_Next:
        case ID_Parent:
        case ID_Children:
        case ID_Locate:
            break;
        default:
            return NULL;
    }

    /* process object name, and fill in type, size and disc address */
    ER ( locate_object (state, how) );

    /* read FileCore map */
    ER ( map_read (state->drive, state->di) );

    /* locate and record details of object fragment(s) */
    ER ( locate_fragments (state) );

    /* free space occupied by map before exit */
    map_free ();

    return NULL;
}


/*
 * Copies values from the state record into the corresponding dbox.
 */

static error * locate_fill_in_dbox (LocatePtr state)
{
    ObjectId dbox = state->dbox;
    Bool notfound = (state->type == 0);
    DiscInfoPtr di = state->di;
    error *err = NULL;

    ED ( displayfield_set_value (0, dbox, ID_FS,
             state->FS == FS_ADFS ? "ADFS" : "SCSIFS") );
    ED ( displayfield_set_value (0, dbox, ID_Drive, num(state->drive)) );
    ED ( writablefield_set_value (0, dbox, ID_Name, state->objname) );
    ED ( displayfield_set_value (0, dbox, ID_Type,
             notfound ? "" : state->type == 2 ? "Dir" : "File") );
    ED ( displayfield_set_value (0, dbox, ID_Size,
             notfound ? "" :
             num ((state->size + di->secsize - 1) >> di->dr.log2secsize)) );
    ED ( displayfield_set_value (0, dbox, ID_Fragid,
             notfound ? "" : num (state->sin.fragid)) );
    ED ( displayfield_set_value (0, dbox, ID_Shared,
             notfound ? "" :
             state->sin.sector == 0 ? "Unshared" : "Shared") );
    ED ( displayfield_set_value (0, dbox, ID_Offset,
             notfound ? "" :
             state->sin.sector == 0 ? "" :
             num (state->sin.sector - 1)) );
    ED ( displayfield_set_value (0, dbox, ID_Numfrags,
             notfound ? "" : num (state->numfrags)) );
    ED ( numberrange_set_bounds (3, /* set lower and upper bounds only */
                                 dbox, ID_Fragnum,
                                 1, 
                                 notfound ? 1 : state->numfrags,
                                 0, 0) );
    ED ( numberrange_set_value (0, dbox, ID_Fragnum, 1) );

    /* fade numberrange gadget iff no object is currently represented */
    ED ( gadget_fade (dbox, ID_Fragnum, notfound) );

    ED ( displayfield_set_value (0, dbox, ID_Fragaddr,
             notfound ? "" : num (state->frags[0].sector)) );
    ED ( displayfield_set_value (0, dbox, ID_Fragsize,
             notfound ? "" : num (state->frags[0].size)) );

    return err;
}


/*
 * Create and initialise a new state record for a Locate dbox.
 */

static error * locate_create_state_rec
(
    int FS,
    int drive,
    ObjectId dbox,
    LocatePtr *state      /* for the result */
)
{
    LocatePtr p;

    /* allocate space */
    ER ( check_alloc ((void **) state, sizeof (LocateRec)) );

    /* initialise */
    p = *state;
    p->FS = FS;
    p->drive = drive;
    p->dbox = dbox;
    strcpy (p->objname, "$");

    p->numfrags = 4;
    p->frags[0].sector = 1;  p->frags[0].size = 100;
    p->frags[1].sector = 2;  p->frags[1].size = 200;
    p->frags[2].sector = 3;  p->frags[2].size = 300;
    p->frags[3].sector = 4;  p->frags[3].size = 400;

    return NULL;
}


/*
 * Called when the user clicks on one of the "Fragment no." adjuster arrows
 *  in order to update that field: note that there are no other adjuster
 *  arrows in the dialogue box.
 */

static Bool locate_numberrange_update
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    ObjectId dbox = idblock->self_id;
    ComponentId gadgetid = idblock->self_component;
    LocatePtr state = (LocatePtr) handle;
    int n;

    IGNORE(code);
    IGNORE(event);

    /* can only be the Fragment number numberrange */
    if (gadgetid != ID_Fragnum)
        EDG (ret, error_lookup ("ShouldBeFragnum") );

    /* read the new fragment number */
    EDG (ret, numberrange_get_value (0, dbox, gadgetid, &n) );

    /* ensure that it is in range */
    if (n < 1 || n > state->numfrags)
    {
        error_box (error_lookup ("BadFragNum", 1, state->numfrags));
        if (n < 1)
            n = 1;
        if (n > state->numfrags)
            n = state->numfrags;
        EDG (ret, numberrange_set_value (0, dbox, gadgetid, n) );
    }

    /* update address and size display fields */
    EDG (ret, displayfield_set_value (0, dbox, ID_Fragaddr,
                                         num (state->frags[n-1].sector)) );
    EDG (ret, displayfield_set_value (0, dbox, ID_Fragsize,
                                         num (state->frags[n-1].size)) );

  ret:
    return TRUE;
}


/*
 * Called when the user clicks an action button in a Locate window; only the
 *  following are actioned:
 *
 *    "Previous"
 *    "Next"
 *    "Parent"
 *    "Children"
 *    "Locate"
 */

static Bool locate_actionbutton_hit
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    ObjectId dbox = idblock->self_id;
    ComponentId gadgetid = idblock->self_component;
    LocatePtr state = (LocatePtr) handle;

    IGNORE(code);
    IGNORE(event);

    /* copy writable into state record */
    EDG (ret, writablefield_get_value (0, dbox, ID_Name,
                                              state->objname, 256, NULL) );

    /* update the state record accordingly */
    EDG (ret, locate_update_state (state, gadgetid) );

    /* and then the display */
    EDG (ret, locate_fill_in_dbox (state) );

  ret:
    return TRUE;
}


/*
 * Called when a Locate window is hidden (ie after the user clicks "Cancel",
 *  or the "close" icon).
 */

static Bool locate_delete
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    ObjectId dbox = idblock->self_id;
    LocatePtr state = (LocatePtr) handle;

    IGNORE(code);
    IGNORE(event);

    /* release the acquired disc information record */
    EDG (ret, dinfo_freeinfo (state->di) );

    /* release space occupied by the status record */
    free (state);

    /* delete the Locate dbox */
    EDG (ret, toolbox_delete_object (0, dbox) );

    /* deregister all those handlers */
    EDG (ret, event_deregister_toolbox_handler
              (
                  dbox,
                  NumberRange_ValueChanged,
                  locate_numberrange_update,
                  (void *) state
              ) );

    EDG (ret, event_deregister_toolbox_handler
              (
                  dbox,
                  ActionButton_Selected,
                  locate_actionbutton_hit,
                  (void *) state
              ) );

    EDG (ret, event_deregister_toolbox_handler
              (
                  dbox,
                  Window_HasBeenHidden,
                  locate_delete,
                  (void *) state
              ) );

  ret:
    return TRUE;
}


/*
 * Called when the user chooses "Locate..." from the icon bar icon menu.
 */

Bool locate_request
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    ObjectId dbox;
    LocatePtr state;
    int FS = setup_FS ();
    int drive = setup_drive ();

    IGNORE(handle);
    IGNORE(event);
    IGNORE(idblock);
    IGNORE(code);

    /* create a new Locate window */
    EDG (ret, toolbox_create_object (0, LOCATE, &dbox) );

    /* and a status record for it */
    EDG (ret, locate_create_state_rec (FS, drive, dbox, &state) );

    /* and acquire the appropriate disc information record */
    EDG (ret, dinfo_getinfo (FS, drive, TRUE, &state->di) );

    /* and initialise the gadgets in the window */
    EDG (ret, locate_fill_in_dbox (state) );

    /* register handler for change to fragment number numberrange */
    EDG (ret, event_register_toolbox_handler
              (
                  dbox,
                  NumberRange_ValueChanged,
                  locate_numberrange_update,
                  (void *) state
              ) );

    /* register handler for action button click */
    EDG (ret, event_register_toolbox_handler
              (
                  dbox,
                  ActionButton_Selected,
                  locate_actionbutton_hit,
                  (void *) state
              ) );

    /* register handler for dbox hidden */
    EDG (ret, event_register_toolbox_handler
              (
                  dbox,
                  Window_HasBeenHidden,
                  locate_delete,
                  (void *) state
              ) );

    /* show the locate window */
    EDG (ret, toolbox_show_object (0, dbox, 0, NULL, 0, 0) );

    /* and update it */
    EDG (ret, locate_update_state (state, ID_Locate) );
    EDG (ret, locate_fill_in_dbox (state) );

  ret:
    return TRUE;
}
