/**
 * 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 InterruptsCallbackUserData {
    APTR ud_Tree;
    ULONG ud_Count;
    UBYTE *ud_FindMe;
    struct Interrupt *ud_FoundThis;
};

static UBYTE *intTypeShort[] = {
    "Serial Out",
    "Disk Block",
    "SoftInt",
    "INT2/CIAA",
    "Copper",
    "Vertical Blank",
    "Blitter",
    "Audio 0",
    "Audio 1",
    "Audio 2",
    "Audio 3",
    "Serial In",
    "Disk Sync",
    "INT6/CIAB",
    "INTEN",
    "NMI"
};

static __asm __saveds LONG inttree_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 InterruptEntry));
}

MakeHook(inttree_conhook, inttree_confunc);

static __asm __saveds LONG inttree_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(inttree_deshook, inttree_desfunc);

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

    if (msg->TreeNode != NULL) {
        struct InterruptEntry *ie = msg->TreeNode->tn_User;

        if (stricmp(ie->ie_Type, "HANDLER") == 0) {
            msg->Array[0] = ie->ie_Address;
            msg->Array[1] = ie->ie_Number;
            msg->Array[2] = ie->ie_Name;
            msg->Array[3] = ie->ie_Kind;
            msg->Array[4] = ie->ie_Pri;
            msg->Array[5] = ie->ie_Data;
            msg->Array[6] = ie->ie_Code;
        } else if (stricmp(ie->ie_Type, "SERVER_LIST") == 0) {
            msg->Array[0] = empty;
            msg->Array[1] = ie->ie_Number;
            msg->Array[2] = empty;
            msg->Array[3] = empty;
            msg->Array[4] = empty;
            msg->Array[5] = empty;
            msg->Array[6] = empty;
        } else if (stricmp(ie->ie_Type, "SERVER_NODE") == 0) {
            msg->Array[0] = ie->ie_Address;
            msg->Array[1] = ie->ie_Number;
            msg->Array[2] = ie->ie_Name;
            msg->Array[3] = ie->ie_Kind;
            msg->Array[4] = ie->ie_Pri;
            msg->Array[5] = ie->ie_Data;
            msg->Array[6] = ie->ie_Code;
        }
    } else {
        msg->Array[0] = MUIX_B "Address";
        msg->Array[1] = MUIX_B "Interrupt";
        msg->Array[2] = MUIX_B "ln_Name";
        msg->Array[3] = MUIX_B "Type";
        msg->Array[4] = MUIX_B "ln_Pri";
        msg->Array[5] = MUIX_B "is_Data";
        msg->Array[6] = MUIX_B "is_Code";
    }

    return 0;
}

MakeHook(inttree_dsphook, inttree_dspfunc);

static __asm __saveds LONG inttree_findfunc(register __a2 Object *obj, register __a1 struct MUIP_NListtree_FindUserDataMessage *msg, register __a0 struct Hook *hook)
{
    struct InterruptEntry *ie;

    ie = (struct InterruptEntry *)msg->UserData;

    if (ie) {
        return stricmp((UBYTE *)msg->User, ie->ie_RealNumber);
    } else {
        return ~0;
    }
}

MakeHook(inttree_findhook, inttree_findfunc);

static void ReceiveList( void (* callback)( struct InterruptEntry *ie, void *userData ),
                         void *userData )
{
    struct InterruptEntry *ie;

    if (ie = tbAllocVecPooled(globalPool, sizeof(struct InterruptEntry))) {
        if (SendDaemon("GetIntList")) {
            while (ReceiveDecodedEntry((UBYTE *)ie, sizeof(struct InterruptEntry))) {
                callback(ie, userData);
            }
        }

        tbFreeVecPooled(globalPool, ie);
    }
}

static void IterateList( void (* callback)( struct InterruptEntry *ie, void *userData ),
                         void *userData )
{
    struct InterruptEntry *ie;

    if (ie = tbAllocVecPooled(globalPool, sizeof(struct InterruptEntry))) {
        ULONG vec;

        for (vec = 0; vec <= 15; vec++) {
            struct IntVector *intvec;

            if (intvec = (struct IntVector *)&SysBase->IntVects[vec]) {

                if (intvec->iv_Node != NULL && intvec->iv_Code != NULL) {
                    struct Node *intnode;

                    intnode = intvec->iv_Node;

                    ie->ie_Addr = (struct Interrupt *)intnode;
                    if (points2ram((APTR)intvec->iv_Code)) {
                        _snprintf(ie->ie_Code, sizeof(ie->ie_Code), MUIX_PH "$%08lx" MUIX_PT, intvec->iv_Code);
                    } else {
                        _snprintf(ie->ie_Code, sizeof(ie->ie_Code), "$%08lx", intvec->iv_Code);
                    }
                    _snprintf(ie->ie_Address, sizeof(ie->ie_Address), "$%08lx", intnode);
                    stccpy(ie->ie_Name, nonetest(intnode->ln_Name), sizeof(ie->ie_Name));
                    _snprintf(ie->ie_Pri, sizeof(ie->ie_Pri), "%4ld", intnode->ln_Pri);
                    _snprintf(ie->ie_Data, sizeof(ie->ie_Data), "$%08lx", intvec->iv_Data);
                    stccpy(ie->ie_Number, intTypeShort[vec], sizeof(ie->ie_Number));
                    stccpy(ie->ie_RealNumber, intTypeShort[vec], sizeof(ie->ie_RealNumber));
                    stccpy(ie->ie_Kind, "Handler", sizeof(ie->ie_Kind));
                    stccpy(ie->ie_Type, "HANDLER", sizeof(ie->ie_Type));
                    ie->ie_IntNumber = vec;

                    callback(ie, userData);
                } else if (intvec->iv_Data != NULL && intvec->iv_Data != (APTR)-1) {
                    struct List *list;
                    struct Interrupt *irq;
                    ULONG servercnt;

                    ie->ie_Addr = NULL;
                    ie->ie_Address[0] = 0x00;
                    ie->ie_Code[0] = 0x00;
                    ie->ie_Data[0] = 0x00;
                    ie->ie_Pri[0] = 0x00;
                    stccpy(ie->ie_Number, intTypeShort[vec], sizeof(ie->ie_Number));
                    stccpy(ie->ie_RealNumber, intTypeShort[vec], sizeof(ie->ie_RealNumber));
                    stccpy(ie->ie_Kind, "Server", sizeof(ie->ie_Kind));
                    stccpy(ie->ie_Type, "SERVER_LIST", sizeof(ie->ie_Type));
                    ie->ie_IntNumber = vec;

                    callback(ie, userData);

                    list = (struct List *)intvec->iv_Data;

                    servercnt = 0;
                    for (irq = (struct Interrupt *)list->lh_Head; irq->is_Node.ln_Succ; irq = (struct Interrupt *)irq->is_Node.ln_Succ) {
                        servercnt++;

                        ie->ie_Addr = irq;
                        if (points2ram((APTR)irq->is_Code)) {
                            _snprintf(ie->ie_Code, sizeof(ie->ie_Code), MUIX_PH "$%08lx" MUIX_PT, irq->is_Code);
                        } else {
                            _snprintf(ie->ie_Code, sizeof(ie->ie_Code), "$%08lx", irq->is_Code);
                        }
                        _snprintf(ie->ie_Address, sizeof(ie->ie_Address), "$%08lx", irq);
                        stccpy(ie->ie_Name, nonetest(irq->is_Node.ln_Name), sizeof(ie->ie_Name));
                        _snprintf(ie->ie_Pri, sizeof(ie->ie_Pri), "%4ld", irq->is_Node.ln_Pri);
                        _snprintf(ie->ie_Data, sizeof(ie->ie_Data), "$%08lx", irq->is_Data);
                        _snprintf(ie->ie_Number, sizeof(ie->ie_Number), "%ld", servercnt);
                        stccpy(ie->ie_RealNumber, intTypeShort[vec], sizeof(ie->ie_RealNumber));
                        stccpy(ie->ie_Kind, "Server", sizeof(ie->ie_Kind));
                        stccpy(ie->ie_Type, "SERVER_NODE", sizeof(ie->ie_Type));
                        ie->ie_IntNumber = vec;

                        callback(ie, userData);
                    }
                }
            }
        }

        tbFreeVecPooled(globalPool, ie);
    }
}

static void UpdateCallback( struct InterruptEntry *ie,
                            void *userData )
{
    struct InterruptsCallbackUserData *ud = (struct InterruptsCallbackUserData *)userData;
    struct MUI_NListtree_TreeNode *parent;
    ULONG flags;
    BOOL search_parent;

    flags = 0;
    search_parent = FALSE;
    if (stricmp(ie->ie_Type, "HANDLER") == 0) {
        ud->ud_Count++;
    } else if (stricmp(ie->ie_Type, "SERVER_LIST") == 0) {
        flags = TNF_LIST | TNF_OPEN;
        // don't count up, we only count real IRQ entries
    } else if (stricmp(ie->ie_Type, "SERVER_NODE") == 0) {
        ud->ud_Count++;
        search_parent = TRUE;
    }

    if (search_parent) {
        parent = (struct MUI_NListtree_TreeNode *)DoMethod(ud->ud_Tree, MUIM_NListtree_FindUserData, MUIV_NListtree_FindUserData_ListNode_Root, ie->ie_RealNumber, MUIV_NListtree_FindUserData_Flag_StartNode);
    } else {
        parent = MUIV_NListtree_Insert_ListNode_Root;
    }
    DoMethod(ud->ud_Tree, MUIM_NListtree_Insert, ie->ie_Name, ie, parent, MUIV_NListtree_Insert_PrevNode_Tail, flags);
}

static void PrintCallback( struct InterruptEntry *ie,
                           void *userData )
{
    if (ie->ie_Addr) {
        if (points2ram((APTR)ie->ie_Addr->is_Code)) {
            PrintFOneLine((BPTR)userData, " %s %4s %s %-9.9s  %-20s %-7.7s %s\n", ie->ie_Address, ie->ie_Pri, ie->ie_Data, ie->ie_Code + 2, ie->ie_RealNumber, ie->ie_Kind, ie->ie_Name);
        } else {
            PrintFOneLine((BPTR)userData, " %s %4s %s %-9.9s  %-20s %-7.7s %s\n", ie->ie_Address, ie->ie_Pri, ie->ie_Data, ie->ie_Code, ie->ie_RealNumber, ie->ie_Kind, ie->ie_Name);
        }
    }
}

static void SendCallback( struct InterruptEntry *ie,
                          void *userData )
{
    SendEncodedEntry((UBYTE *)ie, sizeof(struct InterruptEntry));
}

static void FindCallback( struct InterruptEntry *ie,
                          void *userData )
{
    struct InterruptsCallbackUserData *ud = (struct InterruptsCallbackUserData *)userData;

    if (stricmp(ud->ud_FindMe, ie->ie_Name) == 0) ud->ud_FoundThis = ie->ie_Addr;
}

static void RemoveCallback( struct InterruptEntry *ie,
                            void *userData )
{
    struct InterruptsCallbackUserData *ud = (struct InterruptsCallbackUserData *)userData;

    if (stricmp(ie->ie_Type, "SERVER_NODE") == 0 && stricmp(ud->ud_FindMe, ie->ie_Name) == 0) {
        RemIntServer(ie->ie_IntNumber, ie->ie_Addr);
        ud->ud_FoundThis = ie->ie_Addr;
    }
}

static ULONG __saveds mNew( struct IClass *cl,
                            Object *obj,
                            struct opSet *msg )
{
    APTR intlist, inttree, inttext, intcount, updateButton, printButton, removeButton, moreButton, exitButton;

    if (obj = (Object *)DoSuperNew(cl, obj,
        MUIA_HelpNode, InterruptsText,
        MUIA_Window_ID, MakeID('I','N','T','E'),
        WindowContents, VGroup,

            Child, intlist = MyNListtreeObject(&inttree, "BAR,BAR,BAR,BAR P=" MUIX_C ",BAR P=" MUIX_R ",BAR,BAR", &inttree_conhook, &inttree_deshook, &inttree_dsphook, NULL, &inttree_findhook, 1),
            Child, MyBelowListview(&inttext, &intcount),

            Child, MyVSpace(4),

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

        iwd->iwd_InterruptTree = inttree;
        iwd->iwd_InterruptText = inttext;
        iwd->iwd_InterruptCount = intcount;
        iwd->iwd_RemoveButton = removeButton;
        iwd->iwd_MoreButton = moreButton;

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

        set(obj, MUIA_Window_Title, MyGetWindowTitle("INTERRUPTS", iwd->iwd_Title, sizeof(iwd->iwd_Title)));
        set(obj, MUIA_Window_ActiveObject, intlist);
        set(moreButton, 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(inttree,      MUIM_Notify, MUIA_NListtree_Active,      MUIV_EveryTime, obj,                     1, MUIM_InterruptsWin_ListChange);
        DoMethod(inttree,      MUIM_Notify, MUIA_NListtree_DoubleClick, MUIV_EveryTime, obj,                     1, MUIM_InterruptsWin_More);
        DoMethod(updateButton, MUIM_Notify, MUIA_Pressed,               FALSE,          obj,                     1, MUIM_InterruptsWin_Update);
        DoMethod(printButton,  MUIM_Notify, MUIA_Pressed,               FALSE,          obj,                     1, MUIM_InterruptsWin_Print);
        DoMethod(removeButton, MUIM_Notify, MUIA_Pressed,               FALSE,          obj,                     1, MUIM_InterruptsWin_Remove);
        DoMethod(moreButton,   MUIM_Notify, MUIA_Pressed,               FALSE,          obj,                     1, MUIM_InterruptsWin_More);
        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 InterruptsWinData *iwd = INST_DATA(cl, obj);

    set(obj, MUIA_Window_Open, FALSE);
    DoMethod(iwd->iwd_InterruptTree, MUIM_NListtree_Clear, NULL, 0);

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

static ULONG __saveds mUpdate( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct InterruptsWinData *iwd = INST_DATA(cl, obj);
    struct InterruptsCallbackUserData ud;

    ApplicationSleep(TRUE);
    set(iwd->iwd_InterruptTree, MUIA_NListtree_Quiet, TRUE);
    DoMethod(iwd->iwd_InterruptTree, MUIM_NListtree_Clear, NULL, 0);

    ud.ud_Tree = iwd->iwd_InterruptTree;
    ud.ud_Count = 0;

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

    SetCountText(iwd->iwd_InterruptCount, ud.ud_Count);
    MySetContents(iwd->iwd_InterruptText, "");

    set(iwd->iwd_InterruptTree, MUIA_NListtree_Quiet, FALSE);
    set(iwd->iwd_InterruptTree, MUIA_NListtree_Active, MUIV_NListtree_Active_Off);
    set(iwd->iwd_RemoveButton, MUIA_Disabled, TRUE);
    set(iwd->iwd_MoreButton, MUIA_Disabled, TRUE);
    ApplicationSleep(FALSE);

    return 0;
}

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

    return 0;
}

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

    if (tn = GetActiveTreeNode(iwd->iwd_InterruptTree)) {
        struct InterruptEntry *ie = (struct InterruptEntry *)tn->tn_User;

        if (stricmp(ie->ie_Type, "HANDLER") == 0) {
            MyRequest(msgErrorContinue, msgCantRemoveInterruptHandler);
        } else {
            if (MyRequest(msgYesNo, msgWantToRemoveInterrupt, ie->ie_Name)) {
                MyDoCommand("RemoveInterrupt \"%s\"", ie->ie_Name);
                DoMethod(obj, MUIM_InterruptsWin_Update);
            }
        }
    }

    return 0;
}

static ULONG __saveds mMore( struct IClass *cl,
                             Object *obj,
                             Msg msg )
{
    struct InterruptsWinData *iwd = INST_DATA(cl, obj);
    struct MUI_NListtree_TreeNode *tn;

    if (tn = GetActiveTreeNode(iwd->iwd_InterruptTree)) {
        struct InterruptEntry *ie = (struct InterruptEntry *)tn->tn_User;

        if (stricmp(ie->ie_Type, "HANDLER") == 0 || stricmp(ie->ie_Type, "SERVER_NODE") == 0) {
            APTR detailWin;

            if (detailWin = InterruptsDetailWindowObject,
                    MUIA_Window_ParentWindow, obj,
                End) {
                set(detailWin, MUIA_InterruptsDetailWin_Interrupt, ie);
                set(detailWin, MUIA_Window_Open, TRUE);
            }
        }
    }

    return 0;
}

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

    if (tn = GetActiveTreeNode(iwd->iwd_InterruptTree)) {
        struct InterruptEntry *ie = (struct InterruptEntry *)tn->tn_User;

        if (stricmp(ie->ie_Type, "HANDLER") == 0) {
            MySetContents(iwd->iwd_InterruptText, "%s \"%s\"", ie->ie_Address, ie->ie_Name);
            set(iwd->iwd_RemoveButton, MUIA_Disabled, TRUE);
            if (!clientstate) set(iwd->iwd_MoreButton, MUIA_Disabled, FALSE);
        } else if (stricmp(ie->ie_Type, "SERVER_LIST") == 0) {
            MySetContents(iwd->iwd_InterruptText, "");
            set(iwd->iwd_RemoveButton, MUIA_Disabled, TRUE);
            if (!clientstate) set(iwd->iwd_MoreButton, MUIA_Disabled, TRUE);
        } else if (stricmp(ie->ie_Type, "SERVER_NODE") == 0) {
            MySetContents(iwd->iwd_InterruptText, "%s \"%s\"", ie->ie_Address, ie->ie_Name);
            set(iwd->iwd_RemoveButton, MUIA_Disabled, FALSE);
            if (!clientstate) set(iwd->iwd_MoreButton, MUIA_Disabled, FALSE);
        }
    }

    return 0;
}

ULONG __asm __saveds InterruptsWinDispatcher( 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_InterruptsWin_Update:     return (mUpdate(cl, obj, (APTR)msg));
        case MUIM_InterruptsWin_Print:      return (mPrint(cl, obj, (APTR)msg));
        case MUIM_InterruptsWin_Remove:     return (mRemove(cl, obj, (APTR)msg));
        case MUIM_InterruptsWin_More:       return (mMore(cl, obj, (APTR)msg));
        case MUIM_InterruptsWin_ListChange: return (mListChange(cl, obj, (APTR)msg));
    }

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

void PrintInterrupts( char *filename )
{
    BPTR handle;

    if (handle = HandlePrintStart(filename)) {
        PrintFOneLine(handle, "\n  Address   Pri   Data      Code      NUM                 IntType Name\n\n");
        IterateList(PrintCallback, (void *)handle);
    }

    HandlePrintStop();
}

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

struct Interrupt *FindInterrupt( UBYTE *name )
{
    struct InterruptsCallbackUserData ud;

    ud.ud_FindMe = name;
    ud.ud_FoundThis = NULL;

    IterateList(FindCallback, &ud);

    return ud.ud_FoundThis;
}

struct Interrupt *RemoveInterrupt( UBYTE *name )
{
    struct InterruptsCallbackUserData ud;

    ud.ud_FindMe = name;
    ud.ud_FoundThis = NULL;

    IterateList(RemoveCallback, &ud);

    return ud.ud_FoundThis;
}

