/*
 * This is an example of adding a gadget to the clock area
 */

#define __USE_SYSBASE
long    __oslibversion = 37L;

#include <exec/types.h>
#include <exec/exec.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <graphics/gfx.h>
#include <devices/timer.h>

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/dos.h>
#include <proto/graphics.h>

#include "/_StartMenu/messages.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define AREA_SIZE 200

VOID ShutDown(STRPTR, UBYTE);
VOID SendStartMsg(START_MSG *, APTR, UBYTE);
BOOL StartTimer(VOID);
VOID CloseTimer(VOID);
VOID DrawMeter(VOID);
VOID SetUpRP(VOID);
LONG OpenWin(VOID);
VOID CloseWin(VOID);
VOID DoText(VOID);
VOID str2l(STRPTR, ULONG);

UBYTE ver[] = {"$VER: MemMeter v1.0 "__AMIGADATE__};

struct RastPort     rp;         /* A RastPort for drawing */
PLANEPTR            t_plane;    /* For the TmpRas */
NEW_BITMAP          nbm;        /* A BitMap to attatch to the RastPort */
START_MSG           smsg;       /* The message to send */
START_MENU_GADGET   gad;        /* The gadget */
struct MsgPort     *my_port;    /* A port for StartMenu to send messages to */

struct Window *win;
struct TextFont *the_font;
struct Rectangle rects[3];

struct timerequest *TimerIO;
struct MsgPort     *TimerPort;
struct Library     *TimerBase;

ULONG mem_total, mem_fast, mem_chip;

UWORD total_min, total_max,
      fast_min, fast_max,
      chip_min, chip_max;

main()
{
    struct Message *m;
    struct MsgPort *start_port;
    START_MSG      *rep_smsg;
    GADGET_STATS    gs;
    ULONG t_sig, p_sig, wsig = 0, sig, cnt = 0;
    BOOL done = FALSE;
    UWORD space;

    /* Set up fixed variables */
    mem_total = AvailMem(MEMF_TOTAL);
    mem_fast  = AvailMem(MEMF_FAST|MEMF_TOTAL);
    mem_chip  = AvailMem(MEMF_CHIP|MEMF_TOTAL);

    /* Set up the timer */
    if (!(StartTimer()))
        ShutDown("Could not get timer device", 5);

    /* Try to find StartMenu */
    while (cnt++ < 10) {
        if (start_port = FindPort(STARTMENU_PORT_NAME)) break;
        else Delay(250);
    }
    if (!start_port)
        ShutDown("Could not find StartMenu.", 5);

    /* Create a port */
    if (!(my_port = CreatePort(0, 0)))
        ShutDown("Could not create a message port.", 5);

    /* Prepare the Message */
    smsg.sm_Msg.mn_Node.ln_Type = NT_MESSAGE;
    smsg.sm_Msg.mn_Length = START_MSG_SIZE;
    smsg.sm_Msg.mn_ReplyPort = my_port;

    /* Ask StartMenu to create a BitMap */
    SendStartMsg(&smsg, (APTR)(&nbm), CREATE_NEW_BITMAP);

    /* Check the BitMap */
    if (NULL == nbm.nbm_BitMap)
        ShutDown("Could not create BitMap.", 5);

    /* Set up some drawing variables */
    space = ((nbm.nbm_Size - 2) / 3) - 1;
    chip_min = 1;
    chip_max = chip_min + space;
    fast_min = chip_max + 1;
    fast_max = fast_min + space;
    total_min = fast_max + 1;
    total_max = fast_max + space;
    while (total_max < (nbm.nbm_Size - 1)) total_max++;

    /* Setup the RastPort for drawing */
    SetUpRP();

    /* Ask StartMenu to create a gadget */
    gad.smg_BitMap    = nbm.nbm_BitMap;
    gad.smg_Port      = my_port;
    gad.smg_PrivateBM = FALSE;
    SendStartMsg(&smsg, (APTR)(&gad), ADD_CLOCK_GADGET);

    /* Check the gadget */
    if (NULL == gad.smg_Gadget)
        ShutDown("Could not create gadget.", 5);

    DrawMeter();

    /* Everything ok. Start loop */
    t_sig = 1L << TimerPort->mp_SigBit;
    p_sig = 1L << my_port->mp_SigBit;
    while (!done) {
        sig = Wait(t_sig | p_sig | wsig);

        /* A message from StartMenu - copy and reply ASAP!!! */
        if (sig & p_sig) {
            while (rep_smsg = (START_MSG *)GetMsg(my_port)) {
                if (rep_smsg->sm_Command == GADGET_STATUS) {
                    /* An IDCMP message */
                    memcpy(&gs, rep_smsg->sm_Data, sizeof(GADGET_STATS));
                    ReplyMsg((struct Message *)rep_smsg);
                    if ((gs.gs_IMSG->Qualifier & IEQUALIFIER_LSHIFT) ||
                              (gs.gs_IMSG->Qualifier & IEQUALIFIER_RSHIFT))
                        done = TRUE;
                    else  {
                        if (win) {
                            CloseWin();
                            wsig = 0;
                        }
                        else wsig = OpenWin();
                    }
                }
                else {
                    /* Any other message is a quit message */
                    ReplyMsg((struct Message *)rep_smsg);
                    gad.smg_Gadget = NULL;  /* Set this so we don't free it */
                    done = TRUE;
                }
            }
        }
        else if (sig & t_sig) {
            while (m = GetMsg(TimerPort)) /* NULL */ ;

            TimerIO->tr_node.io_Command = TR_ADDREQUEST;
            TimerIO->tr_time.tv_secs = 10;
            TimerIO->tr_time.tv_micro = 0;
            SendIO((struct IORequest *) TimerIO);
            DrawMeter();
        }
        else if (sig & wsig) {
            CloseWin();
            wsig = 0;
        }
    }

    ShutDown(NULL, 0);
}

VOID ShutDown(STRPTR s, UBYTE rc)
{
    CloseTimer();

    if (win) CloseWin();

    /* Remove our gadget */
    if (gad.smg_Gadget) {
        RE_GADGET re;
        re.re_Gadget = gad.smg_Gadget;
        SendStartMsg(&smsg, (APTR)(&re), REM_CLOCK_GADGET);
    }

    if (my_port) DeletePort(my_port);

    if (t_plane) FreeRaster(t_plane, nbm.nbm_Size, nbm.nbm_Size);
    
    if (s) fprintf(stderr, "MemMeter: %s\n", s);

    exit(rc);
}

VOID SendStartMsg(START_MSG *msg, APTR data, UBYTE command)
{
    struct MsgPort *start_port;
    
    if (start_port = FindPort(STARTMENU_PORT_NAME)) {
        msg->sm_Command = command;
        msg->sm_Data    = data;
        PutMsg(start_port, (struct Message *)msg);
        WaitPort(my_port);           /* StartMenu replies to ALL messages!! */
        GetMsg(my_port);
    }
}

BOOL StartTimer(VOID)
{
    if (TimerPort = CreatePort(0,0)) {
        if (TimerIO = (struct timerequest *)CreateIORequest(TimerPort, sizeof(struct timerequest))) {
            if (!OpenDevice( "timer.device", UNIT_VBLANK, (struct IORequest *)TimerIO, NULL )) {
                TimerIO->tr_node.io_Command = TR_ADDREQUEST;
                TimerIO->tr_time.tv_secs    = 10;
                TimerIO->tr_time.tv_micro   = 0;
                SendIO((struct IORequest *)TimerIO);
                TimerBase = (struct Library *)TimerIO->tr_node.io_Device;
                return(TRUE);
            }
            DeleteIORequest((struct IORequest *)TimerIO);
            TimerIO = NULL;
        }
        DeletePort(TimerPort);
        TimerPort = NULL;
    }
    return(FALSE);
}

VOID CloseTimer(VOID)
{
    if (TimerIO) {
        if (TimerIO->tr_node.io_Command == TR_ADDREQUEST)
            AbortIO((struct IORequest *)TimerIO);
        CloseDevice((struct IORequest *)TimerIO);
        DeleteIORequest((struct IORequest *)TimerIO);
        TimerIO = NULL;
    }
    if (TimerPort) {
        DeletePort(TimerPort);
        TimerPort = NULL;
    }
}

VOID DrawMeter(VOID)
{
    ULONG free_chip, free_fast, free_total, chip_start, fast_start, total_start;
    FLOAT perc;
    RE_GADGET re;
    static ULONG old_chip = 0, old_fast = 0;

    free_chip = AvailMem(MEMF_CHIP);
    perc = 1.0 - ((FLOAT)free_chip / (FLOAT)mem_chip);
    chip_start = perc * (nbm.nbm_Size - 2);

    free_fast = AvailMem(MEMF_FAST);
    perc = 1.0 - ((FLOAT)free_fast / (FLOAT)mem_fast);
    fast_start = perc * (nbm.nbm_Size - 2);

    free_total = free_chip + free_fast;
    perc = 1.0 - ((FLOAT)free_total / (FLOAT)mem_total);
    total_start = perc * (nbm.nbm_Size - 2);

    /* Olny refresh if there's a change */
    if ((old_chip != chip_start) || (old_fast != fast_start)) {
        old_chip  = chip_start;
        old_fast  = fast_start;
        SetRast(&rp, 0);
        SetAPen(&rp, 1);
        RectFill(&rp, chip_min, chip_start, chip_max, nbm.nbm_Size - 1);
        SetAPen(&rp, 2);
        RectFill(&rp, fast_min, fast_start, fast_max, nbm.nbm_Size - 1);
        SetAPen(&rp, 3);
        RectFill(&rp, total_min, total_start, total_max, nbm.nbm_Size - 1);

        SetAPen(&rp, nbm.nbm_FrontPen);
        Move(&rp, 0, 0);
        Draw(&rp, nbm.nbm_Size - 1, 0);
        Draw(&rp, nbm.nbm_Size - 1, nbm.nbm_Size - 1);
        Draw(&rp, 0, nbm.nbm_Size - 1);
        Draw(&rp, 0, 0);

        re.re_Gadget = gad.smg_Gadget;
        SendStartMsg(&smsg, (APTR)(&re), REFRESH_CLOCK_GADGET);

        if (win) DoText();
    }
}

VOID SetUpRP(VOID)
{
    static struct AreaInfo areaInfo = {0};
    static WORD areaBuffer[AREA_SIZE];
    static struct TmpRas tmpRas;

    InitRastPort(&rp);
    rp.BitMap = nbm.nbm_BitMap;

    InitArea(&areaInfo, areaBuffer, (AREA_SIZE * 2) / 5);
    rp.AreaInfo = &areaInfo;

    if (!(t_plane = AllocRaster(nbm.nbm_Size, nbm.nbm_Size)))
        ShutDown("Could not allocate temp raster.", 5);
    InitTmpRas(&tmpRas, t_plane, nbm.nbm_Size * nbm.nbm_Size);
    rp.TmpRas = &tmpRas;
}

LONG OpenWin(VOID)
{
    WIN_INFO sm_win;
    struct RastPort *srp;
    struct Screen *screen;
    ULONG win_width, win_height, left_edge, ypos, x, max, box_size, l;
    UBYTE *words[] = {"Chip:", "Fast:", "Total:"};

    if (NULL != win) return(1L << win->UserPort->mp_SigBit);

    SendStartMsg(&smsg, (APTR)(&sm_win), GET_WINDOW_INFO);
    screen = sm_win.wi_Window->WScreen;
    srp = &(screen->RastPort);

    max = TextLength(srp, "Total: 999,999,999", 18);
    box_size = screen->Font->ta_YSize;
    
    win_width = max + box_size + (screen->BarVBorder * 2) + 25;
    win_height = (3 * (screen->Font->ta_YSize + 2)) + (screen->BarHBorder * 2);
    left_edge = (sm_win.wi_Window->LeftEdge + gad.smg_Gadget->LeftEdge + gad.smg_Gadget->Width) - win_width;
    
    if (win = OpenWindowTags(NULL,
        WA_Left,        left_edge,
        WA_Top,         sm_win.wi_Window->TopEdge - win_height,
        WA_Width,       win_width,
        WA_Height,      win_height,
        WA_IDCMP,       IDCMP_MOUSEBUTTONS,
        WA_RMBTrap,     TRUE,
        TAG_END)) {

        the_font = OpenFont(screen->Font);
        SetFont(win->RPort, the_font);

        ypos = 2;
        for (x = 0; x < 3; x++) {
            SetAPen(win->RPort, x + 1);
            RectFill(win->RPort, 10, ypos + 4, 10 + box_size, ypos + box_size + 2);
            SetAPen(win->RPort, 1);
            Move(win->RPort, 15 + box_size, ypos + the_font->tf_YSize);
            Text(win->RPort, words[x], strlen(words[x]));
            l = TextLength(win->RPort, words[x], strlen(words[x]));
            rects[x].MinX = 15 + box_size + 1 + l;
            rects[x].MinY = ypos;
            rects[x].MaxX = 15 + box_size + 1 + max;
            rects[x].MaxY = ypos + the_font->tf_YSize + 2;
            ypos += the_font->tf_YSize + 2;
        }
        DoText();
        return(1L << win->UserPort->mp_SigBit);
    }
    return(0);
}

VOID CloseWin(VOID)
{
    if (the_font) {
        CloseFont(the_font);
        the_font = NULL;
    }
    if (win) {
        CloseWindow(win);
        win = NULL;
    }
}

VOID DoText(VOID)
{
    static UBYTE str[3][32];
    int x, len, tlen;
    ULONG c, f;

    if (NULL == win) return;

    c = AvailMem(MEMF_CHIP);
    f = AvailMem(MEMF_FAST);
    str2l(str[0], c);
    str2l(str[1], f);
    str2l(str[2], c + f);

    for (x = 0; x < 3; x++) {
        SetAPen(win->RPort, 0);
        RectFill(win->RPort, rects[x].MinX, rects[x].MinY,
                             rects[x].MaxX, rects[x].MaxY);
        SetAPen(win->RPort, 1);
        len = strlen(str[x]);
        tlen = TextLength(win->RPort,str[x], len);
        Move(win->RPort, rects[x].MaxX - tlen, rects[x].MinY + the_font->tf_YSize);
        Text(win->RPort, str[x], len);
    }
}

VOID str2l(STRPTR s, ULONG l)
{
    UBYTE temp[32], *p;
    int len, x;

    sprintf(temp, "%ld", l);
    
    /* Reverse */
    len = strlen(temp) - 1;
    p = temp;
    for (x = len; x >= 0; x--)
        s[x] = *p++;

    p = temp;
    for (x = 0; x <= len; x++) {
        if (x && (0 == (x % 3))) *p++ = ',';
        *p++ = s[x];
    }
    *p = 0;

    /* Reverse */
    len = strlen(temp) - 1;
    p = temp;
    for (x = len; x >= 0; x--)
        s[x] = *p++;
    s[len + 1] = 0;
    
}
