/*
 *              BUFFER.C                                                                                        vi:ts=4
 *
 *      Copyright (c) Eddy Carroll, September 1994.
 *      Updated 5.3.2000 Thomas Richter, THOR
 *
 *              This module maintains the review buffer used by SnoopDos to record
 *              the past events that have occurred.
 */             

#define TESTING         0

#include "system.h"
#include "snoopdos.h"
#include "patches.h"

#define INVALID_HUNK            ((USHORT)(-1))

#if 0
#define DB(s)   KPrintF(s)
#else
#define DB(s)
#endif

static UBYTE       *BufferStart;                /* Ptr to start of our buffer           */
static UBYTE       *BufferEnd;                  /* Ptr to end of our buffer                     */
static UBYTE       *BufferCur;                  /* Ptr to next free loc in buffer       */
static ULONG            BufferSize;                     /* Size of current buffer                       */

/*
 *              This is the mapping that assigns format ID's to the various
 *              fields we can output.
 */
FieldInit FieldTypes[] = {
        EF_ACTION,              'a',    10,             MSG_ACTION_COL,
        EF_CALLADDR,    'c',    8,              MSG_CALL_COL,
        EF_DATE,                'd',    9,              MSG_DATE_COL,
        EF_HUNKOFFSET,  'h',    11,             MSG_HUNK_COL,
        EF_PROCID,              'i',    8,              MSG_PROCESSID_COL,
        EF_FILENAME,    'n',    27,             MSG_FILENAME_COL,
        EF_OPTIONS,             'o',    7,              MSG_OPTIONS_COL,
        EF_PROCNAME,    'p',    18,             MSG_PROCNAME_COL,
        EF_RESULT,              'r',    4,              MSG_RESULT_COL,
        EF_SEGNAME,             's',    20,             MSG_SEGNAME_COL,
        EF_TIME,                't',    8,              MSG_TIME_COL,
        EF_COUNT,               'u',    5,              MSG_COUNT_COL,
        EF_END,                 0,              0,              0
};

/*
 *              InitBuffers()
 *
 *              Initialises certain variables associated with the buffer routines.
 *              Must be called before any of the buffer functions are called.
 */
void InitBuffers(void)
{
        InitSemaphore(&BufSem);
        NewList(&EventList);
}

/*
 *              SetTotalBufferSize(size, alloctype)
 *
 *              This function is used to change the size of the buffer. If no buffer
 *              has yet been created, one is allocated. If there is not enough room
 *              to create a new buffer without freeing the existing buffer first,
 *              then FALSE is returned, _unless_ the alloctype parameter is
 *              SETBUF_FORCENEW, in which case it simply frees the existing buffer
 *              first, losing any existing history info.
 *
 *              In general, the old buffer's contents are copied across to the
 *              new buffer so that the user's history isn't destroyed.
 *
 *              The intention is that the caller tries to use this call with
 *              the tryalways flag set to SETBUF_KEEPOLD first; if that fails,
 *              then it can display a requester to the user saying that this will
 *              cause the existing buffer info to be lost permanently if you go
 *              ahead--Yes/No?  and then do a retry with alloctype set to
 *              SETBUF_FORCENEW if the user so chooses.
 *
 *              In any case, when allocating a new buffer, the routine will always
 *              allocate a buffer if possible, even if it can't allocate the full
 *              size requested.
 *
 *              Returns FALSE if there wasn't enough room to allocate the new buffer
 *              (the minimum buffer size is something like 4K.)
 *
 *              Note that if the size of the buffer is being reduced, the caller
 *              is responsible for updating the display to reflect the new status.
 *
 *              Note also that the whole idea of only freeing the buffer if there's
 *              no other option is great in principle; in practise, it turned out to
 *              be too much work to support copying buffers for such an infrequent
 *              operation, so we don't do it after all.
 */
int SetTotalBufferSize(ULONG newsize, int alloctype)
{
        UBYTE *oldbuf = BufferStart;
        ULONG oldsize = BufferSize;
        UBYTE *newbuf;

        ObtainSemaphore(&BufSem);
        ClearBuffer();          /* Clear the existing buffer first */

        /*
         *              First, work out if we have enough memory to satisfy the request
         *              without freeing our old buffer first. If we don't, and if
         *              alloctype is SETBUF_KEEPOLD, then we fail immediately.
         */
        if (newsize < MIN_BUF_SIZE)
                newsize = MIN_BUF_SIZE;

        newbuf = AllocMem(newsize, MEMF_ANY);
        if (!newbuf) {
                /*
                 *              No memory available -- we _may_ need to bomb out at this
                 *              point. If we didn't have a buffer already, then we
                 *              always go ahead and try and allocate some, regardless
                 *              of alloctype.
                 */
                if (oldbuf && alloctype == SETBUF_KEEPOLD) {
                        ReleaseSemaphore(&BufSem);
                        return (FALSE);
                }

                /*
                 *              Okay, try and allocate the memory even though it means
                 *              freeing up the old buffer first.
                 */
                if (oldbuf) {
                        FreeMem(oldbuf, oldsize);
                        oldbuf = NULL;
                }
                newbuf = AllocMem(newsize, MEMF_ANY);
                if (!newbuf) {
                        /*
                         *              Couldn't allocate memory on second attempt. Finally,
                         *              we just grab the largest chunk we can and use that
                         *              instead.
                         */
                        ULONG freesize;

                        Forbid();
                        freesize = AvailMem(MEMF_ANY | MEMF_LARGEST);
                        if (freesize < MIN_BUF_SIZE) {
                                Permit();
                                ReleaseSemaphore(&BufSem);
                                return (FALSE);
                        }
                        if (freesize < newsize) /* freesize > newsize _is_ possible! */
                                newsize = freesize;
                        newbuf  = AllocMem(newsize, MEMF_ANY);
                        Permit();
                        if (!newbuf) {
                                /*
                                 *              Should never happen, but better safe than sorry
                                 */
                                ReleaseSemaphore(&BufSem);
                                return (FALSE);
                        }
                }
        }
        /*
         *              Okay, we finally have some memory so initialise the global
         *              buffer variables for use by everyone else.
         */
        BufferStart = newbuf;
        BufferSize  = newsize;
        BufferEnd   = newbuf + newsize;
        BufferCur   = BufferStart;

        if (oldbuf != NULL) {
                // CopyEvents(newbuf, newsize, oldbuf, oldsize);
                FreeMem(oldbuf, oldsize);
        }
        NewList(&EventList);            /* Probably need to delete this eventually */
        ReleaseSemaphore(&BufSem);
        return (TRUE);
}

// void CopyEvents(UBYTE *newbuf, ULONG newsize,
//                              UBYTE *oldbuf, ULONG oldsize) {}

/*
 *              ClearBuffer()
 *
 *              Clears the current buffer by a very simple process -- it simply
 *              erases the current list of linked items, and resets RealFirstSeq etc.
 *              to be the next available event number. This means that any events
 *              that were partially in progress will spot, on their completition,
 *              that their associated event number is now < RealFirstSeq, and so
 *              won't try and write into the now-cleared buffer, thus avoiding
 *              corruption.
 *
 *              BaseSeq is used to allow the new buffer count to commence from
 *              1 again, rather than keeping the old number (which might be
 *              into the hundreds by this stage!)
 *
 *              Note: we are a little bit naughty in some of the patches in that
 *              after allocating a brand new event, we sometimes write into it
 *              immediately _without_ semaphore protection (to fill in the
 *              initial details etc.) This is not terribly wise, but since there
 *              is never anything to deliberately cause a task switch, it's
 *              reasonably safe. Adding semaphore protection to all these cases
 *              would complicate things quite a bit.
 */
void ClearBuffer(void)
{
        ObtainSemaphore(&BufSem);
        Delay(5);       /* Just to be safe, let everyone else run for 0.1 seconds */

        NewList(&EventList);
        BufferCur = BufferStart;

        BaseSeq                 =
        FirstSeq                =
        RealFirstSeq    =
        TopSeq                  =
        LastDrawnTopSeq =
        BottomSeq               =
        MaxScannedSeq   =
        EndSeq                  =
        EndCompleteSeq  = NextSeq;

        ReleaseSemaphore(&BufSem);
}

/*
 *              CleanupBuffers()
 *
 *              Frees up the buffer on exit, along with any other pertinent
 *              resources that were allocated by this module.
 */
void CleanupBuffers(void)
{
        if (BufferStart) {
                ObtainSemaphore(&BufSem);
                FreeMem(BufferStart, BufferSize);
                if (BufferSize == 0)
                        ShowError("Warning! BufferSize is zero for some reason!");
                BufferStart = NULL;
                BufferSize  = 0;
                NewList(&EventList);
                ReleaseSemaphore(&BufSem);
        }
}

/*
 *              GetNewEvent(stringsize)
 *
 *              Allocates a new event structure from the list from the buffer
 *              and returns a pointer to it. If necessary, one of the existing
 *              buffers will be flushed to make room for the new event.
 *
 *              The stringsize is the maximum amount of extra space to allocate
 *              after the end of the event to accomodate string storage. A pointer
 *              to this data area can be obtained by accessing the first location
 *              after the event itself. For example, GetNewEvent(20) would return
 *              an event with 20 additional data bytes after it. Note that this
 *              _includes_ any terminating zeros that may be present in the
 *              strings.
 */
Event *GetNewEvent(int stringsize)
{
        UBYTE *startev;
        ULONG totalsize;
        Event *ev;

        /* Calculate total size and ensure it's a longword multiple */
        totalsize = (sizeof(Event) + stringsize + 3) & ~3;

        if (BufferStart == NULL || totalsize > (MIN_BUF_SIZE/2))
                return (NULL);  /* Should never happen but doesn't hurt to check */

        DB(" [GNE-Go] ");
        ObtainSemaphore(&BufSem);
#if 0
        ev = (Event *)BufferStart;
        ReleaseSemaphore(&BufSem);
        return (ev);
#endif

        startev = HeadNode(&EventList);
        if (IsListEmpty(&EventList)) {
                /*
                 *              First time being called, so simply begin at the
                 *              start of the buffer.
                 */
                BufferCur = BufferStart;
        } else {
                /*
                 *              There are already some nodes. Try and allocate a new node,
                 *              flushing nodes from the start of the list as necessary.
                 */
                for (;;) {
                        if (startev < BufferCur) {
                                /*
                                 *              From BufferCur to the end of the buffer is free space,
                                 *              so we can check for a quick fit. If we get one, then
                                 *              we go for it, otherwise we move BufferCur back to the
                                 *              start of the buffer and start flushing events from
                                 *              there.
                                 */
                                if (totalsize <= (BufferEnd - BufferCur))
                                        break;  /* Got it */
                                BufferCur = BufferStart;        /* Else wrap to start of list */
                        }
                        /*
                         *              Now delete events from here to the end of the buffer until
                         *              we finally have enough room in the buffer. If we run off
                         *              the end of the buffer, let the main loop go around again
                         *              to reset everything.
                         */
                        while (startev >= BufferCur && (startev - BufferCur) < totalsize) {
                                RemHead(&EventList);
                                if (IsListEmpty(&EventList)) {
                                        /* This really shouldn't be printed unless
                                         * debugging is turned on. Fixed,
                                         * THOR
                                         */
                                        DB("Uhoh --- list is empty and shouldn't be!\n");
                                        Delay(50);
                                        startev = BufferCur = BufferStart;
                                        break;
                                }
                                startev = HeadNode(&EventList);
                        }
                        if (startev > BufferCur)
                                break;  /* Got it */
                }
        }
        ev                 = (Event *)BufferCur;
        BufferCur += totalsize;

        /*
         *              Allocate our new event from the current buffer block (which
         *              we are now assured will have enough space in it)
         */
        ev->seqnum      = ++NextSeq;                    /* Give it a unique sequence number */
        ev->status      = ES_CREATING;                  /* Mark event as not-to-be-touched      */
        ev->flags   = EFLG_NONE;                        /* No date/segment lookup as yet        */
        AddTail(&EventList, (Node *)ev);
        /*
         *              Update current first seq num so patch code knows when an
         *              event currently being created has become invalid
         */
        RealFirstSeq = ((Event *)HeadNode(&EventList))->seqnum;
        DateStamp(&ev->datestamp);                      /* Store current date                           */
        DB(" [GNEEnd] ");
        ReleaseSemaphore(&BufSem);
        return (ev);
}

/*
 *              ParseFormatString(formatstr, evformat, maxfields)
 *
 *              Parses a text format string containing a list of format identifiers
 *              (%d, %s, etc.) with optional field widths (%20s, %10d) into an
 *              internal list of formatting codes that can be quickly processed
 *              when displaying output.
 *
 *              formatstr points to the format string. evformat points to the
 *              EventFormat[] array to hold the output.
 *
 *              maxfields is the maximum number of format fields that will
 *              be recognised -- any more than this will be ignored. evformat[]
 *              must be able to hold at least maxfields elements. If a field is
 *              duplicated, only the first occurrance is recognised.
 *
 *              Returns the length any output produced using this format array will
 *              have (including separating spaces)
 */
int ParseFormatString(char *formatstr, EventFormat *evformat, int maxfields)
{
        int evindex = 0;
        int totlen  = 0;
        char *p     = formatstr;

        while (*p) {
                int width = 0;
                int i;
                char type;
                char ch;

                if (*p++ != '%')
                        continue;

                /*
                 *              Got a format specifier, so now parse it.
                 *
                 *              We ignore any negative prefix -- in the real printf,
                 *              it means left-align this field, and all our fields are
                 *              left-aligned by default anyway.
                 */
                if (*p == '-')
                        p++;
                while (*p >= '0' && *p <= '9') {
                        width = (width * 10) + *p - '0';
                        p++;
                }
                if (!*p)
                        break;

                ch = *p++;
                if (ch >= 'A' && ch <= 'Z')
                        ch |= 0x20;

                for (i = 0; FieldTypes[i].type != EF_END; i++) {
                        if (FieldTypes[i].idchar == ch)
                                break;
                }
                type = FieldTypes[i].type;
                if (type != EF_END) {
                        /*
                         *              Matched a field. Now try and insert it. But first,
                         *              make sure we don't have a duplicate.
                         */
                        int j;

                        for (j = 0; j < evindex; j++)
                                if (evformat[j].type == type)   /* Got a duplicate */
                                        break;

                        if (j < evindex)                /* Skip over duplicates */
                                continue;       

                        if (width == 0)
                                width = FieldTypes[i].defwidth;

                        if (width < 1)                          width = 1;
                        if (width > MAX_FIELD_LEN)      width = MAX_FIELD_LEN;

                        evformat[evindex].type           = type;
                        evformat[evindex].width          = width;
                        evformat[evindex].titlemsgid = FieldTypes[i].titlemsgid;
                        totlen += width + 1;
                        evindex++;
                        if (evindex >= (maxfields-1))
                                break;
                }
        }
        evformat[evindex].type = EF_END;
        if (totlen)                             /* Compensate for the extra 'space' we counted */
                totlen--;

        return (totlen);
}

/*
 *              BuildFormatString(evformat, formatstr, maxlen)
 *
 *              Converts an existing event format array into an ascii string
 *              corresponding to the format that can be parsed by ParseFormatString.
 *              The string is guaranteed to be at most maxlen chars in length
 *              (including terminating \0);
 */
void BuildFormatString(EventFormat *evformat, char *formatstr, int maxlen)
{
        char *p   = formatstr;
        char *end = formatstr + maxlen - 6;     /* Max length of format element is 5 */

        while (evformat->type != EF_END) {
                int j;

                if (p >= end)   /* Never exceed string length */
                        break;
                /*
                 *              Find the event type in our mapping array so that we can
                 *              determine the default width. This lets us choose not
                 *              to include the width in the ascii field, for neatness.
                 */
                for (j = 0; FieldTypes[j].type != evformat->type; j++)
                        ;
                *p++ = '%';
                if (FieldTypes[j].defwidth != evformat->width) {
                        mysprintf(p, "%ld", evformat->width);
                        p += strlen(p);
                }
                *p++ = FieldTypes[j].idchar;
                *p++ = ' ';     /* Space out entries for neatness */
                evformat++;
        }
        p[-1] = '\0';   /* Replace final space with a terminator instead. */
}

/*
 *              ltoa(buffer, val, numdigits)
 *              ltoap(buffer, val, numdigits)
 *
 *              Converts the given value to an ascii hex string of up to numdigits
 *              (with leading zeros). No zero termination is performed. The output
 *              is stored in the first numdigits characters of buffer.
 *
 *              ltoap is similar, except that it pads leading zeros with spaces.
 *
 *              Returns a pointer to the buffer.
 */
char *ltoa(char *buffer, unsigned long val, int numdigits)
{
        static char HexDigits[] = "0123456789ABCDEF";
        char *p = buffer + numdigits - 1;

        while (p >= buffer) {
                *p-- = HexDigits[val & 0x0F];
                val >>= 4;
        }
        return (buffer);
}

char *ltoap(char *buffer, unsigned long val, int numdigits)
{
        static char HexDigits[] = "0123456789ABCDEF";
        char *p = buffer + numdigits - 1;

        do {
                *p-- = HexDigits[val & 0x0F];
                val >>= 4;
        } while (val && p >= buffer);

        while (p >= buffer)
                *p-- = ' ';

        return (buffer);
}

/*
 *              SetEventDateStr(event)
 *
 *              This function converts the datestamp in the passed event into a date
 *              and time string, also stored in the event.
 */
void SetEventDateStr(Event *ev)
{
        struct DateTime datetime;

        datetime.dat_Stamp              = ev->datestamp;
        datetime.dat_Format             = FORMAT_DOS;
        datetime.dat_Flags              = 0;
        datetime.dat_StrDay             = NULL;
        datetime.dat_StrDate    = ev->date;
        datetime.dat_StrTime    = ev->time;
        DateToStr(&datetime);
}

/*
 *              CheckSegTracker()
 *
 *              Checks to see if SegTracker is loaded and sets the SegTracker
 *              global variable accordingly. Should be called every now and
 *              again to keep us up to date (e.g. whenever a new window is
 *              opened).
 */
void CheckSegTracker(void)
{
        SegTrackerActive = (FindSemaphore("SegTracker") != NULL);
}

/*
 *              SetEventSegmentStr(event)
 *
 *              This function converts the calladdr in the passed event into a segment
 *              name and hunk/offset pair if SegTracker is loaded, or into an invalid
 *              string if SegTracker is not loaded.
 *
 *              See the Enforcer/Segtracker documentation (from Michael Sinz) for
 *              more info on how this works.
 *
 *              Note that if the passed-in event->segname is NULL, then it will
 *              be reset to an "unknown module" string and no attempt to locate
 *              the module will be made. It is thus imperative that the _caller_
 *              only calls this function once for each event. The easiest way
 *              to ensure this is to use the EFLG_DONESEG flag in the ev->flags
 *              register to track whether the call has been made or not.
 *
 *              New: we now detect ROM addresses and search the resident module
 *                       list ourselves looking for the module that contains it.
 */
void SetEventSegmentStr(Event *ev)
{
        typedef char (*__asm SegTrack(reg_a0 ULONG,reg_a1 ULONG *,reg_a2 ULONG *));

        static struct {
                Semaphore       seg_Semaphore;
                SegTrack        *seg_Find;
        } *segsem;

        ULONG hunk;
        ULONG offset;
        char *segname = NULL;

        ev->hunk = INVALID_HUNK;
        if (ev->segname == NULL) {
                ev->segname     = "Unknown module";
                return;
        }

        Forbid();
        if (!segsem)
                segsem = (void *)FindSemaphore("SegTracker");

        if (segsem)
                segname = segsem->seg_Find(ev->calladdr, &hunk, &offset);

        if (segname) {
                strncpy(ev->segname, segname, MAX_SEGTRACKER_LEN);
                ev->segname[MAX_SEGTRACKER_LEN-1] = 0;
                ev->hunk   = hunk;
                ev->offset = offset;
        } else {
                if (ev->calladdr >= RomStart && ev->calladdr <= RomEnd) {
                        /*
                         *              Let's search the resident list for it ourselves.
                         *              and see if we can't do any better.
                         */
                        struct Resident *res;
                        ULONG *pres = (ULONG *)SysBase->ResModules;
                        UBYTE *p;

                        while (*pres) {
                                if (*pres & 0x80000000) {
                                        pres = (ULONG *)(*pres & 0x7FFFFFFF);
                                        continue;
                                }
                                res = (struct Resident *)*pres++;
                                if (ev->calladdr >= (ULONG)res
                                    && ev->calladdr <= (ULONG)res->rt_EndSkip) {
                                        ev->hunk    = 0;
                                        ev->offset  = ev->calladdr - (ULONG)res;
                                        strcpy(ev->segname, "ROM: ");
                                        strncat(ev->segname + 5, res->rt_IdString,
                                                                                     MAX_SEGTRACKER_LEN - 6);
                                        ev->segname[MAX_SEGTRACKER_LEN-1] = 0;
                                        /*
                                         *              Now filter out any remaining carriage returns
                                         *              and linefeeds, since they look ugly when printed
                                         *              We also convert any open brackets into '\0's
                                         *              since these are just dates which we don't care
                                         *              about -- it looks neater to just display the
                                         *              module name and version.
                                         */
                                        for (p = ev->segname; *p; p++) {
                                                if (*p < ' ')
                                                        *p = ' ';
                                                if (*p == '(')
                                                        *p = '\0';
                                        }
                                        break;
                                }
                        }
                }
                if (ev->hunk == INVALID_HUNK)
                        ev->segname = MSG(MSG_SEG_MODULE_NOT_FOUND);
        }
        Permit();
}

/*
 *              FormatEvent(eventformat, event, outputstr, start, end)
 *
 *              Formats the specified event according to the passed in format
 *              array. Only that portion of the fully formatted event which
 *              lies from the 'start' to 'end' characters is produced, and
 *              output always starts at the beginning of the outputstr.
 *
 *              For example, a start of 4 and end of 8 will produce a formatted
 *              string of exactly 5 characters.
 *
 *              Each entry in the eventformat array is decoded, and text which
 *              corresponds to the entry type is copied to the output string. If
 *              the text is longer than the entry width setting, it is truncated;
 *              if shorter, it is padded with spaces.
 *
 *              Each event is separated by one space character.
 *
 *              If the event pointer is NULL, then the title headings are returned
 *              instead of the event contents.
 */
void FormatEvent(EventFormat *eventformat, Event *event,
                                 char *outstr, int start, int end)
{
        static char hexbuf8[]  = "00000000";
        static char hexbuf13[] = "0000:00000000";
        static char countbuf[] = "             ";

        EventFormat *ef = eventformat;
        int savechar = 0;
        char *savep;
        char *p;
        int col = 0;

        end++;          /* Make exclusive rather than inclusive */

        while (ef->type != EF_END && col < end) {
                int width = ef->width;

                /*
                 *              First skip over any entries that fall before desired section
                 */
                if ((col + width) < start) {
                        col += width + 1;
                        if (col > start)
                                *outstr++ = ' ';        /* Add leading space */
                        ef++;
                        continue;
                }

                /*
                 *              Now parse our current entry
                 */
                if (event) {
                        switch (ef->type) {

                        case EF_PROCNAME:       p = event->procname;                                    break;
                        case EF_ACTION:         p = event->action;                                              break;
                        case EF_OPTIONS:        p = event->options;                                             break;
                        case EF_RESULT:         p = event->result;                                              break;
                        case EF_CALLADDR:
                                p = ltoap(hexbuf8, event->calladdr, 8);
                                if (*p == ' ')
                                        p++;
                                break;

                        case EF_PROCID:
                                p = ltoap(hexbuf8, event->processid,8);
                                if (*p == ' ')
                                        p++;
                                break;

                        case EF_FILENAME:
                        {
                                /*
                                 *              This is a little trickier since we allow the user
                                 *              to view it left-aligned or right-aligned. For
                                 *              right-aligned, we temporarily replace the first char
                                 *              with a « to indicate there are additional characters
                                 *              to the left, then restore it after we've copied the
                                 *              string data later on. This is much quicker (and
                                 *              safer) than copying the entire string to a temporary
                                 *              just so we can patch a single character of it.
                                 */
                                int len;

                                p = event->filename;
                                if (RightAligned) {
                                        len = strlen(p);
                                        if (len > width) {
                                                p += len - width;
                                                if (width > 1) {
                                                        savep    = p;
                                                        savechar = *p;
                                                        *p       = '«';
                                                }
                                        }
                                }
                                break;
                        }

                        case EF_DATE:
                                if ((event->flags & EFLG_DONEDATE) == 0) {
                                        SetEventDateStr(event);
                                        event->flags |= EFLG_DONEDATE;
                                }
                                p = event->date;
                                break;

                        case EF_TIME:
                                if ((event->flags & EFLG_DONEDATE) == 0) {
                                        SetEventDateStr(event);
                                        event->flags |= EFLG_DONEDATE;
                                }
                                p = event->time;
                                break;

                        case EF_SEGNAME:
                                if ((event->flags & EFLG_DONESEG) == 0) {
                                        SetEventSegmentStr(event);
                                        event->flags |= EFLG_DONESEG;
                                }
                                p = event->segname;
                                break;

                        case EF_COUNT:
                                /*
                                 *              We subtract BaseSeq from the event number when
                                 *              displaying it so that we have a convenient way of
                                 *              making the event number appear to return to 1,
                                 *              while internally still ensuring that the event
                                 *              number is ALWAYS monotonically increasing, even
                                 *              across buffer clears and the like.
                                 */
                                mysprintf(countbuf, "%ld", event->seqnum - BaseSeq);
                                p = countbuf;
                                break;

                        case EF_HUNKOFFSET:
                                if ((event->flags & EFLG_DONESEG) == 0) {
                                        SetEventSegmentStr(event);
                                        event->flags |= EFLG_DONESEG;
                                }
                                if (event->hunk == INVALID_HUNK) {
                                        /*
                                         *              If we couldn't locate the module address,
                                         *              then don't bother printing the hunk/offset
                                         *              info since it will only be zero anyway.
                                         */
                                        p = ""; /* Couldn't find module so don't print hunk addr */
                                } else {
                                        /*
                                         *              Only output 6 digits of offset and 2 digits
                                         *              of hunk, but arrange that we can handle a
                                         *              full 8 / 4 combination if necessary
                                         */
                                        p = ltoa(hexbuf13+7, event->offset, 6);
                                        *--p = ':';
                                        p = ltoap(p-2, event->hunk, 2);
                                }
                                break;

                        default:
                                p = "<Invalid>";
                                break;
                        }
                } else {
                        /*
                         *              Format column headings instead
                         */
                        p = MSG(ef->titlemsgid);
                }

                /*
                 *              Okay, so now we have p pointing to our string. We now need to
                 *              copy a sufficient number of characters into the string to
                 *              fulfill our need.
                 */
                if (col < start) {
                        /*
                         *              Our first special case is where we have to skip over some of
                         *              the early characters, i.e. chars to left are clipped.
                         */
                        while (col < start && *p) {
                                col++;
                                width--;
                                p++;
                        }
                        if (col < start) {
                                /*
                                 *              Ran out of characters in our string, so pretend we
                                 *              have a null string and let the following code pad
                                 *              the remaining width with spaces.
                                 */
                                width -= (start - col);
                                col    = start;
                        }
                }

                /*
                 *              Now copy characters intact into string
                 */
                if ((col + width) > end) {
                        /*
                         *              This field needs to be clipped. We do this quite simply
                         *              by adjusting the width to be the remaining number of
                         *              chars in the string.
                         */
                        width = end - col;
                }

                /*
                 *              Now copy width chars from our string to the output buffer,
                 *              padding with spaces as necessary.
                 */
                while (width && *p) {
                        *outstr++ = *p++;
                        width--;
                        col++;
                }
                if (width) {
                        memset(outstr, ' ', width);
                        outstr += width;
                        col    += width;
                }
                *outstr++ = ' ';                        /* Space in between output fields       */
                col++;                                          /* onto next column                                     */
                ef++;                                           /* Onto the next format field           */
                /*
                 *              Finally, restore the character we replaced with « earlier
                 *              (if any)
                 */
                if (savechar) {
                        *savep   = savechar;
                        savechar = 0;
                }
        }

        /*
         *              Okay, we've generated the string as requested. Now check to see if
         *              we need to pad the remainder of the string with spaces (i.e. the
         *              number of fields listed wasn't as wide as the whole window).
         *
         *              Ideally, we will never be passed in a start/end pair wider than
         *              the width of the formatted output, but you never know...
         */
        if (col <= end) {
                memset(outstr, ' ', end-col+1);
                outstr += end-col+1;
        }
        outstr[-1] = '\0';              /* Remove final terminating space */
}

/*
 *              UnderlineTitles(eventformat, outstr, underchar)
 *
 *              This function creates a string of underlines which matches the
 *              event headings in the passed event format, such that if the
 *              headings were printed out in full, the underline string would
 *              line up properly. underchar is the char used for underlining.
 *
 *              The reason we can't just take the obvious approach and generate
 *              a title string, then convert all non-space chars to minus, is
 *              because we can have titles like "Process Name" and we want the
 *              underline to be continuous, not break between process and name.
 *
 *              Returns a pointer to the newly formatted string (outstr). Outstr
 *              must have room to store the entire width, as defined by the
 *              event format.
 */
char *UnderlineTitles(EventFormat *ef, char *outstr, char underchar)
{
        char *p = outstr;

        while (ef->type != EF_END) {
                int width    = ef->width;
                int titleid  = ef->titlemsgid;
                int titlelen = strlen(MSG(titleid));

                if (titlelen > width)
                        titlelen = width;

                memset(p, underchar, titlelen);
                p     += titlelen;
                width -= titlelen-1;            /* Include an extra space between cols */
                if (width) {
                        memset(p, ' ', width);
                        p += width;
                }
                ef++;
        }
        *--p = 0;                                               /* Replace final space with a null */
        return (outstr);
}

#if TESTING
/*
 *              TestEventFields()
 *
 *              Simple test function to check out our format conversion routines
 */
void TestEventFields(void)
{
        static char inline[200];
        static char formatstr[200];
        static EventFormat evformat[50];

        while (gets(inline) != NULL) {
                int len;

                if (*inline == 'q')
                        break;

                len = ParseFormatString(inline, evformat, 50);
                BuildFormatString(evformat, formatstr, 200);

                printf("Converted string: >>%s<<, length = %d\n", formatstr, len);
        }
}

/*
 *              TestFormat
 *
 *              Test our text formatting routines
 */
void TestFormat()
{
        static char inline[200];
        static char formatstr[200];
        static EventFormat evformat[50];
        static char outstr[500];
        Event *ev;
        int showtitles = 0;

        for (;;) {
                int len;

                printf("Enter a format string (q to quit): ");
                gets(inline);
                if (*inline == 'q')
                        return;

                len = ParseFormatString(inline, evformat, 50);
                BuildFormatString(evformat, formatstr, 200);
                printf("Actual string is >> %s << (%d chars)\n", formatstr, len);

                /*
                 *              Get ourselves a new event
                 */
                ev = GetNewEvent(0);
                if (!ev) {
                        printf("Uhoh! Couldn't allocate event!\n");
                        continue;
                }
                ev->procname  = "[ 1] SAS/C compiler";
                ev->filename  = "s:startup-sequence";
                ev->action    = "Open";
                ev->result    = "Okay";
                ev->calladdr  = 0x12345678;
                ev->processid = 0xDEADBEEF;
                DateStamp(&ev->datestamp);

                for (;;) {
                        int start, end;

                        printf("Now enter start,end values (-1 to quit, s to toggle): ");
                        gets(inline);
                        if (*inline == 'q')
                                return;
                        if (*inline == 's') {
                                showtitles = !showtitles;
                                printf("Now printing %s\n", showtitles ? "titles" : "contents");
                                continue;
                        }
                        sscanf(inline, "%d,%d", &start, &end);
                        if (start == -1)
                                break;

                        if (showtitles)
                                FormatEvent(evformat, NULL, outstr, start, end);
                        else
                                FormatEvent(evformat, ev, outstr, start, end);
                        printf("|%s|\n", outstr);
                }
        }
}

/*
 *              TestEvents
 *
 *              Creates some random events, then prints them out
 */
void TestEvents(void)
{
        int howmany = 0;
        int i;

        printf("Generate how many events? ");
        scanf("%d", &howmany);

        for (i = 0; i < howmany; i++) {
                Event *ev = GetNewEvent((rand() & 63));

                printf("Event head: %8x   Event new: %8x\n",
                                (UBYTE *)HeadNode(&EventList) - BufferStart,
                                (UBYTE *)ev - BufferStart);
        }
}
#endif
