/*
 *
 *	DISCLAIMER:
 *
 *	This program is provided as a service to the programmer
 *	community to demonstrate one or more features of the Amiga
 *	personal computer.  These code samples may be freely used
 *	for commercial or noncommercial purposes.
 * 
 * 	Commodore Electronics, Ltd ("Commodore") makes no
 *	warranties, either expressed or implied, with respect
 *	to the program described herein, its quality, performance,
 *	merchantability, or fitness for any particular purpose.
 *	This program is provided "as is" and the entire risk
 *	as to its quality and performance is with the user.
 *	Should the program prove defective following its
 *	purchase, the user (and not the creator of the program,
 *	Commodore, their distributors or their retailers)
 *	assumes the entire cost of all necessary damages.  In 
 *	no event will Commodore be liable for direct, indirect,
 *	incidental or consequential damages resulting from any
 *	defect in the program even if it has been advised of the 
 *	possibility of such damages.  Some laws do not allow
 *	the exclusion or limitation of implied warranties or
 *	liabilities for incidental or consequential damages,
 *	so the above limitation or exclusion may not apply.
 *
 */


/*****************************************************************************
 *
 * FreeMap program creates a visual diagram of free memory
 *
 * =Robert J. Mical=
 * 9 January 1986
 * 
 * Copyright (C) 1986, COMMODORE-AMIGA, INC.
 * All Rights Reserved
 *
 ****************************************************************************/

#include "freemap.h"

#define SCREENWIDTH	320
#define SCREENHEIGHT	36

#define STARTLINE	6
#define STARTCOLUMN	(2 + 4)

#define LEFTOFFSET	6

#define MENU_INFO	2
#define MENU_REDISPLAY	1
#define MENU_QUIT	0

#define SCREENBYTEWIDTH	40
#define MAPBYTEWIDTH	32	/* must be a power of two */
#define MAX_BLOCK_COUNT	((512 * 1024) >> 6)

struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;
extern struct ExecBase *SysBase;

struct TextAttr SafeFont =
    {
    "topaz.font",
    8,
    0,
    0,
    };


struct IntuiText PleaseText = 
    {
    0, 1,
    JAM2,
    1, 1,
    NULL,
    "Please",
    NULL,
    };


struct MenuItem PleaseItem =
    {
    NULL,
    0, 0,
    88, 9,
    ITEMTEXT | ITEMENABLED | HIGHCOMP,
    0,
    (APTR)&PleaseText,
    NULL,
    0,
    NULL,
    NULL,
    };


struct Menu MapMenus[] =
    {
	{
	NULL,
	10, 0,
	48, 0,
	MENUENABLED,
	"Info",
	&PleaseItem,
	},

	{
	&MapMenus[0],
	58, 0,
	88, 0,
	MENUENABLED,
	"Redisplay",
	&PleaseItem,
	},

	{
	&MapMenus[1],
	146, 0,
	48, 0,
	MENUENABLED,
	"Quit",
	&PleaseItem,
	},

    };


struct NewScreen MapScreen =
    {
    0, 200 - SCREENHEIGHT,
    SCREENWIDTH, SCREENHEIGHT, 1,
    0, 1,
    NULL,
    CUSTOMSCREEN,
    &SafeFont,
    NULL,
    NULL,
    NULL,
    };


struct NewWindow MapWindow =
    {
    LEFTOFFSET, 0,
    (MAPBYTEWIDTH << 3) + 4, SCREENHEIGHT,
    -1, -1,
    MENUPICK | MENUVERIFY,
    SMART_REFRESH | BACKDROP | BORDERLESS | NOCAREREFRESH,
    NULL, 
    NULL, 
    NULL, 
    NULL, 
    NULL, 
    0, 0, 0, 0,
    CUSTOMSCREEN,
    };
    
    
struct Screen *MyScreen;
struct Window *MyWindow;
SHORT BitsAvailable;
SHORT BlockCount;
BYTE BitsValue;
SHORT WidthIndex;
UBYTE *MemBlock;
UBYTE *MapPointer;

ShiftMask[8] =
    {
    0xFF,
    0X01,
    0x03,
    0x07,
    0x0F,
    0x1F,
    0x3F,
    0x7F,
    };


RestartWindow()
/* This routine clears the window and then draws the border */
{
    SetRast(MyWindow->RPort, 0);
    Move(MyWindow->RPort, 0, 0);
    Draw(MyWindow->RPort, 0, SCREENHEIGHT - 1);
    Draw(MyWindow->RPort, (MAPBYTEWIDTH << 3)+4-1, SCREENHEIGHT - 1);
    Draw(MyWindow->RPort, (MAPBYTEWIDTH << 3) + 4 - 1, 0);
    Draw(MyWindow->RPort, 0, 0);
}


BumpMapPointer()
/* This routine bumps the MapPointer variable, which variable contains the
 * pointer to the byte in the Screen's BitMap which is to receive the
 * next FreeMap bits.
 */
{
    WidthIndex++;
    if (WidthIndex == MAPBYTEWIDTH)
	{
	/* We're at the right edge of the map window, so skip ahead
	 * to the left edge of the next line
	 */
	WidthIndex = 0;
	MapPointer += (SCREENBYTEWIDTH - MAPBYTEWIDTH + 1);
	}
    else MapPointer++;
}


ShiftInBits(bitcount)
SHORT bitcount;
/* This routine "shifts" the next set of bits into the memory map */
{
    SHORT shift, bytecount;

    if (BitsAvailable & 0x7)
	{
	/* There's less than a whole byte available, so we must start 
	 * by shifting.  Get the shift amount, which will be either all
	 * of the bits that are available, or the block count if it's less
	 * than the count of the available bits
	 */
	if (bitcount > BitsAvailable) shift = BitsAvailable;
	else shift = bitcount;

	/* Shift the current contents of this byte of the map 
	 * out of the way of our new bits.
	 */
	*MapPointer <<= shift;
	
	/* The above shift shifts zero bits in from the right.
	 * If the current BitsValue is clear, leave them zero, else set our
	 * new bits.
	 */
	if (BitsValue) *MapPointer |= ShiftMask[shift];
	
	/* Reduce the block count by the number of bits we just arranged */
	bitcount -= shift;
	
	/* if we've used up this memory map byte, advance to the next one */
	if ((BitsAvailable -= shift) == 0)
	    {
	    BitsAvailable = 8;
	    BumpMapPointer();
	    }

	}

    /* Now that we've taken care of any partially completed Map bytes,
     * let's check for any whole bytes that we can set (speed!)
     */
    bytecount = bitcount >> 3;
    while (bytecount)
	{
	*MapPointer = BitsValue;
	BumpMapPointer();
	bytecount--;
	}

    /* Finally, do any remaining bitcount bits to be set */
    if (bitcount &= 0x7)
	{
	*MapPointer = BitsValue;	/* Cheating, in a sense, but FAST! */
	BitsAvailable = 8 - bitcount;
	}
}


SHORT FindFreeBlock()
/* This routine feels through the memory-free list, looking for a block
 * of free memory that is big enough to qualify as being a block.
 * A block starts on 64-byte boundaries and is 64-bytes wide.  For a
 * block to be considered "free" all of the 64 bytes must be free
 */
{
    SHORT firstwholeblock, nextrealblock;

    FOREVER
	{
	if (MemBlock == 0) return(0);
	firstwholeblock = (LONG)(MemBlock + 63) >> 6;
	nextrealblock = (LONG)( MemBlock 
		+ ((struct MemChunk *)MemBlock)->mc_Bytes ) >> 6;

	MemBlock = (UBYTE *)((struct MemChunk *)MemBlock)->mc_Next;

	if (firstwholeblock < nextrealblock)
	    {
	    BlockCount = nextrealblock - firstwholeblock;
	    return(firstwholeblock);
	    }
	}
}



SetTimer(sec, micro, timermsg)
ULONG sec, micro;
struct IOStdReq *timermsg;
/* This routine simply sets the timer to interrupt us after secs.micros */
{
    timermsg->io_Command = TR_ADDREQUEST;    /* add a new timer request */
    timermsg->io_Actual = sec;    /* seconds */
    timermsg->io_Length = micro;    /* microseconds */
    SendIO(timermsg);	/* post a request to the timer */
}



main()
{
    struct MsgPort *timerport;
    struct IOStdReq *timermsg;
    struct MemHeader *MemHeader;
    SHORT NextFreeBlock, NextTakenBlock;
    ULONG wakeupbits, timerbit, windowbit;
    struct IntuiMessage *message;
    ULONG class;
    USHORT code;
    BOOL Redisplay;


    if ((IntuitionBase = (struct IntuitionBase *)OpenLibrary(
	    "intuition.library", 0)) == 0)
	{
	printf("NO LIBRARY");
	goto EXITING;
	}

    if ((GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0)) == 0)
	{
	printf("NO LIBRARY");
	goto EXITING;
	}

    if ((MyScreen = (struct Screen *)OpenScreen(&MapScreen)) == NULL)
	{
	printf("NO SCREEN");
	goto EXITING;
	}

    ShowTitle(MyScreen, FALSE);
    SetRGB4(&MyScreen->ViewPort, 0, 15, 12, 8);
    SetRGB4(&MyScreen->ViewPort, 1, 8, 0, 0);

    MapWindow.Screen = MyScreen;	
    if ((MyWindow = (struct Window *)OpenWindow(&MapWindow)) == NULL)
	{
	printf("NO WINDOW");
	goto EXITING;
	}
    SetMenuStrip(MyWindow, &MapMenus[2]);
    SetFont(MyWindow->RPort, OpenFont(&SafeFont));

    Move(&MyScreen->RastPort, (MAPBYTEWIDTH << 3) + 8 + 4, 18);
    Text(&MyScreen->RastPort, "512K", 4);
    Move(&MyScreen->RastPort, (MAPBYTEWIDTH << 3) + 8 + 4, 26);
    Text(&MyScreen->RastPort, "MEMORY", 6);
    Move(&MyScreen->RastPort, (MAPBYTEWIDTH << 3) + 8 + 4, 34);
    Text(&MyScreen->RastPort, "MAP", 3);
    RestartWindow();

    timerport = (struct MsgPort *)CreatePort(0, 0);
    if (timerport == 0) goto EXITING;

    timermsg = (struct IOStdReq *)CreateStdIO(timerport);
    if (timermsg == 0) goto EXITING;

    if (OpenDevice(TIMERNAME, UNIT_VBLANK, timermsg, 0) != 0)
	goto EXITING;
    
    timerbit = 1 << timerport->mp_SigBit;
    windowbit = 1 << MyWindow->UserPort->mp_SigBit;

    SetTimer(2, 0, timermsg);

    Redisplay = TRUE;

    FOREVER
	{
	if (Redisplay)
	    {
	    /* This program draws the memory map information right into
	     * the BitMap of the Screen, rather than using the graphics 
	     * support library routines to draw into the Window. 
	     * There is nothing illegal or immoral about doing this, but
	     * is does require a thorough knowledge of how Intuition and
	     * the graphics library create and manipulate the display.
	     * In other words, this is *not* something that the novice 
	     * will want to do lightly, but the soon-to-be-expert may
	     * want to examine as an example of how to do meta-graphics
	     * in the Amiga window environment.
	     */
	    MapPointer = MyScreen->RastPort.BitMap->Planes[0];
	    MapPointer += (SCREENBYTEWIDTH * 2 + 1);

	    Forbid();

	    do
		MemHeader = (struct MemHeader *)SysBase->MemList.lh_Head;
	    while ((MemHeader->mh_Attributes & MEMF_CHIP) == 0);

	    MemBlock = (UBYTE *)MemHeader->mh_First;
	
	    BitsAvailable = 8;
	    WidthIndex = 0;
	    NextTakenBlock = 0;

	    do 
		{
		if (NextFreeBlock = FindFreeBlock())
		    {
		    BitsValue = 0;
		    ShiftInBits(NextFreeBlock - NextTakenBlock);
		    BitsValue = -1;
		    ShiftInBits(BlockCount);
		    NextTakenBlock = NextFreeBlock + BlockCount;
		    }
		else ShiftInBits(MAX_BLOCK_COUNT - NextTakenBlock);
		}
	    while (MemBlock);

	    if (NextTakenBlock < MAX_BLOCK_COUNT)
		{
		BitsValue = 0;
		ShiftInBits(MAX_BLOCK_COUNT - NextTakenBlock);
		}
	    Permit();
	    }

	wakeupbits = Wait(timerbit | windowbit);

	if (wakeupbits & timerbit)
	    {
	    GetMsg(timerport);	/* this does nothing more than "clear" */
	    SetTimer(2, 0, timermsg);
	    }

	if (wakeupbits & windowbit)
	    {
	    message = (struct IntuiMessage *)GetMsg(MyWindow->UserPort);
	    class = message->Class;
	    code = message->Code;
	    ReplyMsg(message);
	    switch (class)
		{
		case MENUVERIFY:
		    Redisplay = FALSE;
		    break;
		case MENUPICK:
		    switch (MENUNUM(code))
			{
			case MENU_INFO:
			    RestartWindow();
			    Move(MyWindow->RPort, 
				    STARTCOLUMN, STARTLINE);
			    Text(MyWindow->RPort,
				    "Each pixel represents 64 bytes.", 31);
			    Move(MyWindow->RPort, 
				    STARTCOLUMN + 20, STARTLINE + 9);
			    Text(MyWindow->RPort,
				    "If all bytes are free, the", 26);
			    Move(MyWindow->RPort, 
				    STARTCOLUMN, STARTLINE + 18);
			    Text(MyWindow->RPort,
				    "pixel is dark, else it's light.", 31);
			    Move(MyWindow->RPort, 
				    STARTCOLUMN + 20, STARTLINE + 28);
			    Text(MyWindow->RPort,
				    "This trinket by  =RJMical=", 26);
			    break;
			case MENU_REDISPLAY:
			    RestartWindow();
			case NOMENU:
			    Redisplay = TRUE;
			    break;
			case MENU_QUIT:
			    goto EXITING;
			}
		}
	    }
	}

EXITING:
    if (MyWindow) CloseWindow(MyWindow);
    if (MyScreen) CloseScreen(MyScreen);
    if (timerport)
	{
	Wait(timerbit);
	GetMsg(timerport);

	CloseDevice(timermsg);
	DeleteStdIO(timermsg);
	DeletePort(timerport);
	}
}


