#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>
#include <libraries/gadtools.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <dos/dosextens.h>
#include <dos/exall.h>
#include <dos/datetime.h>
#include <graphics/gfx.h>

#include <clib/alib_protos.h>
#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>
#include <clib/dos_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/exec_protos.h>

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "rp.h"
#include "files.h"

#include <reaction/reaction.h>
#include <gadgets/listbrowser.h>
#include <pragmas/listbrowser_pragmas.h>
#include <gadgets/chooser.h>
#include <pragmas/chooser_pragmas.h>
#include <gadgets/string.h>
#include <pragmas/string_pragmas.h>
#include <gadgets/checkbox.h>
#include <pragmas/checkbox_pragmas.h>
#include <gadgets/layout.h>
#include <pragmas/layout_pragmas.h>
#include <gadgets/button.h>
#include <pragmas/button_pragmas.h>
#include <images/label.h>
#include <pragmas/label_pragmas.h>
#include <proto/label.h>
#include <classes/window.h>
#include <pragmas/window_pragmas.h>

MODULE void updatefiles(void);
MODULE void stage1(void);
MODULE void files_work(void);                                 // for stage1()
MODULE void stage2(void);
MODULE ABOOL __inline quickcomp(STRPTR first, STRPTR second); // for stage2()
MODULE void dobuffer(ABOOL item, ULONG whichpen);             // for stage2()
MODULE void stage3(void);
MODULE void stopping(STRPTR status);
MODULE void files_updatelog(void);

IMPORT ABOOL                fillwindows;
IMPORT TEXT                 IOBuffer[LONGESTFIELD + 1],
                            aslresult[MEDFIELD + 1];
IMPORT SBYTE                page;
IMPORT ULONG                signal;
IMPORT struct SharedStruct  shared;
IMPORT struct Screen*       ScreenPtr;
IMPORT struct Library     *ButtonBase,
                          *CheckBoxBase,
                          *ChooserBase,
                          *IconBase,
                          *LabelBase,
                          *LayoutBase,
                          *ListBrowserBase,
                          *StringBase,
                          *WindowBase;
IMPORT struct ExAllData*    EADataPtr;
IMPORT struct VisualInfo*   VisualInfoPtr;
IMPORT struct NewGadget     Gadget;
IMPORT struct List          EmptyList;
IMPORT struct Menu*         MenuPtr;
IMPORT Object*              WinObject[FUNCTIONS + 1];

AGLOBAL struct Gadget*      files_gadgets[GIDS_10 + 1];

MODULE struct List          DirList, FileList;
MODULE struct List          ResultList;
MODULE ABOOL                ResultNodes = FALSE;
MODULE ULONG                status = STATUS_READY;
MODULE struct
{   ULONG show[8]; // anything we want to use GetAttr() with must be ULONG
    ULONG entries;
    ULONG osversion;
    TEXT  path[VLONGFIELD + 1];
} files =
{   TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE,
    0, 5, ""
};
MODULE  BPTR           LogFileHandle       = NULL;
MODULE  TEXT           stringholder1[VLONGFIELD + 1],
                       stringholder2[VLONGFIELD + 1],
                       lockstring[VLONGFIELD + 1];

#define OSOPTIONS 5 // counting from 0
MODULE  STRPTR OSOptions[OSOPTIONS + 1] =
{   "OS3.1",
    "OS3.5",
    "OS3.5 & Boing Bag(s)",
    "OS3.9",
    "OS3.9 & Genesis Update",
    "OS3.9 & Boing Bag 1",
};
MODULE struct
{   ULONG red, green, blue, pennumber;
} penn[8] =
{     {0xFFFFFFFF, 0x22222222, 0x22222222, -1}, // red
      {0xFFFFFFFF, 0x88888888, 0x00000000, -1}, // orange
      {0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, -1}, // yellow
      {0x55555555, 0xFFFFFFFF, 0x55555555, -1}, // green
      {0x55555555, 0x55555555, 0xFFFFFFFF, -1}, // blue (obsolete)
      {0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, -1}, // cyan (3rd-party)
      {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, -1}, // white (missing normal)
      {0xFFFFFFFF, 0x99999999, 0x99999999, -1}  // pink (missing optional)
};

// from rp.c
IMPORT SBYTE               page;
IMPORT struct Window*      MainWindowPtr;
IMPORT TEXT                weekdaystring[LEN_DATSTRING],
                           datestring[LEN_DATSTRING],
                           timestring[LEN_DATSTRING];

AGLOBAL void files1(void)
{   ULONG               i;
    struct List         ChooserList;
    struct ChooserNode* ChooserNodePtr[OSOPTIONS + 1];
    struct Hook         Hook10Struct;
    BOOL                inverse;

    if (shared.log)
    {   inverse = FALSE;
    } else
    {   inverse = TRUE;
    }
    if (!shared.pathname[0])
    {   strcpy(shared.pathname, "RAM:Report.txt");
    }

    NewList(&ChooserList);
    for (i = 0; i <= OSOPTIONS; i++)
    {   if (!(ChooserNodePtr[i] = (struct ChooserNode *) AllocChooserNode(CNA_Text, OSOptions[i], TAG_DONE)))
        {   rq("Can't allocate chooser node!");
        }
        AddTail(&ChooserList, (struct Node *) ChooserNodePtr[i]);
    } // automatically freed by ReAction at DisposeObject() time

    /* Create the window object. */
    lockscreen();
    gadtools();
    InitHook(&Hook10Struct, Hook10Func, NULL);

    if (!(WinObject[10] =         NewObject(WINDOW_GetClass(), NULL,
    // window
    WA_PubScreen,                 ScreenPtr,
    WA_ScreenTitle,               "Report+",
    WA_Title,                     "Report+: System Files Report",
    WA_Activate,                  TRUE,
    WA_DepthGadget,               TRUE,
    WA_DragBar,                   TRUE,
    WA_CloseGadget,               TRUE,
    WA_SizeGadget,                TRUE,
    WA_IDCMP,                     IDCMP_RAWKEY,
    WINDOW_IDCMPHook,             &Hook10Struct,
    WINDOW_IDCMPHookBits,         IDCMP_RAWKEY,
    WINDOW_MenuStrip,             MenuPtr,
    WINDOW_Position,              WPOS_CENTERSCREEN,
    WINDOW_ParentGroup,           files_gadgets[GID_10_LY1] =
                                  NewObject(LAYOUT_GetClass(), NULL,
        // root-layout
        LAYOUT_Orientation,       LAYOUT_ORIENT_VERT,
        LAYOUT_SpaceOuter,        TRUE,
        LAYOUT_DeferLayout,       TRUE,
        LAYOUT_AddChild,          NewObject(LAYOUT_GetClass(), NULL,
            // layout
            LAYOUT_Orientation,   LAYOUT_ORIENT_HORIZ,
            LAYOUT_SpaceOuter,    TRUE,
            LAYOUT_VertAlignment, LALIGN_CENTER,
            LAYOUT_HorizAlignment,LALIGN_CENTER,
            LAYOUT_BevelStyle,    BVS_FIELD,
            LAYOUT_AddChild,      NewObject(LAYOUT_GetClass(), NULL,
                // layout
                LAYOUT_Orientation,   LAYOUT_ORIENT_VERT,
                LAYOUT_SpaceOuter,    TRUE,
                LAYOUT_VertAlignment, LALIGN_CENTER,
                LAYOUT_HorizAlignment,LALIGN_LEFT,
                LAYOUT_BevelStyle,    BVS_FIELD,
                LAYOUT_AddImage,
                NewObject
                (   LABEL_GetClass(),     NULL,
                    // label
                    LABEL_Text,           "Show:",
                    LABEL_Justification,  LJ_LEFT,
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                LAYOUT_AddChild,          files_gadgets[GID_10_CB1] =
                NewObject
                (   CHECKBOX_GetClass(),  NULL,
                    // checkbox
                    GA_ID,                GID_10_CB1,
                    GA_RelVerify,         TRUE,
                    CHECKBOX_BackgroundPen, penn[0].pennumber,
                    GA_Text,              "V_ery important",
                    GA_Selected,          (BOOL) files.show[0],
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                LAYOUT_AddChild,          files_gadgets[GID_10_CB2] =
                NewObject(CHECKBOX_GetClass(), NULL,
                    // checkbox
                    GA_ID,                GID_10_CB2,
                    GA_RelVerify,         TRUE,
                    CHECKBOX_BackgroundPen, penn[1].pennumber,
                    GA_Text,              "_Important",
                    GA_Selected,          (BOOL) files.show[1],
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                LAYOUT_AddChild,          files_gadgets[GID_10_CB3] =
                NewObject
                (   CHECKBOX_GetClass(), NULL,
                    // checkbox
                    GA_ID,                GID_10_CB3,
                    GA_RelVerify,         TRUE,
                    CHECKBOX_BackgroundPen, penn[2].pennumber,
                    GA_Text,              "_Semi-important",
                    GA_Selected,          (BOOL) files.show[2],
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                LAYOUT_AddChild,          files_gadgets[GID_10_CB4] =
                NewObject
                (   CHECKBOX_GetClass(), NULL,
                    // checkbox
                    GA_ID,                GID_10_CB4,
                    GA_RelVerify,         TRUE,
                    CHECKBOX_BackgroundPen, penn[3].pennumber,
                    GA_Text,              "U_nimportant",
                    GA_Selected,          (BOOL) files.show[3],
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                LAYOUT_AddChild,          files_gadgets[GID_10_CB5] =
                NewObject
                (   CHECKBOX_GetClass(), NULL,
                    // checkbox
                    GA_ID,                GID_10_CB5,
                    GA_RelVerify,         TRUE,
                    CHECKBOX_BackgroundPen, penn[4].pennumber,
                    GA_Text,              "_Obsolete",
                    GA_Selected,          (BOOL) files.show[4],
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                LAYOUT_AddChild,          files_gadgets[GID_10_CB6] =
                NewObject
                (   CHECKBOX_GetClass(), NULL,
                    // checkbox
                    GA_ID,                GID_10_CB6,
                    GA_RelVerify,         TRUE,
                    CHECKBOX_BackgroundPen, penn[5].pennumber,
                    GA_Text,              "_3rd-party",
                    GA_Selected,          (BOOL) files.show[5],
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                LAYOUT_AddChild,          files_gadgets[GID_10_CB7] =
                NewObject
                (   CHECKBOX_GetClass(), NULL,
                    // checkbox
                    GA_ID,                GID_10_CB7,
                    GA_RelVerify,         TRUE,
                    CHECKBOX_BackgroundPen, penn[6].pennumber,
                    GA_Text,              "_Missing normal",
                    GA_Selected,          (BOOL) files.show[6],
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                LAYOUT_AddChild,          files_gadgets[GID_10_CB8] =
                NewObject
                (   CHECKBOX_GetClass(), NULL,
                    // checkbox
                    GA_ID,                GID_10_CB8,
                    GA_RelVerify,         TRUE,
                    CHECKBOX_BackgroundPen, penn[7].pennumber,
                    GA_Text,              "Missin_g optional",
                    GA_Selected,          (BOOL) files.show[7],
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                TAG_END
            ),
            CHILD_WeightedWidth,      0,
            LAYOUT_AddChild,
            NewObject
            (   LAYOUT_GetClass(),    NULL,
                // layout
                LAYOUT_Orientation,   LAYOUT_ORIENT_VERT,
                LAYOUT_SpaceOuter,    TRUE,
                LAYOUT_VertAlignment, LALIGN_TOP,
                LAYOUT_HorizAlignment,LALIGN_CENTER,
                LAYOUT_BevelStyle,    BVS_NONE,
                LAYOUT_ShrinkWrap,    TRUE,
                LAYOUT_AddChild,
                NewObject
                (   LAYOUT_GetClass(),    NULL,
                    // layout
                    LAYOUT_Orientation,   LAYOUT_ORIENT_HORIZ,
                    LAYOUT_SpaceOuter,    TRUE,
                    LAYOUT_VertAlignment, LALIGN_CENTER,
                    LAYOUT_HorizAlignment,LALIGN_CENTER,
                    LAYOUT_BevelStyle,    BVS_FIELD,
                    LAYOUT_AddImage,      NewObject(LABEL_GetClass(), NULL,
                        // label
                        LABEL_Text,       "AmigaOS _Version:",
                        LABEL_Justification,LJ_LEFT,
                        TAG_END
                    ),
                    LAYOUT_AddChild,          files_gadgets[GID_10_CH1] =
                    NewObject
                    (   CHOOSER_GetClass(),   NULL,
                        // chooser
                        GA_ID,                GID_10_CH1,
                        GA_RelVerify,         TRUE,
                        CHOOSER_PopUp,        TRUE,
                        CHOOSER_Labels,       &ChooserList,
                        CHOOSER_Selected,     (WORD) files.osversion,
                        TAG_END
                    ),
                    CHILD_WeightedHeight,     0,
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                LAYOUT_AddChild,          NewObject(LAYOUT_GetClass(), NULL,
                    // layout
                    LAYOUT_Orientation,   LAYOUT_ORIENT_HORIZ,
                    LAYOUT_SpaceOuter,    TRUE,
                    LAYOUT_VertAlignment, LALIGN_CENTER,
                    LAYOUT_HorizAlignment,LALIGN_CENTER,
                    LAYOUT_BevelStyle,    BVS_FIELD,
                    LAYOUT_ShrinkWrap,    TRUE,
                    LAYOUT_AddChild,      files_gadgets[GID_10_CB9] =
                    NewObject
                    (   CHECKBOX_GetClass(), NULL,
                        // checkbox
                        GA_ID,                GID_10_CB9,
                        GA_RelVerify,         TRUE,
                        GA_Text,              "_Log to:",
                        GA_Selected,          (BOOL) shared.log,
                        TAG_END
                    ),
                    CHILD_WeightedWidth,  0,
                    LAYOUT_AddChild,      files_gadgets[GID_10_ST1] =
                                          NewObject(STRING_GetClass(), NULL,
                        // string        
                        GA_ID,            GID_10_ST1,
                        STRINGA_TextVal,  shared.pathname,
                        STRINGA_MinVisible,20,
                        GA_Disabled,      inverse,
                        TAG_END
                    ),
                    LAYOUT_AddChild,      files_gadgets[GID_10_BU1] =
                    NewObject
                    (   NULL, "button.gadget",
                        // button
                        GA_ID,            GID_10_BU1,
                        GA_RelVerify,     TRUE,
                        BUTTON_AutoButton,BAG_POPFILE,
                        GA_Disabled,      inverse,
                        TAG_END
                    ),
                    CHILD_WeightedWidth,  0,
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                LAYOUT_AddChild,          files_gadgets[GID_10_ST2] =
                NewObject
                (   STRING_GetClass(),    NULL,
                    GA_ReadOnly,          TRUE,
                    GA_ID,                GID_10_ST2,
                    STRINGA_TextVal,      "Ready.",
                    TAG_END
                ),
                LAYOUT_AddChild,
                NewObject
                (   LAYOUT_GetClass(),    NULL,
                    // layout
                    LAYOUT_Orientation,   LAYOUT_ORIENT_HORIZ,
                    LAYOUT_SpaceOuter,    TRUE,
                    LAYOUT_VertAlignment, LALIGN_CENTER,
                    LAYOUT_HorizAlignment,LALIGN_CENTER,
                    LAYOUT_BevelStyle,    BVS_FIELD,
                    LAYOUT_AddChild,      files_gadgets[GID_10_BU2] =
                    NewObject
                    (   NULL,             "button.gadget",
                        // button
                        GA_ID,            GID_10_BU2,
                        GA_RelVerify,     TRUE,
                        GA_Text,          "_Update",
                        TAG_END
                    ),
                    CHILD_WeightedWidth,  50,
                    LAYOUT_AddChild,      files_gadgets[GID_10_BU3] =
                    NewObject
                    (   NULL,             "button.gadget",
                        // button
                        GA_ID,            GID_10_BU3,
                        GA_RelVerify,     TRUE,
                        GA_Text,          "Stop",
                        GA_Disabled,      TRUE,
                        TAG_END
                    ),
                    CHILD_WeightedWidth,  50,
                    TAG_END
                ),
                CHILD_WeightedHeight,     0,
                TAG_END
            ),
            CHILD_WeightedHeight,         0,
            TAG_END
        ),
        CHILD_WeightedHeight,         0,
        LAYOUT_AddChild,              files_gadgets[GID_10_LB1] =
        NewObject
        (   LISTBROWSER_GetClass(),   NULL,
            GA_ID,                    GID_10_LB1,
            GA_ReadOnly,              TRUE,
            GA_TextAttr,              Gadget.ng_TextAttr,
            LISTBROWSER_ScrollRaster, TRUE,
            LISTBROWSER_Labels,       (ULONG) &EmptyList,
            LISTBROWSER_MinVisible,   12,
            TAG_END
        ),
        CHILD_WeightedHeight,         100,
        LAYOUT_AddChild,              files_gadgets[GID_10_BU4] =
        NewObject
        (   NULL,                     "button.gadget",
            // button
            GA_ID,                    GID_10_BU4,
            GA_RelVerify,             TRUE,
            GA_Text,                  "Menu",
            TAG_END),
        CHILD_WeightedHeight,         0,
        TAG_END
    ),
    TAG_END
    )))
    {   rq("Can't create ReAction object(s)!");
    }
    unlockscreen();
    openwindow();
    loop();
    if (ResultNodes)
    {   clearreactionlist(&ResultList);
        ResultNodes = FALSE;
    }
    closewindow();
}

MODULE void updatefiles(void)
{   ULONG  i;
    STRPTR stringptr;

    /* Most gadgets are ghosted during the operation. Then their ghosting
    status returns to normal (not necessarily unghosted).

    Disable the `Menu' (BU), `Update' (BU). `Log to file?' (CB),
    `Pathname' (ST), `...' (BU), `Show' (CB), `OS version' (MX), `Stop'
    (BU) gadgets. Status (TE) is changed.

        0: Gadget handling.
        1: Set up lists, do the directory examination.
           (For each `source' directory in the `queue', before doing it
           we check for a break. `Break opportunity 1'.)
    At this point we have an empty DirList, and a full FileList, and an
    empty ResultList.
        2: Create the ResultList.
           (For each `source' file in the `queue', before doing it we
           check for a break. `Break opportunity 2'.)
        3: Show results.

       DirList: an Exec list of directories found, awaiting examination.
      FileList: an Exec list of files found, awaiting processing.
    ResultList: a listbrowser list of files found, for display.

    Apparently the way strings work is that you get told an address where
    the string is stored. This is apparently a pointer into system memory;
    it doesn't need explicit allocation/deallocation by our appliprog. */

    if (!(GetAttr
    (   STRINGA_TextVal, files_gadgets[GID_10_ST1], (ULONG *) &stringptr
    )))
    {   rq("Unsupported inquiry!"); // should never happen
    }
    strcpy(shared.output, stringptr);

    SetGadgetAttrs // `Menu'
    (   files_gadgets[GID_10_BU4], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
    SetGadgetAttrs // `Update'
    (   files_gadgets[GID_10_BU2], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
    SetGadgetAttrs // `Log to file?' (checkbox)
    (   files_gadgets[GID_10_CB9], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
    /* For some reason, we need to explicitly refresh the checkbox gadget
    visuals here */
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB9], MainWindowPtr, NULL);
    SetGadgetAttrs // `Log to file:' (string)
    (   files_gadgets[GID_10_ST1], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
    SetGadgetAttrs // `Log to file:' (ASL button)
    (   files_gadgets[GID_10_BU1], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
    SetGadgetAttrs // `AmigaOS Version:'
    (   files_gadgets[GID_10_CH1], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
    for (i = 0; i <= 7; i++)
    {   SetGadgetAttrs // `Show:'
        (   files_gadgets[GID_10_CB1 + i], MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_END
        );
        /* For some reason, we need to explicitly refresh the checkbox gadget
        visuals here */
        RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB1 + i], MainWindowPtr, NULL);
    }
    SetGadgetAttrs // `Stop'
    (   files_gadgets[GID_10_BU3], MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_END
    );
    SetGadgetAttrs // `Status'
    (   files_gadgets[GID_10_ST2], MainWindowPtr, NULL,
        STRINGA_TextVal, "Busy...",
        TAG_END
    );
    SetGadgetAttrs
    (   files_gadgets[GID_10_LB1], MainWindowPtr, NULL,
        LISTBROWSER_Labels, NULL,
        TAG_END
    );
    SetGadgetAttrs
    (   files_gadgets[GID_10_LB1], MainWindowPtr, NULL,
        LISTBROWSER_Labels, (ULONG) &EmptyList,
        TAG_END
    );
    if (ResultNodes)
    {   clearreactionlist(&ResultList);
        ResultNodes = FALSE;
    }

    status = STATUS_BUSY;
    stage1();
    if (status == STATUS_BUSY)
    {   stage2();
    }
    FreeNameNodes(&FileList);
    if (status == STATUS_BUSY)
    {   stage3();
    }
    status = STATUS_READY;

    SetGadgetAttrs // `Menu'
    (   files_gadgets[GID_10_BU4], MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_END
    );
    SetGadgetAttrs // `Update'
    (   files_gadgets[GID_10_BU2], MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_END
    );
    SetGadgetAttrs // `Log to?' (checkbox)
    (   files_gadgets[GID_10_CB9], MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_END
    );
    /* For some reason, we need to explicitly refresh the checkbox gadget
    visuals here */
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB9], MainWindowPtr, NULL);
    files_updatelog();
    SetGadgetAttrs // `AmigaOS Version'
    (   files_gadgets[GID_10_CH1], MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_END
    );
    for (i = 0; i <= 7; i++)
    {   SetGadgetAttrs // `Show'
        (   files_gadgets[GID_10_CB1 + i], MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_END
        );
        /* For some reason, we need to explicitly refresh the checkbox gadget
        visuals here */
        RefreshGadgets((struct Gadget *) files_gadgets[GID_10_CB1 + i], MainWindowPtr, NULL);
    }
    SetGadgetAttrs // `Stop'
    (   files_gadgets[GID_10_BU3], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
    SetGadgetAttrs // `Status'
    (   files_gadgets[GID_10_ST2], MainWindowPtr, NULL,
        STRINGA_TextVal, "Ready.",
        TAG_END
    );
}

MODULE void stage1(void)
{   struct NameNode* NodePtr;

    NewList(&FileList);
    NewList(&DirList);
    NewList(&ResultList);
    AddNameToTail(&DirList, "");
    // pop all the directories from the work stack, and send them one
    // at a time to files_work() for processing.
    while ((DirList.lh_Head)->ln_Succ) // while the list is non-empty
    {   if (ra_checkbreak() == 1) // we don't yet support completely quitting
        {   status = STATUS_STOPPING;
            stopping("Stopping...");
            break;
        }

        /* remove the dir-node from the list, copy its path to
           stringholder1, call files_work, then free the dir-node. */

        if (!(NodePtr = (struct NameNode *) RemTail(&DirList)))
        {   rq("RemTail() failed (list is empty!)"); // this should never happen
        }
        strcpy(stringholder1, NodePtr->nn_Data);
        files_work();
        FreeMem(NodePtr, sizeof(struct NameNode));
    }
    FreeNameNodes(&DirList); // Not required unless stopping early (although harmless).
}

MODULE void stage2(void)
{   ABOOL            matched, infomatched;
    ULONG            i, length;
    UBYTE            theversion;
    struct NameNode* NodePtr;

    // Everything is already allocated. Don't deallocate in this routine.

    if (shared.log)
    {   if (!(LogFileHandle = (BPTR) Open(shared.output, MODE_READWRITE)))
            rq("Can't open file for appending!");
        Seek(LogFileHandle, 0, OFFSET_END);
    }

    // Now we have the list of files
    getdate();
    strcpy(IOBuffer, "AmigaOS 3.");
    if (files.osversion == 0)
    {   theversion = 31;
        strcat(IOBuffer, "1");
    } elif (files.osversion == 1)
    {   theversion = 35;
        strcat(IOBuffer, "5");
    } elif (files.osversion == 2)
    {   theversion = 36;
        strcat(IOBuffer, "5 + BB1/2");
    } elif (files.osversion == 3)
    {   theversion = 39;
        strcat(IOBuffer, "9");
    } elif (files.osversion == 5)
    {   theversion = 41;
        strcat(IOBuffer, "9 + BB1");
    } else
    {   // assert(files.osversion == 4);
        theversion = 40;
        strcat(IOBuffer, "9 + Genesis Update");
    }
    strcat(IOBuffer, " system files at ");
    strcat(IOBuffer, timestring);
    strcat(IOBuffer, " on ");
    strcat(IOBuffer, weekdaystring);
    strcat(IOBuffer, " ");
    strcat(IOBuffer, datestring);
    strcat(IOBuffer, ":\n"); // another \n is inserted automatically by dobuffer()
    dobuffer(FALSE, 0);

    for (i = 0; i <= FILES; i++)
        os[i].matched = os[i].infomatched = FALSE;

    /* status is always STATUS_BUSY at this point, because this function is
    not called otherwise. */

    while ((FileList.lh_Head)->ln_Succ) // while the list is non-empty
    {   if (ra_checkbreak() == 1)
        {   status = STATUS_STOPPING;
            stopping("Stopping...");
            strcpy(IOBuffer, "Aborted by user!");
            dobuffer(FALSE, 0);
            break;
        }
        // assert(status != STATUS_STOPPING);

        if (!(NodePtr = (struct NameNode *) RemTail(&FileList)))
        {   rq("RemTail() failed (list is empty!)"); // should never happen
        }
        strcpy(stringholder1, NodePtr->nn_Data);
        length = strlen(stringholder1);
        FreeMem(NodePtr, sizeof(struct NameNode));

        // now we have removed the file from the `queue', and copied its
        // name into stringholder1. Now we check it against each system
        // file.

        matched = infomatched = FALSE;
        for (i = 0; i <= FILES; i++)
        {   if (os[i].version <= theversion)
            {   if (!matched && quickcomp(stringholder1, os[i].pathname))
                {   // we have a match

                    strcpy(IOBuffer, os[i].pathname);
                    matched = os[i].matched = TRUE;

                    // check whether file is obsoleted by OS3.9 or by Genesis Update
                    if
                    (   (theversion >= 39 && (os[i].flags & NOT39))
                     || (theversion >= 40 && (os[i].flags & NOT39UPDATE))
                     || (os[i].flags & CODE_BLUE)
                    )
                    {   dobuffer(TRUE, 12);
                    } elif (os[i].flags & CODE_RED)
                    {   dobuffer(TRUE,  8);
                    } elif (os[i].flags & CODE_ORANGE)
                    {   dobuffer(TRUE,  9);
                    } elif (os[i].flags & CODE_YELLOW)
                    {   dobuffer(TRUE, 10);
                    } else
                    {   // assert(os[i].flags & CODE_GREEN);
                        dobuffer(TRUE, 11);
                }   }
                elif
                (   !infomatched
                 && (os[i].flags & INFO)
                 && (stringholder1[length - 5] == '.')
                ) // we probably have a match, let's confirm
                {   strcpy(IOBuffer, os[i].pathname);
                    strcat(IOBuffer, ".info");
                    if (quickcomp(stringholder1, IOBuffer))
                    {   // we have a match
                        infomatched = os[i].infomatched = TRUE;

                        // check whether file is obsoleted by OS3.9 or by Genesis Update
                        if
                        (   (theversion >= 39 && (os[i].flags & NOT39))
                         || (theversion >= 40 && (os[i].flags & NOT39UPDATE))
                        )
                        {   dobuffer(TRUE, 12); // obsolete
                        } else
                        {   dobuffer(TRUE, 11); // automatic .info files are always green
        }   }   }   }   }
        if (!matched && !infomatched) // third-party (cyan)
        {   strcpy(IOBuffer, stringholder1);
            dobuffer(TRUE, 13);
    }   }

    // missing files
    if (status == STATUS_BUSY)
    {   for (i = 0; i <= FILES; i++)
        {   if
            (   !(os[i].flags & CODE_BLUE) // missing obsolete are NEVER shown
             && os[i].version <= theversion
             && (theversion < 39 || !(os[i].flags & NOT39))
             && (theversion < 40 || !(os[i].flags & NOT39UPDATE))
            )
            if (files.show[6] && !(os[i].flags & OPTIONAL))
            {   if (!os[i].matched)
                {   strcpy(IOBuffer, os[i].pathname); // white (missing normal)
                    dobuffer(TRUE, 14);
                }
                if ((os[i].flags & INFO) && !os[i].infomatched)
                {   strcpy(IOBuffer, os[i].pathname); // white (missing normal)
                    strcat(IOBuffer, ".info");
                    dobuffer(TRUE, 14);
            }   }
            elif (files.show[7] &&  (os[i].flags & OPTIONAL))
            {   if (!os[i].matched)
                {   strcpy(IOBuffer, os[i].pathname); // pink (missing optional)
                    dobuffer(TRUE, 15);
                }
                if ((os[i].flags & INFO) && !os[i].infomatched)
                {   strcpy(IOBuffer, os[i].pathname); // pink (missing optional)
                    strcat(IOBuffer, ".info");
                    dobuffer(TRUE, 15);
    }   }   }   }

    if (LogFileHandle)
    {   Close(LogFileHandle); // Close() doesn't return an error code
        LogFileHandle = NULL;
}   }

MODULE void stage3(void)
{   /* pens go thusly:
    0 red
    1 orange
    2 yellow
    3 green
    4 blue
    5 cyan
    6 white
    7 pink */

    stopping("Ready.");

    SetGadgetAttrs
    (   files_gadgets[GID_10_LB1], MainWindowPtr, NULL,
        LISTBROWSER_Labels, NULL,
        TAG_END
    );
    SetGadgetAttrs
    (   files_gadgets[GID_10_LB1], MainWindowPtr, NULL,
        LISTBROWSER_Labels, (ULONG) &ResultList,
        TAG_END
    );
}

/* FileNamePtr[]: each of these is a pointer to the allocated memory
area holding the pathname of that file found (excepting "SYS:" portion).
FileNamesAllocated = number of elements of FileNamePtr[]. Or, another way
to think of it, the number of files on the SYS: partition. Element 0 of
the array is not used. */

MODULE void files_work(void)
{   BOOL                 more; // BOOL, not ABOOL
    BPTR                 DirHandle; // = NULL;
    struct ExAllControl* eac;       // = NULL;
    struct ExAllData*    ead;

    /* Service routine for stage1(). Each call of this routine
    handles one directory from the work stack. This routine is the one
    that actually makes the DOS calls. It pushes any subdirectories
    found onto the stack.

    stringholder1 contains the pathname of the directory to examine
        (without "SYS:").
    lockstring contains the pathname of the directory to examine
        (with "SYS:").
    stringholder2 will contain the pathname of each file/dir found
        (with "SYS:"). */

    strcpy(lockstring, "SYS:");
    strcat(lockstring, stringholder1);

    if (!(DirHandle = (BPTR) Lock(lockstring, ACCESS_READ)))
    {   // Printf("Can't lock %s!\n", lockstring);
        rq("Can't lock directory!");
    }
    if (!(eac = AllocDosObject(DOS_EXALLCONTROL, NULL)))
    {   UnLock(DirHandle);
        DirHandle = NULL;
        rq("Can't allocate DOS object!");
    }

    eac->eac_LastKey = 0;
    do
    {   more = ExAll(DirHandle, (struct ExAllData *) EADataPtr, 4096, ED_SIZE, eac);
        if ((!more) && (IoErr() != ERROR_NO_MORE_ENTRIES))
        {   FreeDosObject(DOS_EXALLCONTROL, eac);
            eac = NULL;
            UnLock(DirHandle);
            DirHandle = NULL;
            rq("Can't examine path!"); /* ExAll() failed abnormally */
        }
        if (eac->eac_Entries == 0)
        {   ; /* ExAll() failed normally with no entries */
            continue; /* more is USUALLY zero */
        }
        ead = (struct ExAllData *) EADataPtr;

        do
        {   /* use ead here */

            strcpy(stringholder2, lockstring);
            if (!AddPart(stringholder2, ead->ed_Name, VLONGFIELD))
            {   FreeDosObject(DOS_EXALLCONTROL, eac);
                eac = NULL;
                UnLock(DirHandle);
                DirHandle = NULL;
                rq("Can't add filename/dirname to pathname!");
            }
            if (ead->ed_Type == 2) /* +2 is dir, +3 is softlink, -3 is file */
            {   AddNameToTail(&DirList,  &(stringholder2[4]));
            } elif (ead->ed_Type == -3) // if it's a file
            {   AddNameToTail(&FileList, &(stringholder2[4]));
            }

            /* get next ead */
            ead = ead->ed_Next;
        } while(ead);
    } while(more);

    FreeDosObject(DOS_EXALLCONTROL, eac);
    eac = NULL;
    UnLock(DirHandle);
    DirHandle = NULL;
}

MODULE ABOOL __inline quickcomp(STRPTR first, STRPTR second)
{   ULONG i = 0;

    while (*(first + i))
    {   if (*(first + i)      != *(second + i)
        &&  *(first + i)      != *(second + i) + 32
        &&  *(first + i) + 32 != *(second + i)
        &&  *(first + i) + 32 != *(second + i) + 32
        )
            return FALSE;
        i++;
    }
    return TRUE;
}

MODULE void dobuffer(ABOOL item, ULONG whichpen)
{   struct Node* ListBrowserNodePtr;
    TEXT         codestring[VLONGFIELD + 1];

    /* Service routine for stage2(). */

    if (item && files.show[whichpen - 8])
    {   if (!(ListBrowserNodePtr = AllocListBrowserNode
        (   1,              // columns,
            LBNA_Column,    0,
            LBNA_Flags,     LBFLG_CUSTOMPENS,
            LBNCA_FGPen,    BLACK,
            LBNCA_BGPen,    penn[whichpen - 8].pennumber,
            LBNCA_CopyText, TRUE,
            LBNCA_Text,     IOBuffer,
            TAG_END))
        )
        {   rq("Can't create ReAction listbrowser.gadget node(s)!");
        }
        AddTail(&ResultList, ListBrowserNodePtr); // AddTail() has no return code
        ResultNodes = TRUE;
    }
    if (shared.log)
    {   if (!item)
        {   strcpy(codestring, IOBuffer);
            strcat(codestring, "\n");
            if (Write(LogFileHandle, codestring, strlen(codestring)) == -1)
            {   rq("Can't append to file!");
        }   }
        elif (files.show[whichpen - 8])
        {   switch(whichpen)
            {
            case 8:
                strcpy(codestring, "! "); // red
            break;
            case 9:
                strcpy(codestring, "@ "); // orange
            break;
            case 10:
                strcpy(codestring, "# "); // yellow
            break;
            case 11:
                strcpy(codestring, "$ "); // green
            break;
            case 12:
                strcpy(codestring, "% "); // blue (obsolete)
            break;
            case 13:
                strcpy(codestring, "& "); // cyan (3rd-party)
            break;
            case 14:
                strcpy(codestring, "- "); // white (missing normal)
            break;
            case 15:
                strcpy(codestring, "= "); // pink (missing optional)
            break;
            default:
                // assert(0);
            break;
            }
            strcat(codestring, IOBuffer);
            strcat(codestring, "\n");
            if (Write(LogFileHandle, codestring, strlen(codestring)) == -1)
            {   rq("Can't append to file!");
}   }   }   }

AGLOBAL void files_init(void)
{   ULONG i;

    lockscreen();
    for (i = 0; i <= 7; i++)
    {   penn[i].pennumber = FindColor
        (   ScreenPtr->ViewPort.ColorMap,
            penn[i].red,
            penn[i].green,
            penn[i].blue,
            -1
        );
    }
    unlockscreen();
    if (!(shared.output[0]))
    {   strcpy(shared.output, "RAM:Report.txt");
}   }

AGLOBAL void files_exit(void)
{   // The window should be closed before calling this.

    if (ResultNodes)
    {   clearreactionlist(&ResultList);
        ResultNodes = FALSE;
    }
    if (LogFileHandle)
    {   Close(LogFileHandle); // Close() doesn't return an error code
        LogFileHandle = NULL;
}   }

AGLOBAL void files_die(void)
{   ULONG i;

    IOBuffer[7] = files.osversion;

    for (i = 0; i <= 7; i++)
    {   IOBuffer[8 + i] = files.show[i];
}   }

AGLOBAL void files_config(void)
{   ULONG i;

    files.osversion = IOBuffer[7];

    for (i = 0; i <= 7; i++)
    {   files.show[i] = IOBuffer[8 + i];
}   }

AGLOBAL void files_loop(ULONG gid)
{   STRPTR stringptr;

    switch (gid)
    {
    case GID_10_CH1:
        if (!(GetAttr
        (   CHOOSER_Selected, files_gadgets[GID_10_CH1], (ULONG *) &files.osversion
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB1:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB1], (ULONG *) &files.show[0]
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB2:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB2], (ULONG *) &(files.show[1])
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB3:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB3], (ULONG *) &files.show[2]
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB4:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB4], (ULONG *) &files.show[3]
        )))                                                                    
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB5:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB5], (ULONG *) &files.show[4]
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB6:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB6], (ULONG *) &files.show[5]
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB7:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB7], (ULONG *) &files.show[6]
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_CB8:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB8], (ULONG *) &files.show[7]
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
    break;
    case GID_10_ST1:
        if (!(GetAttr
        (   STRINGA_TextVal, files_gadgets[GID_10_ST1], (ULONG *) &stringptr
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        strcpy(shared.output, stringptr);
    break;
    case GID_10_BU1:
        if (asl("~(#?.info)"))
        {   strcpy(shared.pathname, aslresult);
            SetGadgetAttrs
            (   files_gadgets[GID_10_ST1], MainWindowPtr, NULL,
                STRINGA_TextVal, shared.pathname,
                TAG_END
           );
        }
    break;
    case GID_10_BU2:
        updatefiles();
    break;
    case GID_10_BU4:
        page = 0;
    break;
    case GID_10_CB9:
        if (!(GetAttr
        (   GA_Selected, files_gadgets[GID_10_CB9], &shared.log
        )))
        {   rq("Unsupported inquiry!"); // should never happen
        }
        files_updatelog();
        if (shared.log)
        {   ActivateLayoutGadget(files_gadgets[GID_10_LY1], MainWindowPtr, NULL, (Object) files_gadgets[GID_10_ST1]);
        }
    break;
    default:
    break;
}   }

AGLOBAL ULONG Hook10Func(struct Hook *h, VOID *o, VOID *msg)
{   /* "When the hook is called, the data argument points to the 
    window object and message argument to the IntuiMessage." */

    UWORD code, qual;
    ULONG scroll;

    geta4(); // wait till here before doing anything

    code = ((struct IntuiMessage *) msg)->Code;
    qual = ((struct IntuiMessage *) msg)->Qualifier;

    switch(code)
    {
    case SCAN_HELP:
        if (status == STATUS_READY)
        {   helpabout();
        }
    break;
    case SCAN_ESCAPE:
        if (status == STATUS_READY)
        {   if ((qual & IEQUALIFIER_LSHIFT) || (qual & IEQUALIFIER_RSHIFT))
            {   cleanexit(EXIT_SUCCESS);
            } else page = 0;
        } else
        {   // assert(status == STATUS_BUSY);
            status = STATUS_STOPPING;
        }
    break;
    case SCAN_V:
        if (status == STATUS_READY)
        {   if (!(qual & IEQUALIFIER_LSHIFT) && !(qual & IEQUALIFIER_RSHIFT))
            {   if (files.osversion < OSOPTIONS)
                    files.osversion++;
                else files.osversion = 0;
            } else
            {   if (files.osversion > 0)
                    files.osversion--;
                else files.osversion = OSOPTIONS;
            }
            SetGadgetAttrs
            (   files_gadgets[GID_10_CH1], MainWindowPtr, NULL,
                CHOOSER_Selected, files.osversion,
                TAG_END
            );
        }
    break;
    case SCAN_UP:
        if (qual & IEQUALIFIER_CONTROL)
        {   scroll = LBP_TOP;
        } elif (qual & IEQUALIFIER_LSHIFT || qual & IEQUALIFIER_RSHIFT)
        {   scroll = LBP_PAGEUP;
        } else scroll = LBP_LINEUP;
    break;
    case SCAN_DOWN:
        if (qual & IEQUALIFIER_CONTROL)
        {   scroll = LBP_BOTTOM;
        } elif (qual & IEQUALIFIER_LSHIFT || qual & IEQUALIFIER_RSHIFT)
        {   scroll = LBP_PAGEDOWN;
        } else scroll = LBP_LINEDOWN;
    break;
    default:
    break;
    }

    if (code == SCAN_UP || code == SCAN_DOWN)
    {   SetGadgetAttrs
        (   files_gadgets[GID_10_LB1],    // pointer to gadget
            MainWindowPtr,                // pointer to window (not window object!)
            NULL,                         // pointer to requester
            LISTBROWSER_Position, scroll, // tags
            TAG_DONE                      // done
        );
    }

    return(1);
}

MODULE void stopping(STRPTR status)
{   SetGadgetAttrs // `Status'
    (   files_gadgets[GID_10_ST2], MainWindowPtr, NULL,
        STRINGA_TextVal, status,
        TAG_END
    );
    SetGadgetAttrs // `Stop'
    (   files_gadgets[GID_10_BU3], MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_END
    );
}

MODULE void files_updatelog(void)
{   BOOL inverse;

    if (shared.log)
    {   inverse = FALSE;
    } else
    {   inverse = TRUE;
    }

    SetGadgetAttrs // `Log to file:' (string)
    (   files_gadgets[GID_10_ST1], MainWindowPtr, NULL,
        GA_Disabled, inverse,
        TAG_END
    );
    /* For some reason, we need to explicitly refresh the string gadget
    visuals here */
    RefreshGadgets((struct Gadget *) files_gadgets[GID_10_ST1], MainWindowPtr, NULL);

    SetGadgetAttrs // `Log to file:' (ASL button)
    (   files_gadgets[GID_10_BU1], MainWindowPtr, NULL,
        GA_Disabled, inverse,
        TAG_END
    );
}

