#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <functions.h>

#include "MRTimer.h"

extern struct ExecBase *SysBase;

#define CHIP_WANTED_GADGET      1
#define FAST_WANTED_GADGET      2
#define TOTAL_WANTED_GADGET     3
#define CHIP_AVAIL_GADGET       4
#define FAST_AVAIL_GADGET       5
#define TOTAL_AVAIL_GADGET      6


#include "MemEater-pw.h"

/*  We represent our "held-in-reserve" blocks with the following structure. */

struct MemNode {  
    struct MemNode  *next;
    LONG            blockSize;
    /* ... remainder of allocated memory ... */
    };

/*  ReserveMem describes the entries in a given "reserve" list. */

struct MemSpecs {
    ULONG           memType;        /* MEMF_CHIP or MEMF_FAST */
    struct MemNode  *list;          /* list of nodes allocated */
    LONG            avail;          /* amount currently available */
    LONG            limit;          /* amount we want */
    LONG            maximum;
    };

long _stack = 4000;
long _priority = 1;
long _BackGroundIO;
char *_procname = "MemEater Process";

struct IntuitionBase    *IntuitionBase;
struct GfxBase          *GfxBase;

struct MemSpecs         chipSpecs;      /* chip memory in reserve */
struct MemSpecs         fastSpecs;      /* fast memory specifications */

LONG                    minBlockSize = sizeof(struct MemNode);
struct Window           *myWindow;
ULONG                   myWindowBit;

struct timerequest      *timer;
ULONG                   timerBit;

#ifndef min
#define min(a, b) ( (a) < (b) ? (a) : (b) )
#endif

/* Get the pointer to a gadget string, given a pointer to a gadget. */
#define GadgetString(g) ((struct StringInfo *) ((g)->SpecialInfo))->Buffer

void    DoGadget(struct Gadget *theGadget);
void    FreeSome(struct MemSpecs *spec, LONG newLimit);
void    GrabMem(struct MemSpecs *spec);
void    InitSpecs(struct MemSpecs *specs, ULONG memType);
LONG    MaxSize(LONG t);
void    ReleaseMem(struct MemSpecs *specs);
void    ResetStringInfo(struct StringInfo *s);
void    SetNumberGadget(struct Gadget *gadget, LONG value);
void    SetStringGadget(struct Gadget *gadget, 
                        struct Window *window, 
                        struct Requester *requester, 
                        const char *s);
void    Update(void);


main(int argc, char **argv)
{
    static char *noTimerMsg =       "MemEater: can't create timer!\n";
    static char *noWindowMsg =      "MemEater: can't open my window!\n";
    static char *whereIntuition =   "MemEater: where is intuition.library?\n";
    static char *whereGraphics =    "MemEater: where is graphics.library?\n";

    ULONG               bits;       /* returned by Wait() */
    LONG                class;
    struct Gadget       *gadget;
    struct IntuiMessage *msg;
    BOOL                quit = FALSE;
    struct RastPort     *rp;

    IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", 0L);
    if (IntuitionBase == NULL) {
        Write(Output(), whereIntuition, sizeof(whereIntuition));
        exit(1);
    }
    
    GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 0L);
    if (GfxBase == NULL) {
        Write(Output(), whereGraphics, sizeof(whereGraphics));
        exit(1);
    }

    timer = CreateTimer(1);             /* Use VBLANK timer. */
    if (! timer ) {
        Write(Output(), noTimerMsg, sizeof(noTimerMsg));
        exit(1);
    }

    myWindow = OpenWindow(&NewWindowStructure1);

    if (! myWindow) {
        Write(Output(), noWindowMsg, sizeof(noWindowMsg));
        exit(1);
    }
    rp = myWindow->RPort;

    PrintIText(rp, &IntuiTextList1, 0L, 0L);

    
    /* Get system maximums. We should only have to do this once. If the user pulls
     * an AddMem behind our backs, he'll have to restart us. T. S.
     */

    InitSpecs(&chipSpecs, MEMF_CHIP);
    InitSpecs(&fastSpecs, MEMF_FAST);

    SetNumberGadget(&chipWantedGadget, chipSpecs.limit / 1024L);
    SetNumberGadget(&fastWantedGadget, fastSpecs.limit / 1024L);
    SetNumberGadget(&totalWantedGadget, (chipSpecs.limit + fastSpecs.limit) / 1024L);

    myWindowBit = (1L << myWindow->UserPort->mp_SigBit);
    timerBit = (1L << timer->tr_node.io_Message.mn_ReplyPort->mp_SigBit);

    while (! quit ) {
        StartTimer(timer, 0L, 500000L);     /* Start 1/2 second timer. */ 
        bits = Wait(myWindowBit | timerBit);
        if (! bits & timerBit)
            StopTimer(timer);
        else 
            WaitIO(&timer->tr_node);
        
        chipSpecs.avail = AvailMem(MEMF_CHIP);
        fastSpecs.avail = AvailMem(MEMF_FAST);

        if (bits & myWindowBit) {
            while (msg  = (struct IntuiMessage *) GetMsg(myWindow->UserPort)) {
                class = msg->Class;
                gadget = (struct Gadget *) msg->IAddress;
        
                ReplyMsg( (struct Message *) msg);
                if (class == CLOSEWINDOW) {
                    quit = TRUE;
                    break;
                }

                if (class == GADGETUP) {
                    DoGadget(gadget);
                    continue;
                }
                
                if (class == WINDOWREFRESH) {
                    BeginRefresh(myWindow);
                    EndRefresh(myWindow, TRUE);
                }
            }
        }
        GrabMem(&chipSpecs);
        GrabMem(&fastSpecs);
        Update();
    }

    ReleaseMem(&chipSpecs);
    ReleaseMem(&fastSpecs);
    CloseWindow(myWindow);
    DeleteTimer(timer);
}

/*  FUNCTION
 *      DoGadget - process gadget event.
 *
 *  SYNOPSIS
 *      void DoGadget(struct Gadget *theGadget);
 *
 *  DESCRIPTION
 *      DoGadget is called whenever a gadget-related Intuition message is
 *      received. There are only two gadgets that will cause messages to be
 *      generated: CHIP_WANTED_GADGET and FAST_WANTED_GADGET. The value for the
 *      respective gadget is verified and the display is updated, if necessary.
 */

void
DoGadget(struct Gadget *theGadget)
{
    LONG               newValue;
    struct StringInfo   *si;
    struct MemSpecs     *specs = NULL;
    BOOL                updateTotal = FALSE;

    switch (theGadget->GadgetID) {

    case CHIP_WANTED_GADGET:
        specs = &chipSpecs;
        break;

    case FAST_WANTED_GADGET:
        specs = &fastSpecs;
        break;

    default:
        break;
    }

    if (specs) {
        si = (struct StringInfo *) theGadget->SpecialInfo;
        newValue = si->LongInt * 1024L;
        if (newValue > specs->maximum) {
            specs->limit = specs->maximum;
            SetNumberGadget(theGadget, specs->limit / 1024L);
        }
        else {
            /* Are we giving some back? */
            if (newValue > specs->limit) FreeSome(specs, newValue);
            specs->limit = newValue;
            updateTotal = TRUE;
        }
    }

    if (updateTotal) {
        SetNumberGadget(&totalWantedGadget, 
            (chipSpecs.limit + fastSpecs.limit) / 1024L);
    }
}

/*  FUNCTION
 *      FreeSome - free up some of our reserve memory.
 *
 *  SYNOPSIS
 *      void FreeSome(struct MemSpecs *specs, LONG newLimit);
 *
 *  DESCRIPTION
 *      FreeSome is called whenever the user ups the limit on a given class of
 *      memory (described by parameter <specs>). The <newLimit> parameter defines
 *      the new upper limit for the memory class.
 */

void
FreeSome(struct MemSpecs *specs, LONG newLimit)
{
    struct MemNode  *nextNode;

    while (specs->list && (newLimit > specs->limit)) {
        nextNode = specs->list->next;
        newLimit -= specs->list->blockSize;
        FreeMem(specs->list, specs->list->blockSize);
        specs->list = nextNode;
    }
}

/*  FUNCTION
 *      GrabMem - grab memory from the system.
 *
 *  SYNOPSIS
 *      GrabMem is called once each timer interval (or upon receipt of an
 *      Intuition message) for each class of memory being maintained. If the
 *      current amount of memory available for the class described by 
 *      <specs> exceeds the desired limit, memory is "consumed", preserving
 *      the limit.
 */

void
GrabMem(struct MemSpecs *specs)
{
    LONG            blockSize;
    LONG            diff;
    LONG            largest;
    struct MemNode  *node;

    while ((diff = (specs->avail - specs->limit)) > minBlockSize) {

        /* What's the largest block available? */
        largest = AvailMem(specs->memType | MEMF_LARGEST);
    
        /* Figure the size that we need. */
        blockSize = min(diff, largest);

        /* If it's less than the size of a MemNode, just forget it. */
        if (blockSize < minBlockSize)
            break;

        node = AllocMem(blockSize, specs->memType); /* Grab the block. */
        if (! node) break;                          /* Something went very wrong. */

        node->blockSize = blockSize;
        node->next = specs->list;
        specs->list = node;
        specs->avail = AvailMem(specs->memType);    /* Reassess available memory. */
    }
}

/*  FUNCTION
 *      InitSpecs - initialize a memory specifications packet.
 *
 *  SYNOPSIS
 *      void InitSpecs(struct MemSpecs *specs, ULONG memType);
 *
 *  DESCRIPTION
 *      InitSpecs initializes a memory specifications packet with information
 *      pertinent to the class of memory defined by <memType>.
 */

void
InitSpecs(struct MemSpecs *specs, ULONG memType)
{
    specs->memType = memType;
    specs->avail = AvailMem(memType);
    Forbid();
    specs->maximum = MaxSize(memType);
    Permit();
    specs->limit = specs->maximum;
    specs->list = NULL;
}

/*  FUNCTION
 *      MaxSize - determine maximum memory available for a given type
 *
 *  SYNOPSIS
 *      LONG MaxSize(LONG t);
 *
 *  DESCRIPTION
 *      MaxSize determines the maximum amount of memory available for a given
 *      class (MEMF_CHIP or MEMF_FAST) as defined by <t>. MaxSize must be bracketed
 *	    by calls to Forbid/Permit since system structures are accessed.
 *
 *  CREDITS
 *      This code was lifted from the "avail" program.
 */

LONG
MaxSize (LONG t)
{
    /* THIS CODE MUST ALWAYS BE CALLED WHILE FORBIDDEN */
    LONG               size = 0;
    struct MemHeader    *mem;
    struct ExecBase     *eb = SysBase;

    for (mem = (struct MemHeader *)eb->MemList.lh_Head;
	     mem->mh_Node.ln_Succ;
	     mem = (struct MemHeader *)mem->mh_Node.ln_Succ) {
        if (mem -> mh_Attributes & t) {
	        size += ((LONG) mem -> mh_Upper - (LONG) mem -> mh_Lower);
	    }
    }

    return size;
}

/*  FUNCTION
 *      ReleaseMem - release all memory for a given class.
 *
 *  SYNOPSIS
 *      void ReleaseMem(struct MemSpecs *specs);
 *
 *  DESCRIPTION
 *      ReleaseMem is called at program termination to free up all memory consumed
 *      by this program.
 */

void
ReleaseMem(struct MemSpecs *specs)
{
    specs->limit = 0;
    FreeSome(specs, 0x7fffffff);
}


/*  FUNCTION
        ResetStringInfo - reset information in a StringInfo structure.

    SYNOPSIS
        void ResetStringInfo(struct StringInfo *s);

    DESCRIPTION
        ResetStringInfo resets certain parameters in the StringInfo
        structure pointed to by <s>, including:

            UndoBuffer
            DispPos
            UndoPos
            NumChars
*/
void
ResetStringInfo(struct StringInfo *s)
{
    *(s->UndoBuffer) = '\0';
    s->BufferPos = 0;
    s->DispPos = 0;
    s->UndoPos = 0;
    s->NumChars = strlen((char *) s->Buffer);
}

/*  FUNCTION
 *      SetNumberGadget - update the contents of a numerical gadget.
 *
 *  SYNOPSIS
 *      void SetNumberGadget(struct Gadget *gadget, LONG value);
 *
 *  DESCRIPTION
 *      SetNumberGadget provides an economical way to update the contents of
 *      number (string) gadgets without requiring runtime library support. It
 *      calls SetStringGadget to actually change the gadget's contents.
 */

void
SetNumberGadget(struct Gadget *gadget, LONG value)
{
    int     digitCount = 0;
    char    digits[12];
    char    numString[12], *ns;

    do {
        digits[digitCount++] = (value % 10) + '0';
        value /= 10;
    } while (value > 0);

    ns = numString;
    while (--digitCount >= 0) {
        *ns++ = digits[digitCount];
    }

    *ns = '\0';                         /* Add terminating null. */
    SetStringGadget(gadget, myWindow, NULL, numString);
}

/*  FUNCTION
        SetStringGadget - set the value of a string gadget.

    SYNOPSIS
        void SetStringGadget(gadget, window, requester, s)
             struct Gadget      *gadget;
             struct Window      *window;
             struct Requester   *requester;
             const char         *s;

    DESCRIPTION
        SetStringGadget sets the string value of a <gadget>, which
        belongs to <window>, to the character string pointed to by <s>.
        It does this in a "polite" way, first removing the gadget from
        the list, modifying it, then adding it back and refreshing the
        gadget list. 

        If the <gadget> belongs to a requester, <requester> must contain
        the address of that requester.  Otherwise, it must be NULL.

        If <window> is NULL, the gadget is modified without attempting
        to remove/restore it to/from a window gadget list.
*/
void
SetStringGadget(gadget, window, requester, s)
    struct Gadget       *gadget;
    struct Window       *window;
    struct Requester    *requester;
    const char          *s;
{
    char    *gs;                        /* pointer to gadget's text */
    int     max;
    ULONG   position;
    struct StringInfo *sInfo;
    char    *s1;

    /* Make sure we are trying to modify a string gadget. If we aren't,
     * just don't do anything.
     */
    if (gadget->GadgetType & STRGADGET) {
        gs = (char *) GadgetString(gadget);
        sInfo = (struct StringInfo *) (gadget->SpecialInfo);
        max = sInfo->MaxChars;
        if (window)
            position = RemoveGList(window, gadget, 1L);
        strncpy(gs, s, max);            /* Don't exceed gadget capacity. */
        if (s1 = strchr(gs, '\n'))      /* Eliminate newline characters. */
            *s1 = '\0';
        ResetStringInfo(sInfo);
        if (window) {
            AddGList(window, gadget, position, 1L, requester);
            RefreshGList(gadget, window, requester, 1L);
        }
    }
}

/*  FUNCTION
 *      Update - update the "Current" information in our display window.
 *
 *  SYNOPSIS
 *      void Update(void);
 *
 *  DESCRIPTION
 *      Update is called periodically to refresh the display window's "Current"
 *      information.
 */

void
Update(void)
{
    SetNumberGadget(&chipAvailGadget, chipSpecs.avail / 1024L);
    SetNumberGadget(&fastAvailGadget, fastSpecs.avail / 1024L);
    SetNumberGadget(&totalAvailGadget, (chipSpecs.avail + fastSpecs.avail) / 1024L);    
}
