#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 <gadgets/listbrowser.h>
#include <pragmas/listbrowser_pragmas.h>

#include <stdlib.h>
#include "amigan.h"
#include "files.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);

AGLOBAL struct List         ResultList;

IMPORT ABOOL                pal, fillwindows;
IMPORT TEXT                 IOBuffer[LONGESTFIELD + 1],
                            aslresult[MEDFIELD + 1];
IMPORT SBYTE                page;
IMPORT ULONG                wbval;
IMPORT struct SharedStruct  shared;
IMPORT struct Screen*       ScreenPtr;
IMPORT struct Library*      ListBrowserBase;
IMPORT struct ExAllData*    ead;
IMPORT struct VisualInfo*   VisualInfoPtr;

MODULE struct List          DirList, FileList;
MODULE ABOOL                list_has_nodes = FALSE, stop;
MODULE struct
{   ABOOL show[8];
    TEXT  showlabel[8][17 + 1];
    TEXT  path[VLONGFIELD + 1];
    ULONG entries;
} files =
{   TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE,
    "V_ery important",
    "_Important",
    "_Semi-important",
    "U_nimportant",
    "O_bsolete",
    "_3rd-party",
    "Missin_g",
    "Missing o_ptional",
    "", 0
};
MODULE  BPTR           TheFileHandle       = NULL;
MODULE  UWORD          osversion;
MODULE  TEXT           stringholder1[VLONGFIELD + 1],
                       stringholder2[VLONGFIELD + 1],
                       lockstring[VLONGFIELD + 1];
MODULE  struct Gadget *MX101_OSVersion     = NULL,
                      *TE101_OSVersionText = NULL,
                      *TE101_Status        = NULL,
                      *TE101_ShowText      = NULL,
                      *CB101_Show[8] =
                      {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};

#define OSVERSIONS 5 // counting from 0
MODULE  STRPTR OSOptions[OSVERSIONS + 2] =
{   "OS3.1",
    "OS3.5",
    "OS3.5 & Boing Bag(s)",
    "OS3.9",
    "OS3.9 & Genesis Update",
    "OS3.9 & Boing Bag 1",
    NULL
};
MODULE struct
{   ULONG red, green, blue, pennumber;
} penn[7] =
{     {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)
};

// from rp.c
IMPORT SBYTE               page;
IMPORT struct NewGadget*   Gadget;
IMPORT struct Gadget      *BU99_Right,
                          *ST99_Output,
                          *BU99_OutputASL,
                          *BU99_Update,
                          *BU99_Stop,
                          *CB99_Log,
                          *PrevGadPtr;
IMPORT struct Window*      MainWindowPtr;
IMPORT TEXT                weekdaystring[LEN_DATSTRING],
                           datestring[LEN_DATSTRING],
                           timestring[LEN_DATSTRING];

AGLOBAL void files1(void)
{   ULONG i;

    verynewwindow
    (   FILES1WIDTH, FILES1HEIGHT,
        "Report+: System File Report",
        BUTTONIDCMP | STRINGIDCMP | MXIDCMP
    );
    if (fillwindows)
    {   SetAPen(MainWindowPtr->RPort, 0);
        RectFill(MainWindowPtr->RPort, 446, 20, 446 + 72, 20 + 12);
    }

    // OS version
    setgadget(244, 108, 128, 0, "AmigaOS _Version:", PLACETEXT_ABOVE);
    TE101_OSVersionText = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );
    setgadget(250, 108, 0, 0, NULL, PLACETEXT_RIGHT);
    MX101_OSVersion = PrevGadPtr = (struct Gadget *) CreateGadget
    (   MX_KIND,
        PrevGadPtr,
        &Gadget,
        GTMX_Labels, &OSOptions,
        GTMX_Active, osversion,
        GT_Underscore, '_',
        TAG_DONE
    );

    // show
    setgadget(10, 35, 40, 0, "Show:", PLACETEXT_ABOVE);
    TE101_ShowText = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        TAG_DONE
    );
    for (i = 0; i <= 7; i++)
    {   setgadget(16, 35 + (i * 12), 0, 0, files.showlabel[i], PLACETEXT_RIGHT);
        CB101_Show[i] = PrevGadPtr = (struct Gadget *) CreateGadget
        (   CHECKBOX_KIND,
            PrevGadPtr,
            &Gadget,
            GTCB_Checked, files.show[i],
            GT_Underscore, '_',
            TAG_DONE
        );
    }

    /* update */
    setgadget(244, 20, 60, 12, "_Update", NULL);
    BU99_Update = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );
    /* stop */
    setgadget(304, 20, 60, 12, "Stop", NULL);
    BU99_Stop = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GA_Disabled, TRUE,
        TAG_DONE
    );

    if (wbval >= 44)
        shared.log = FALSE;
    else shared.log = TRUE;
    // log to file?
    setgadget(244, 45, 0, 0, "_Log to file?", PLACETEXT_RIGHT);
    CB99_Log = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GTCB_Checked, shared.log,
        GT_Underscore, '_',
        TAG_DONE
    );

    /* output */
    if (!(shared.output[0]))
        strcpy(shared.output, "RAM:Report.txt");
    setgadget(244, 72, 244, 12, "_Output pathname:", PLACETEXT_ABOVE);
    ST99_Output = PrevGadPtr = (struct Gadget *) CreateGadget
    (   STRING_KIND,
        PrevGadPtr,
        &Gadget,
        GTST_String, &(shared.output),
        GTST_MaxChars, LONGFIELD,
        GA_TabCycle, TRUE,
        GT_Underscore, '_',
        TAG_DONE
    );
    /* output... */
    setgadget(490, 72, 28, 12, "_...", NULL);
    BU99_OutputASL = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );

    // status
    setgadget(446, 20, 72, 12, "Status:", NULL);
    TE101_Status = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GTTX_Text, "Ready",
        GTTX_Border, TRUE,
        TAG_DONE
    );

    /* There are 32 gadgets:
        1  BU99_Right         = 1
        1 TE101_OSVersionText = 2
        4 MX101_OSVersion     = 6
        1 TE101_ShowText      = 7
       16 CB101_Show[8]       =23
        1  BU99_Update        =24
        1  BU99_Stop          =25
        2  CB99_Log           =27
        2  ST99_Output        =29
        1  BU99_OutputASL     =30
        2 TE101_Status        =32 */
    drawgadgets(32);

    if (wbval < 44)
    {   GT_SetGadgetAttrs
        (   CB99_Log,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
    }
    if (!shared.log)
    {   GT_SetGadgetAttrs
        (   ST99_Output,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   BU99_OutputASL,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
    }
    loop();
    strcpy
    (   shared.output,
        ((struct StringInfo *) ST99_Output->SpecialInfo)->Buffer
    );
    closewindow();
}

MODULE void updatefiles(void)
{   ULONG            i;

    /* Gadgets are: BU99_Right ('menu')
                    BU99_Update
                    CB99_Log, ST99_Output, BU99_OutputASL
                    MX101_OSVersion
                    CB101_Show[]
    All are ghosted during 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. */

    stop = FALSE;
    GT_SetGadgetAttrs
    (   BU99_Right,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Update,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   CB99_Log,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   ST99_Output,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_OutputASL,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   MX101_OSVersion,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    for (i = 0; i <= 7; i++)
    {   GT_SetGadgetAttrs
        (   CB101_Show[i],
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
    }
    GT_SetGadgetAttrs
    (   BU99_Stop,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   TE101_Status,
        MainWindowPtr, NULL,
        GTTX_Text, "Busy",
        TAG_DONE
    );

    stage1();
    if (!stop)
    {   stage2();
    }
    FreeNameNodes(&FileList);
    if (!stop)
    {   stage3();
    }
    clearlblist();

    GT_SetGadgetAttrs
    (   BU99_Right,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Update,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    if (wbval >= 44)
    {   GT_SetGadgetAttrs
        (   CB99_Log,
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
    }
    if (shared.log)
    {   GT_SetGadgetAttrs
        (   ST99_Output,
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   BU99_OutputASL,
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
    }
    GT_SetGadgetAttrs
    (   MX101_OSVersion,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    for (i = 0; i <= 7; i++)
    {   GT_SetGadgetAttrs
        (   CB101_Show[i],
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
    }
    GT_SetGadgetAttrs
    (   BU99_Stop,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   TE101_Status,
        MainWindowPtr, NULL,
        GTTX_Text, "Ready",
        TAG_DONE
    );
}

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 (checkbreak() == 1) // we don't yet support completely quitting
        {   stop = TRUE;
            GT_SetGadgetAttrs
            (   TE101_Status,
                MainWindowPtr, NULL,
                GTTX_Text, "Stopping",
                TAG_DONE
            );
            GT_SetGadgetAttrs
            (   BU99_Stop,
                MainWindowPtr, NULL,
                GA_Disabled, TRUE,
                TAG_DONE
            );
            break;
        }

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

        if (!(NodePtr = 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 (!(TheFileHandle = (BPTR) Open(shared.output, MODE_READWRITE)))
            rq("Can't open file for appending!");
        Seek(TheFileHandle, 0, OFFSET_END);
    }

    // Now we have the list of files
    getdate();
    strcpy(IOBuffer, "AmigaOS 3.");
    if (osversion == 0)
    {   theversion = 31;
        strcat(IOBuffer, "1");
    } elif (osversion == 1)
    {   theversion = 35;
        strcat(IOBuffer, "5");
    } elif (osversion == 2)
    {   theversion = 36;
        strcat(IOBuffer, "5 + BB1/2");
    } elif (osversion == 3)
    {   theversion = 39;
        strcat(IOBuffer, "9");
    } elif (osversion == 5)
    {   theversion = 41;
        strcat(IOBuffer, "9 + BB1");
    } else
    {   // assert(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;

    // stop is always FALSE at this point, because this function is not
    // called otherwise.

    while ((FileList.lh_Head)->ln_Succ) // while the list is non-empty
    {   if (checkbreak() == 1)
        {   stop = TRUE;
            GT_SetGadgetAttrs
            (   TE101_Status,
                MainWindowPtr, NULL,
                GTTX_Text, "Stopping",
                TAG_DONE
            );
            GT_SetGadgetAttrs
            (   BU99_Stop,
                MainWindowPtr, NULL,
                GA_Disabled, TRUE,
                TAG_DONE
            );
            strcpy(IOBuffer, "Aborted by user!");
            dobuffer(FALSE, 0);
            break;
        }
        // assert(!stop);

        if (!(NodePtr = 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 (!stop)
    {   for (i = 0; i <= FILES; i++)
        {   if
            (   !(os[i].flags & CODE_BLUE) // missing obsolete are NEVER shown
             && (   (files.show[6] && !(os[i].flags & OPTIONAL))
                 || (files.show[7] &&  (os[i].flags & OPTIONAL))
                )
             && os[i].version <= theversion
             && (theversion < 39 || !(os[i].flags & NOT39))
             && (theversion < 40 || !(os[i].flags & NOT39UPDATE))
            )
            {   if (!os[i].matched)
                {   strcpy(IOBuffer, os[i].pathname); // white (missing)
                    dobuffer(TRUE, 14);
                }
                if ((os[i].flags & INFO) && !os[i].infomatched)
                {   strcpy(IOBuffer, os[i].pathname); // white (missing)
                    strcat(IOBuffer, ".info");
                    dobuffer(TRUE, 14);
    }   }   }   }

    if (shared.log && TheFileHandle)
    {   Close(TheFileHandle); // Close() doesn't return an error code
        TheFileHandle = NULL;
}   }

MODULE void stage3(void)
{   if (wbval >= 44)
    {   /* pens go thusly:
        0 red
        1 orange
        2 yellow
        3 green
        4 blue
        5 cyan
        6 white */

        GT_SetGadgetAttrs
        (   BU99_Right,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   TE101_Status,
            MainWindowPtr, NULL,
            GTTX_Text, "Showing",
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   BU99_Stop,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
        listbrowse(); // this routine is synchronous, not asynchronous
}   }

/* 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*    trueead   = 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 *) trueead, 2048, 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 = trueead; // the two eads are about to split

        do
        {   /* use ead here */

            strcpy(stringholder2, lockstring);
            if (!AddPart(stringholder2, ead->ed_Name, VLONGFIELD))
            {   FreeDosObject(DOS_EXALLCONTROL, eac);
                eac = NULL;
                UnLock(DirHandle);
                DirHandle = NULL;
                ead = trueead; // for clean shutdown
                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);
        // the way this works is that ead is fooled with. So by the end
        // of the routine ead is wrong, and can't be reused.
    } while(more);

    ead = trueead; // the two eads are reunited as one :-)
    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];
    ULONG        truewhichpen;

    /* Service routine for stage2(). */

    if (item && wbval >= 44 && files.show[whichpen - 8])
    {   if (pal)
        {   if (whichpen == 14)
            {   truewhichpen = 2; // white
            } else truewhichpen = whichpen;

            if (!(ListBrowserNodePtr = AllocListBrowserNode
            (   1,              // columns,
                LBNA_Column,    0,
                LBNA_Flags,     LBFLG_CUSTOMPENS,
                LBNCA_FGPen,    BLACK,
                LBNCA_BGPen,    truewhichpen,
                LBNCA_CopyText, TRUE,
                LBNCA_Text,     IOBuffer,
                TAG_END))
            )
            {   rq("Can't create ReAction listbrowser.gadget node(s)!");
        }   }
        else
        {   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
        list_has_nodes = TRUE;
    }
    if (shared.log)
    {   if (item)
        {   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)
            break;
            default:
                // assert(0);
            break;
            }
            strcat(codestring, IOBuffer);
        } else
        {   strcpy(codestring, IOBuffer);
        }
        strcat(codestring, "\n");
        if (Write(TheFileHandle, codestring, strlen(codestring)) == -1)
        {   rq("Can't append to file!");
}   }   }

AGLOBAL void files_loop(ULONG class, struct Gadget* addr, UWORD code, UWORD qual)
{   ULONG i;

    // page is ALWAYS 101 in this routine, so it is not checked for.

    if (class == IDCMP_VANILLAKEY)
    {   code = toupper(code);
        if (code == 'V')
        {   if (!(qual & IEQUALIFIER_LSHIFT) && !(qual & IEQUALIFIER_RSHIFT))
            {   if (osversion < OSVERSIONS)
                    osversion++;
                else osversion = 0;
            } else
            {   if (osversion > 0)
                    osversion--;
                else osversion = OSVERSIONS;
            }
            GT_SetGadgetAttrs
            (   MX101_OSVersion,
                MainWindowPtr,
                NULL,
                GTMX_Active, osversion,
                TAG_DONE
            );
        } elif (code == 'L')
        {   fliplog(TRUE);
        } elif (code == 'U')
        {   updatefiles();
        } elif (code == 'E')
        {   if (CB101_Show[0]->Flags & GFLG_SELECTED)
                files.show[0] = FALSE;
            else files.show[0] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[0],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[0],
                TAG_DONE
            );
        } elif (code == 'I')
        {   if (CB101_Show[1]->Flags & GFLG_SELECTED)
                files.show[1] = FALSE;
            else files.show[1] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[1],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[1],
                TAG_DONE
            );
        } elif (code == 'S')
        {   if (CB101_Show[2]->Flags & GFLG_SELECTED)
                files.show[2] = FALSE;
            else files.show[2] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[2],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[2],
                TAG_DONE
            );
        } elif (code == 'N')
        {   if (CB101_Show[3]->Flags & GFLG_SELECTED)
                files.show[3] = FALSE;
            else files.show[3] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[3],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[3],
                TAG_DONE
            );
        } elif (code == 'B')
        {   if (CB101_Show[4]->Flags & GFLG_SELECTED)
                files.show[4] = FALSE;
            else files.show[4] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[4],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[4],
                TAG_DONE
            );
        } elif (code == '3')
        {   if (CB101_Show[5]->Flags & GFLG_SELECTED)
                files.show[5] = FALSE;
            else files.show[5] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[5],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[5],
                TAG_DONE
            );
        } elif (code == 'G')
        {   if (CB101_Show[6]->Flags & GFLG_SELECTED)
                files.show[6] = FALSE;
            else files.show[6] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[6],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[6],
                TAG_DONE
            );
        } elif (code == 'P')
        {   if (CB101_Show[7]->Flags & GFLG_SELECTED)
                files.show[7] = FALSE;
            else files.show[7] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[7],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[7],
                TAG_DONE
            );
        } elif (code == 'O')
        {   ActivateGadget(ST99_Output, MainWindowPtr, NULL);
        } elif (code == '.')
        {   asl();
            GT_SetGadgetAttrs
            (   ST99_Output,
                MainWindowPtr,
                NULL,
                GTST_String, aslresult,
                TAG_DONE
            );
        } elif (code == ESCAPE)
        {   page = 0;
    }   }
    elif (class == IDCMP_GADGETDOWN)
    {   // radio gadgets
        if (addr == MX101_OSVersion)
            osversion = code;
    } elif (class == IDCMP_GADGETUP)
    {   /* IDCMP_GADGETUP is sent by the string gadget
           when the user presses RETURN, ENTER, Help, Tab
           or Shift-Tab inside the string gadget. */

        if (addr == BU99_Right)
        {   page = 0;
        } elif (addr == BU99_Update)
        {   updatefiles();
        } elif (addr == CB99_Log)
        {   fliplog(FALSE);
        } elif (addr == ST99_Output)
        {   outputstring();
        } elif (addr == BU99_OutputASL)
        {   outputasl();
        } else
        {   for (i = 0; i <= 6; i++)
            {   if (addr == CB101_Show[i])
                {   if (CB101_Show[i]->Flags & GFLG_SELECTED)
                    {   files.show[i] = TRUE;
                    } else files.show[i] = FALSE;
                    GT_SetGadgetAttrs
                    (   CB101_Show[i],
                        MainWindowPtr, NULL,
                        GTCB_Checked, files.show[i],
                        TAG_DONE
                    );
                    break;
}   }   }   }   }

AGLOBAL void files_init(void)
{   ULONG i;

    if (wbval <= 43)
    {   osversion = 0;  // OS3.1
    } elif (wbval == 44)
    {   osversion = 2;  // OS3.5 + BB1/2
    } else osversion = 5; // OS3.9 + BB1

    if (!pal)
    {   if (!(ScreenPtr = LockPubScreen(NULL)))
            rq("Can't lock default public screen!");
        for (i = 0; i <= 6; i++)
        {   penn[i].pennumber = FindColor
            (   ScreenPtr->ViewPort.ColorMap,
                penn[i].red,
                penn[i].green,
                penn[i].blue,
                -1
            );
        }
        UnlockPubScreen(NULL, ScreenPtr);
        for (i = 0; i <= 6; i++)
        {   penn[i].pennumber = FindColor
            (   ScreenPtr->ViewPort.ColorMap,
                penn[i].red,
                penn[i].green,
                penn[i].blue,
                -1
            );
}   }   }

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

    if (shared.log && TheFileHandle)
    {   Close(TheFileHandle); // Close() doesn't return an error code
        TheFileHandle = NULL;
    }
    if (ead)
    {   FreeMem(ead, 2048);
        ead = NULL;
}   }

// Function to free an Exec List of ListBrowser nodes.
AGLOBAL void clearlblist(void)
{   /* Requirements:
        listbrowser class must be already open
        list must not be currently attached to a listbrowser
        non-empty list. */

    struct Node *NodePtr, *NextNodePtr;

    if (list_has_nodes)
    {   NodePtr = ResultList.lh_Head;
        while (NextNodePtr = NodePtr->ln_Succ)
        {   FreeListBrowserNode(NodePtr);
            NodePtr = NextNodePtr;
    }   }
    list_has_nodes = FALSE;
    NewList(&ResultList); // to avoid accidental reuse. Harmless.
}
