/* scan.c for !DiscEx */


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


#define  GATHER    "Gather"     /* name of "gathering" window template */
#define  ANALYSIS  "Analyse"    /* name of "analysis" window template  */


static ScanListPtr scanlist = NULL;  /* list of active scans */
static ScanListPtr current = NULL;   /* address of current active scan */


/* component IDs for gathering window */
#define  ID_G_Dir           1
#define  ID_G_FS           31
#define  ID_G_Drive        30
#define  ID_Faster          4
#define  ID_PauseResume     3

/* event codes for gathering window */
#define  Event_PauseResume 10
#define  Event_Faster      11


/* event codes for analysis window */
#define  Event_Rescan      12

/*
 * A problem arises when using event handlers to specify actions to be taken
 *  when a window is deleted. Consider:
 *
 *    Create window object W1.
 *    Register handler H1 for WindowHasBeenHidden event on W1.
 *                     ....    (a)
 *    Deregister H1.
 *    Delete W1.
 *    Create window object W2.
 *    Register handler H2 for WindowHasBeenHidden event on W2.
 *                     ....    (b)
 *
 *  where calls to Wimp_Poll are made only in the '....' bits.
 *
 * A WindowHasBeenHidden event will be raised at (b) for window W1 - but
 *  handler H1 will not be called since it has been deregistered.
 *
 * Handler H2 will also not be called - unless W1 and W2 have the same
 *  ObjectId! And this is quite likely ...
 *
 * One solution - probably the simplest - is to specify a different event
 *  for W1 and W2, instead of using the default one; this way, H2 can never
 *  be called when W1 is deleted.
 */

#define  Event_GatherHide   13
#define  Event_AnalyseHide  14



/* forward references */
static Bool scan_delete (int c, ToolboxEvent *e, IdBlock *i, void *h);
static Bool scan_pauseresume (int c, ToolboxEvent *e, IdBlock *i, void *h);
static Bool scan_rescan (int c, ToolboxEvent *e, IdBlock *i, void *h);
static Bool scan_faster (int c, ToolboxEvent *e, IdBlock *i, void *h);
static Bool scan_continue (int c, WimpPollBlock *e, IdBlock *i, void *h);
static error * scan_create (int FS, int drive);


static int activegatherers = 0;  /* count of scans currently gathering */



/*
 * Called when a new "gathering" scan starts.
 *
 * Enables null events and registers a suitable handler if this is the
 *  first one.
 */

static error * enable_nulls (void)
{
    if (activegatherers == 0)
    {
        unsigned int mask;

        /* enable null events */
        event_get_mask (&mask);
        event_set_mask (mask & 0xfffffffe);

        /* register scan_continue(..) as the null event handler */
        ER ( event_register_wimp_handler
             (
                 -1,
                 0,                /* Null_Reason_Code */
                 scan_continue,
                 NULL
             ) );
    }

    activegatherers++;

    return NULL;
}


/*
 * Called when the "gathering" phase of a scan completes.
 *
 * Disables null events and deregisters the null handler is this is the
 *  last one.
 */

static error * disable_nulls (void)
{
    activegatherers--;

    if (activegatherers == 0)
    {
        unsigned int mask;

        /* disable null events */
        event_get_mask (&mask);
        event_set_mask (mask | 1);

        /* deregister the null event handler */
        ER ( event_deregister_wimp_handler
             (
                 -1,
                 0,                /* Null_Reason_Code */
                 scan_continue,
                 NULL
             ) );
    }

    return NULL;
}


/*
 * Update fields reflecting current state of scan in the "gather" dbox.
 */

static error * scan_update_gather_dbox (ScanListPtr p)
{
    char *s;

    /* obtain pointer to name of current directory */
    s = tree_scan_position (p);

    /* and copy it to the dialogue box */
    ER ( displayfield_set_value (0, p->dbox, ID_G_Dir, s) );

    return NULL;
}


/*
 * Frees those resources required only during the analysis phase.
 */

static error * scan_end_analysis_phase (ScanListPtr p)
{
    /* free space occupied by ClientHandleRec */
    {
        void *h;

        ER ( toolbox_get_client_handle (0, p->dbox, &h));
        free (h);
    }

    /* deregister the analysis-phase handlers */
    ER ( event_deregister_toolbox_handler
              (
                  p->dbox,
                  Event_AnalyseHide,
                  scan_delete,
                  (void *) p
              ));
    ER ( event_deregister_toolbox_handler
              (
                  p->dbox,
                  Event_Rescan,
                  scan_rescan,
                  (void *) p
              ));

    /* delete the "analysis" window */
    ER ( toolbox_delete_object (0, p->dbox));

    return NULL;
}


/*
 * Frees those resources required only during the gathering phase.
 */

static error * scan_end_gathering_phase (ScanListPtr p)
{
    /* deregister the handlers */
    ER ( event_deregister_toolbox_handler
              (
                  p->dbox,
                  Event_GatherHide,
                  scan_delete,
                  (void *) p
              ) );
    ER ( event_deregister_toolbox_handler
              (
                  p->dbox,
                  Event_PauseResume,
                  scan_pauseresume,
                  (void *) p
              ) );
    ER ( event_deregister_toolbox_handler
              (
                  p->dbox,
                  Event_Faster,
                  scan_faster,
                  (void *) p
              ) );

    /* delete the "gathering" window */
    ER ( toolbox_delete_object (0, p->dbox) );
    p->dbox = 0;

    /* disable NULL events if appropriate */
    ER ( disable_nulls () );

    return NULL;
}


/*
 * Called when the user clicks on the "Rescan" button in an "analysis"
 *  window.
 */

static Bool scan_rescan
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    ScanListPtr p = (ScanListPtr) handle;
    int FS = p->FS;
    int drive = p->drive;

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

    /* close down this scan */
    scan_delete (0, 0, 0, (void *) p);

    /* and start a new one */
    scan_create (FS, drive);

    return TRUE;
}


/*
 * Called when the user clicks on "Cancel" or the close icon of a "gathering"
 *  or "analysis" window.
 */

static Bool scan_delete
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    ScanListPtr p = (ScanListPtr) handle;

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

    /* required action depends on whether "gathering" phase is over or not */
    if (p->completed)
    {
        EDG (ret, scan_end_analysis_phase (p));
    }
    else
    {
        EDG (ret, scan_end_gathering_phase (p));
    }

    /* release resources used by the tree scan */
    EDG (ret, tree_delete_scan (p));

    /* release the allocator */
    free_big_blocks (p->alloc);

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

    /* remove scan record from list of active scans */
    {
        ScanListPtr prev = scanlist;

        if (p == scanlist)
            scanlist = p->next;
        else
        {
            while (prev->next != p)
                prev = prev->next;
            prev->next = p->next;
        }
    }

    /* free the scan record and associated structures */
    free (p->frag);
    free (p->stats);
    free (p);

  ret:
    return TRUE;
}


/*
 * Called when a NULL event arrives: chooses the next active scan (on a
 *  round-robin basis) and gives it a timeslice.
 */

static Bool scan_continue
(
    int code,
    WimpPollBlock *event,
    IdBlock *idblock,
    void *handle
)
{
    ScanListPtr p = scanlist;
    ScanListPtr q;

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

    /* choose which scan to reactivate */

    /* find the previous one, if it's still there */
    while (p != NULL && p != current)
        p = p->next;

    /* and move to the one after that */
    if (p)
        p = p->next;
    if (!p)
        p = scanlist;

    /* and now choose the first one that is neither complete nor paused */
    q = p;
    while (p->completed || p->paused)
    {
        p = p->next;
        if (!p)
            p = scanlist;

        /* if we're back to the beginning again, there's nothing to do */
        if (p == q)
            return TRUE;
    }

    /* reset current for next time */
    current = p;

    _swix (Hourglass_On, 0);

    EDG (ret, tree_continue_scan (p, &p->completed) );
    if (p->completed)
    {
        /* tidy up after "gathering" phase */
        EDG (ret, scan_end_gathering_phase (p) );

        /* complete analysis */
        EDG (ret, analyse_analyse (p) );

        /* note new state */
        p->completed = TRUE;

        /* create the "analysis" window, fill it in and display it */
        EDG (ret, toolbox_create_object (0, ANALYSIS, &p->dbox));
        EDG (ret, analyse_update_dbox (p));
        EDG (ret, toolbox_show_object (0, p->dbox, 0, 0, 0, 0));

        /* create client handle, initialise and attach it (for SaveAs) */
        {
            ClientHandlePtr h;

            EDG (ret, check_alloc ((void **) &h, sizeof (ClientHandleRec)) );

            h->p = (void *) p;
            h->pf = analyse_print;
            h->filename = analyse_saveas_filename;

            EDG (ret, toolbox_set_client_handle (0, p->dbox, (void *) h) );
        }

        /* register handlers */

        /* window has been hidden ("Cancel" or close icon click) */
        EDG (ret, event_register_toolbox_handler
                  (
                      p->dbox,
                      Event_AnalyseHide,
                      scan_delete,
                      (void *) p
                  ));

        /* Click on "Rescan" button */
        EDG (ret, event_register_toolbox_handler
                  (
                      p->dbox,
                      Event_Rescan,
                      scan_rescan,
                      (void *) p
                  ));
    }
    else
    {
        if (!p->faster)
        {
            EDG (ret, scan_update_gather_dbox (p) );
        }
    }

    _swix (Hourglass_Off, 0);

  ret:
    return TRUE;
}


/*
 * Called when the user clicks on the "Pause/Resume" button in a "gathering"
 *  window.
 */

static Bool scan_pauseresume
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    ScanListPtr p = (ScanListPtr) handle;

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

    p->paused = !p->paused;
    EDG (ret, actionbutton_set_text (0, p->dbox, ID_PauseResume,
                                        p->paused ? "Resume" : "Pause") );

  ret:
    return TRUE;
}


/*
 * Called when the user clicks on the "Faster" option icon in a "gathering"
 *  window.
 */

static Bool scan_faster
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    ScanListPtr p = (ScanListPtr) handle;

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

    p->faster = !p->faster;

    /* update progress display if no longer "faster", else blank name */
    if (p->faster)
    {
        EDG (ret, displayfield_set_value (0, p->dbox, ID_G_Dir, "") );
    }
    else
    {
        EDG (ret, scan_update_gather_dbox (p) );
    }

  ret:
    return TRUE;
}


/*
 * Called from scan_request(..) and from scan_rescan(..) to start a fresh
 *  scan.
 */

static error * scan_create (int FS, int drive)
{
    ScanListPtr p;

    /* see if a scan for this drive is already active */
    p = scanlist;
    while (p != NULL)
    {
        if (p->FS == FS && p->drive == drive)
        {
            /* found it - so just bring the window to the front */
            ER ( toolbox_show_object (0, p->dbox, 0, 0, 0, 0));

            return NULL;
        }

        p = p->next;
    }

    /* allocate a new ScanListRec and add it to the list */
    ER ( check_alloc ((void **) &p, sizeof (ScanListRec)) );
    p->next = scanlist;
    scanlist = p;

    /* initialise basic fields */
    p->FS = FS;
    p->drive = drive;
    p->completed = FALSE;
    p->paused = FALSE;
    p->faster = FALSE;
    p->frag = NULL;
    p->stats = NULL;

    /* create allocator for this scan */
    ER ( check_alloc ((void **) &p->alloc, sizeof (BigBlockDefRec)) );

    ER ( alloc_big_block (p->alloc) );

    /* acquire disc information record for this scan */
    ER ( dinfo_getinfo (FS, drive, TRUE, &p->di) );

    /* initialise and start the tree hierarchy scan */
    ER ( tree_init_scan (p) );

    /* create the "gathering" window, fill it in and display it */
    ER ( toolbox_create_object (0, GATHER, &p->dbox));
    ER ( displayfield_set_value (0, p->dbox, ID_G_FS,
                                        FS == FS_ADFS ? "ADFS" : "SCISFS") );
    ER ( displayfield_set_value (0, p->dbox, ID_G_Drive, num (drive)) );
    ER ( scan_update_gather_dbox (p) );
    ER ( toolbox_show_object (0, p->dbox, 0, 0, 0, 0));

    /* register handlers */

    /* window has been hidden ("Cancel" or close icon click) */
    ER ( event_register_toolbox_handler
              (
                  p->dbox,
                  Event_GatherHide,
                  scan_delete,
                  (void *) p
              ));

    /* Click on "Pause/Resume" button */
    ER ( event_register_toolbox_handler
              (
                  p->dbox,
                  Event_PauseResume,
                  scan_pauseresume,
                  (void *) p
              ));

    /* Click on "Faster" option icon */
    ER ( event_register_toolbox_handler
              (
                  p->dbox,
                  Event_Faster,
                  scan_faster,
                  (void *) p
              ));

    /* finally, enable null events (if they are not already enabled) */
    ER ( enable_nulls () );

    return NULL;
}


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

Bool scan_request
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    int FS = setup_FS ();
    int drive = setup_drive ();

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

    EDG (ret, scan_create (FS, drive));

  ret:
    return TRUE;
}
