/**
 * Scout - The Amiga System Monitor
 *
 *------------------------------------------------------------------
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * You must not use this source code to gain profit of any kind!
 *
 *------------------------------------------------------------------
 *
 * @author Andreas Gelhausen
 * @author Richard Körber <rkoerber@gmx.de>
 */

#include "system_headers.h"

struct CommandsCallbackUserData {
    APTR ud_Tree;
    ULONG ud_Count;
};

static __asm __saveds LONG comtree_confunc(register __a2 Object *obj, register __a1 struct MUIP_NListtree_ConstructMessage *msg, register __a0 struct Hook *hook)
{
    return AllocListEntry(msg->MemPool, msg->UserData, sizeof(struct CommandEntry));
}

MakeHook(comtree_conhook, comtree_confunc);

static __asm __saveds LONG comtree_desfunc(register __a2 Object *obj, register __a1 struct MUIP_NListtree_ConstructMessage *msg, register __a0 struct Hook *hook)
{
    FreeListEntry(msg->MemPool, &msg->UserData);

    return 0;
}

MakeHook(comtree_deshook, comtree_desfunc);

static __asm __saveds LONG comtree_dspfunc(register __a2 Object *obj, register __a1 struct MUIP_NListtree_DisplayMessage *msg, register __a0 struct Hook *hook)
{
    static UBYTE *empty = "\0";

    if (msg->TreeNode) {
        struct CommandEntry *ce = msg->TreeNode->tn_User;

        if (stricmp(ce->ce_Type, "SEG_SINGLE") == 0 || stricmp(ce->ce_Type, "SEG_LIST") == 0) {
            msg->Array[0] = ce->ce_Address;
            msg->Array[1] = ce->ce_Name;
            msg->Array[2] = ce->ce_UseCount;
            msg->Array[3] = ce->ce_SegmentNumStr;
            msg->Array[4] = ce->ce_Lower;
            msg->Array[5] = ce->ce_Upper;
            msg->Array[6] = ce->ce_Size;
        } else {
            msg->Array[0] = empty;
            msg->Array[1] = empty;
            msg->Array[2] = empty;
            msg->Array[3] = ce->ce_SegmentNumStr;
            msg->Array[4] = ce->ce_Lower;
            msg->Array[5] = ce->ce_Upper;
            msg->Array[6] = ce->ce_Size;
        }
    } else {
        msg->Array[0] = MUIX_B "Address";
        msg->Array[1] = MUIX_B "Name";
        msg->Array[2] = MUIX_B "UseCount";
        msg->Array[3] = MUIX_B "Segment";
        msg->Array[4] = MUIX_B MUIX_C "Lower";
        msg->Array[5] = MUIX_B MUIX_C "Upper";
        msg->Array[6] = MUIX_B "Size";
    }

    return 0;
}

MakeHook(comtree_dsphook, comtree_dspfunc);

static __asm __saveds LONG comtree_cmpfunc(register __a2 Object *obj, register __a1 struct MUIP_NListtree_CompareMessage *msg, register __a0 struct Hook *hook)
{
    return stricmp(msg->TreeNode1->tn_Name, msg->TreeNode2->tn_Name);
}

MakeHook(comtree_cmphook, comtree_cmpfunc);

static __asm __saveds LONG comtree_findfunc(register __a2 Object *obj, register __a1 struct MUIP_NListtree_FindUserDataMessage *msg, register __a0 struct Hook *hook)
{
    struct CommandEntry *ce;

    ce = (struct CommandEntry *)msg->UserData;

    if (ce) {
        return strcmp((UBYTE *)msg->User, ce->ce_Name);
    } else {
        return ~0;
    }
}

MakeHook(comtree_findhook, comtree_findfunc);

UBYTE *GetUCType( LONG uc )
{
    static LONG uctype[] = { CMD_SYSTEM, CMD_INTERNAL, CMD_DISABLED, 0 };
    static UBYTE *uctypetext[] = { "SYSTEM", "INTERNAL", "DISABLED", NULL };

    int i = 0;

    while (uctype[i]) {
        if (uc == uctype[i]) {
            return (uctypetext[i]);
        }
        i++;
     }

     return NULL;
}

static void ReceiveList( void (* callback)( struct CommandEntry *ce, void *userData ),
                         void *userData )
{
    struct CommandEntry *ce;

    if (ce = tbAllocVecPooled(globalPool, sizeof(struct CommandEntry))) {
        if (SendDaemon("GetComList")) {
            while (ReceiveDecodedEntry((UBYTE *)ce, sizeof(struct CommandEntry))) {
                callback(ce, userData);
            }
        }

        tbFreeVecPooled(globalPool, ce);
    }
}

static void IterateList( void (* callback)( struct CommandEntry *ce, void *userData ),
                         void *userData )
{
    struct CommandEntry *ce;

    if (ce = tbAllocVecPooled(globalPool, sizeof(struct CommandEntry))) {
        struct Segment *segment;

        segment = (struct Segment *)BADDR(DEVINFO->di_NetHand);
        while (segment) {
            UBYTE *tmp;

            ce->ce_Addr = segment;
            _snprintf(ce->ce_Address, sizeof(ce->ce_Address), "$%08lx", segment);
            stccpy(ce->ce_Name, &segment->seg_Name[1], segment->seg_Name[0] + 1);

            if (tmp = GetUCType(segment->seg_UC)) {
                stccpy(ce->ce_UseCount, tmp, sizeof(ce->ce_UseCount));

                stccpy(ce->ce_Lower, MUIX_C "---", sizeof(ce->ce_Lower));
                stccpy(ce->ce_Upper, MUIX_C "---", sizeof(ce->ce_Upper));
                stccpy(ce->ce_Size, MUIX_R "---", sizeof(ce->ce_Size));

                ce->ce_SegmentNum = -1;
                stccpy(ce->ce_SegmentNumStr, "---", sizeof(ce->ce_SegmentNumStr));
                stccpy(ce->ce_Type, "SEG_SINGLE", sizeof(ce->ce_Type));

                callback(ce, userData);
             } else {
                LONG *seg;
                BOOL first = TRUE;

                _snprintf(ce->ce_UseCount, sizeof(ce->ce_UseCount), "%lD", segment->seg_UC - 1);

                ce->ce_SegmentNum = 1;
                _snprintf(ce->ce_SegmentNumStr, sizeof(ce->ce_SegmentNumStr), "%lD", ce->ce_SegmentNum);

                seg = BADDR(segment->seg_Seg);
                do {
                    LONG size;

                    size = *(seg - 1);
                    _snprintf(ce->ce_Lower, sizeof(ce->ce_Lower), "$%08lx", ((UBYTE *)seg) + 4);
                    _snprintf(ce->ce_Size, sizeof(ce->ce_Size), MUIX_R "%lD", size);
                    _snprintf(ce->ce_Upper, sizeof(ce->ce_Upper), "$%08lx", ((UBYTE *)seg) - 4 + size);

                    seg = BADDR(*seg);

                    if (!first) {
                        ce->ce_SegmentNum++;
                        _snprintf(ce->ce_SegmentNumStr, sizeof(ce->ce_SegmentNumStr), "%lD", ce->ce_SegmentNum);
                        stccpy(ce->ce_Type, "SEG_NODE", sizeof(ce->ce_Type));

                        callback(ce, userData);
                    } else {
                        stccpy(ce->ce_Type, "SEG_LIST", sizeof(ce->ce_Type));

                        callback(ce, userData);
                        first = FALSE;
                    }
                } while (seg);
             }

             segment = (struct Segment *) BADDR(segment->seg_Next);
        }

        tbFreeVecPooled(globalPool, ce);
    }
}

static void UpdateCallback( struct CommandEntry *ce,
                            void *userData )
{
    struct CommandsCallbackUserData *ud = (struct CommandsCallbackUserData *)userData;
    struct MUI_NListtree_TreeNode *parent;
    ULONG flags;

    flags = 0;
    if (stricmp(ce->ce_Type, "SEG_SINGLE") == 0) {
        ud->ud_Count++;
    } else if (stricmp(ce->ce_Type, "SEG_LIST") == 0) {
        flags = TNF_LIST | TNF_OPEN;
        ud->ud_Count++;
    } else if (stricmp(ce->ce_Type, "SEG_NODE") == 0) {
        // don't count up, this belongs to a SEG_LIST node
    }

    parent = (struct MUI_NListtree_TreeNode *)DoMethod(ud->ud_Tree, MUIM_NListtree_FindUserData, MUIV_NListtree_FindUserData_ListNode_Root, ce->ce_Name, MUIV_NListtree_FindUserData_Flag_StartNode);
    DoMethod(ud->ud_Tree, MUIM_NListtree_Insert, ce->ce_Name, ce, parent, MUIV_NListtree_Insert_PrevNode_Sorted, flags);
}

static void PrintCallback( struct CommandEntry *ce,
                           void *userData )
{
    if (ce->ce_SegmentNum == -1) {
        PrintFOneLine((BPTR)userData, " %9s %-15.15s %8s %-9.9s %-9.9s %10.10s\n", ce->ce_Address, ce->ce_Name, ce->ce_UseCount, ce->ce_Lower + 2, ce->ce_Upper + 2, ce->ce_Size + 2);
    } else if (ce->ce_SegmentNum == 1) {
        PrintFOneLine((BPTR)userData, " %9s %-15.15s %8s %-9.9s %-9.9s %10.10s\n", ce->ce_Address, ce->ce_Name, ce->ce_UseCount, ce->ce_Lower, ce->ce_Upper, ce->ce_Size + 2);
    } else {
        PrintFOneLine((BPTR)userData, " %9s %-15.15s %8s %-9.9s %-9.9s %10.10s\n", "", "", "", ce->ce_Lower, ce->ce_Upper, ce->ce_Size + 2);
    }
}

static void SendCallback( struct CommandEntry *ce,
                          void *userData )
{
    SendEncodedEntry((UBYTE *)ce, sizeof(struct CommandEntry));
}

static ULONG __saveds mNew( struct IClass *cl,
                            Object *obj,
                            struct opSet *msg )
{
    APTR comlist, comtree, comtext, comcount, updateButton, printButton, removeButton, exitButton;

    if (obj = (Object *)DoSuperNew(cl, obj,
        MUIA_HelpNode, CommandsText,
        MUIA_Window_ID, MakeID('R','C','O','M'),
        WindowContents, VGroup,

            Child, comlist = MyNListtreeObject(&comtree, "BAR,BAR,BAR,BAR,BAR,BAR,", &comtree_conhook, &comtree_deshook, &comtree_dsphook, &comtree_cmphook, &comtree_findhook, 3),
            Child, MyBelowListview(&comtext, &comcount),

            Child, MyVSpace(4),

            Child, HGroup, MUIA_Group_SameSize, TRUE,
                Child, updateButton = MakeButton(txtUpdate),
                Child, printButton  = MakeButton(txtPrint),
                Child, removeButton = MakeButton(txtRemove),
                Child, exitButton   = MakeButton(txtExit),
            End,
        End,
        TAG_MORE, msg->ops_AttrList))
    {
        struct CommandsWinData *cwd = INST_DATA(cl, obj);
        APTR parent;

        cwd->cwd_CommandTree = comtree;
        cwd->cwd_CommandText = comtext;
        cwd->cwd_CommandCount = comcount;
        cwd->cwd_RemoveButton = removeButton;

        parent = (APTR)GetTagData(MUIA_Window_ParentWindow, (ULONG)NULL, msg->ops_AttrList);

        set(obj, MUIA_Window_Title, MyGetWindowTitle("RESIDENT COMMANDS", cwd->cwd_Title, sizeof(cwd->cwd_Title)));
        set(obj, MUIA_Window_ActiveObject, comlist);
        set(removeButton, MUIA_Disabled, TRUE);

        DoMethod(parent,       MUIM_Window_AddChildWindow, obj);
        DoMethod(obj,          MUIM_Notify, MUIA_Window_CloseRequest, TRUE,           MUIV_Notify_Application, 5, MUIM_Application_PushMethod, parent, 2, MUIM_Window_RemChildWindow, obj);
        DoMethod(comtree,      MUIM_Notify, MUIA_NListtree_Active,    MUIV_EveryTime, obj,                     1, MUIM_CommandsWin_ListChange);
        DoMethod(updateButton, MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_CommandsWin_Update);
        DoMethod(printButton,  MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_CommandsWin_Print);
        DoMethod(removeButton, MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_CommandsWin_Remove);
        DoMethod(exitButton,   MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     3, MUIM_Set, MUIA_Window_CloseRequest, TRUE);
    }

    return (ULONG)obj;
}

static ULONG __saveds mDispose( struct IClass *cl,
                                Object *obj,
                                struct opSet *msg )
{
    struct CommandsWinData *cwd = INST_DATA(cl, obj);

    set(obj, MUIA_Window_Open, FALSE);
    DoMethod(cwd->cwd_CommandTree, MUIM_NListtree_Clear, NULL, 0);

    return (DoSuperMethodA(cl, obj, msg));
}

static ULONG __saveds mUpdate( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct CommandsWinData *cwd = INST_DATA(cl, obj);
    struct CommandsCallbackUserData ud;

    ApplicationSleep(TRUE);
    set(cwd->cwd_CommandTree, MUIA_NListtree_Quiet, TRUE);
    DoMethod(cwd->cwd_CommandTree, MUIM_NListtree_Clear, NULL, 0);

    ud.ud_Tree = cwd->cwd_CommandTree;
    ud.ud_Count = 0;

    if (clientstate) {
        ReceiveList(UpdateCallback, &ud);
    } else {
        IterateList(UpdateCallback, &ud);
    }

    SetCountText(cwd->cwd_CommandCount, ud.ud_Count);
    MySetContents(cwd->cwd_CommandText, "");

    set(cwd->cwd_CommandTree, MUIA_NListtree_Quiet, FALSE);
    set(cwd->cwd_RemoveButton, MUIA_Disabled, TRUE);
    ApplicationSleep(FALSE);

    return 0;
}

static ULONG __saveds mPrint( struct IClass *cl,
                              Object *obj,
                              Msg msg )
{
    PrintCommands(NULL);

    return 0;
}

static ULONG __saveds mRemove( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct CommandsWinData *cwd = INST_DATA(cl, obj);
    struct MUI_NListtree_TreeNode *tn;

    if (tn = GetActiveTreeNode(cwd->cwd_CommandTree)) {
        struct CommandEntry *ce = (struct CommandEntry *)tn->tn_User;

        if (ce->ce_UseCount[0] == 'I') {
            MyRequest(msgErrorContinue, msgCantRemoveInternal);
        } else if (ce->ce_UseCount[0] == 'S') {
            MyRequest(msgErrorContinue, msgCantRemoveSystem);
        } else if (ce->ce_UseCount[0] == 'D') {
            MyRequest(msgErrorContinue, msgCantRemoveDisabled);
        } else {
            if (MyRequest(msgYesNo, msgWantToRemoveCommand, ce->ce_Name)) {
                MyDoCommand("RemoveCommand $%08lx", ce->ce_Addr);
                DoMethod(obj, MUIM_CommandsWin_Update);
            }
        }
    }

    return 0;
}

static ULONG __saveds mListChange( struct IClass *cl,
                                   Object *obj,
                                   Msg msg )
{
    struct CommandsWinData *cwd = INST_DATA(cl, obj);
    struct MUI_NListtree_TreeNode *tn;

    if (tn = GetActiveTreeNode(cwd->cwd_CommandTree)) {
        struct CommandEntry *ce = (struct CommandEntry *)tn->tn_User;

        MySetContents(cwd->cwd_CommandText, "%s \"%s\"", ce->ce_Address, ce->ce_Name);
        set(cwd->cwd_RemoveButton, MUIA_Disabled, FALSE);
    }

    return 0;
}

ULONG __asm __saveds CommandsWinDispatcher( register __a0 struct IClass *cl,
                                            register __a2 Object *obj,
                                            register __a1 Msg msg )
{
    switch (msg->MethodID) {
        case OM_NEW:                      return (mNew(cl, obj, (APTR)msg));
        case OM_DISPOSE:                  return (mDispose(cl, obj, (APTR)msg));
        case MUIM_CommandsWin_Update:     return (mUpdate(cl, obj, (APTR)msg));
        case MUIM_CommandsWin_Print:      return (mPrint(cl, obj, (APTR)msg));
        case MUIM_CommandsWin_Remove:     return (mRemove(cl, obj, (APTR)msg));
        case MUIM_CommandsWin_ListChange: return (mListChange(cl, obj, (APTR)msg));
    }

    return (DoSuperMethodA(cl, obj, msg));
}

void PrintCommands( char *filename )
{
    BPTR handle;

    if (handle = HandlePrintStart(filename)) {
        PrintFOneLine(handle, "\n  Address  Name            UseCount   Lower     Upper         Size\n\n");
        IterateList(PrintCallback, (void *)handle);
    }

    HandlePrintStop();
}

void SendComList( void )
{
    IterateList(SendCallback, NULL);
}

