/**
 * 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 ResidentsCallbackUserData {
    APTR ud_List;
    ULONG ud_Count;
};

static __asm __saveds LONG resilist_con2func(register __a2 Object *obj, register __a1 struct NList_ConstructMessage *msg, register __a0 struct Hook *hook)
{
    return AllocListEntry(msg->pool, msg->entry, sizeof(struct ResidentEntry));
}

MakeHook(resilist_con2hook, resilist_con2func);

static __asm __saveds LONG resilist_des2func(register __a2 Object *obj, register __a1 struct NList_DestructMessage *msg, register __a0 struct Hook *hook)
{
    FreeListEntry(msg->pool, &msg->entry);

    return 0;
}

MakeHook(resilist_des2hook, resilist_des2func);

static __asm __saveds LONG resilist_dsp2func(register __a2 Object *obj, register __a1 struct NList_DisplayMessage *msg, register __a0 struct Hook *hook)
{
    struct ResidentEntry *re = (struct ResidentEntry *)msg->entry;

    if (re) {
        msg->strings[0] = re->re_Address;
        msg->strings[1] = re->re_Name;
        msg->strings[2] = re->re_Pri;
        msg->strings[3] = re->re_IdString;
    } else {
        msg->strings[0] = MUIX_B "Address";
        msg->strings[1] = MUIX_B "ln_Name";
        msg->strings[2] = MUIX_B "rt_Pri";
        msg->strings[3] = MUIX_B "rt_IdString";
    }

    return 0;
}

MakeHook(resilist_dsp2hook, resilist_dsp2func);

static LONG resilist_cmp2colfunc( struct ResidentEntry *re1,
                                  struct ResidentEntry *re2,
                                  ULONG column )
{
    LONG pri1, pri2;

    switch (column) {
        case 0: return stricmp(re1->re_Address, re2->re_Address);
        case 1: return stricmp(re1->re_Name, re2->re_Name);
        case 2: IsDec(re1->re_Pri, &pri1); IsDec(re2->re_Pri, &pri2); return pri2 - pri1;
        case 3: return stricmp(re1->re_IdString, re2->re_IdString);
    }
}

static __asm __saveds LONG resilist_cmp2func(register __a2 Object *obj, register __a1 struct NList_CompareMessage *msg, register __a0 struct Hook *hook)
{
    LONG cmp;
    struct ResidentEntry *re1, *re2;
    ULONG col1, col2;

    re1 = (struct ResidentEntry *)msg->entry1;
    re2 = (struct ResidentEntry *)msg->entry2;
    col1 = msg->sort_type & MUIV_NList_TitleMark_ColMask;
    col2 = msg->sort_type2 & MUIV_NList_TitleMark2_ColMask;

    if (msg->sort_type == MUIV_NList_SortType_None) return 0;

    if (msg->sort_type & MUIV_NList_TitleMark_TypeMask) {
        cmp = resilist_cmp2colfunc(re2, re1, col1);
    } else {
        cmp = resilist_cmp2colfunc(re1, re2, col1);
    }

    if (cmp != 0 || col1 == col2) return cmp;

    if (msg->sort_type2 & MUIV_NList_TitleMark2_TypeMask) {
        cmp = resilist_cmp2colfunc(re2, re1, col2);
    } else {
        cmp = resilist_cmp2colfunc(re1, re2, col2);
    }

    return cmp;
}

MakeHook(resilist_cmp2hook, resilist_cmp2func);

static void ReceiveList( void (* callback)( struct ResidentEntry *re, void *userData ),
                         void *userData )
{
    struct ResidentEntry *re;

    if (re = tbAllocVecPooled(globalPool, sizeof(struct ResidentEntry))) {
        if (SendDaemon("GetResiList")) {
            while (ReceiveDecodedEntry((UBYTE *)re, sizeof(struct ResidentEntry))) {
                callback(re, userData);
            }
        }

        tbFreeVecPooled(globalPool, re);
    }
}

static void IterateList( void (* callback)( struct ResidentEntry *re, void *userData ),
                         void *userData )
{
    struct ResidentEntry *re;

    if (re = tbAllocVecPooled(globalPool, sizeof(struct ResidentEntry))) {
        struct ShortList *sl;
        LONG *resip, resi;

        sl = (struct ShortList *)SysBase->KickTagPtr;
        while (sl != NULL && sl->sl_Resident != NULL && points2ram(sl->sl_Resident)) {
            re->re_Addr = sl->sl_Resident;
            _snprintf(re->re_Address, sizeof(re->re_Address), MUIX_PH "$%08lx" MUIX_PT, sl->sl_Resident);
            stccpy(re->re_Name, nonetest(sl->sl_Resident->rt_Name), sizeof(re->re_Name));
            _snprintf(re->re_Pri, sizeof(re->re_Pri), "%4ld", sl->sl_Resident->rt_Pri);
            stccpy(re->re_IdString, nonetest(sl->sl_Resident->rt_IdString), sizeof(re->re_IdString));

            healstring(re->re_Name);
            healstring(re->re_IdString);

            callback(re, userData);

            sl = (struct ShortList *)((LONG)sl->sl_Next & 0x7fffffff);
        }

        resip = (LONG *)FIRSTRESIDENT;
        while (resi = *resip) {
            // HSMOD: must handle the case that has directly jump after each other
            while (resi & 0x80000000) {
                resip = (LONG *)(resi & 0x7fffffff);
                resi = *resip;
            }
            re->re_Addr = (struct Resident *)resi;
            _snprintf(re->re_Address, sizeof(re->re_Address), "$%08lx", resi);
            stccpy(re->re_Name, nonetest(((struct Resident *)resi)->rt_Name), sizeof(re->re_Name));
            _snprintf(re->re_Pri, sizeof(re->re_Pri), "%4ld", ((struct Resident *)resi)->rt_Pri);
            stccpy(re->re_IdString, nonetest(((struct Resident *)resi)->rt_IdString), sizeof(re->re_IdString));

            healstring(re->re_Name);
            healstring(re->re_IdString);

            callback(re, userData);

            resip++;
        }

        tbFreeVecPooled(globalPool, re);
    }
}

static void UpdateCallback( struct ResidentEntry *re,
                            void *userData )
{
    struct ResidentsCallbackUserData *ud = (struct ResidentsCallbackUserData *)userData;

    InsertBottomEntry(ud->ud_List, re);
    ud->ud_Count++;
}

static void PrintCallback( struct ResidentEntry *re,
                           void *userData )
{
    PrintFOneLine((BPTR)userData, " %s %-23.23s %4s %s\n", re->re_Address, re->re_Name, re->re_Pri, re->re_IdString);
}

static void SendCallback( struct ResidentEntry *re,
                          void *userData )
{
    SendEncodedEntry((UBYTE *)re, sizeof(struct ResidentEntry));
}

static ULONG __saveds mNew( struct IClass *cl,
                            Object *obj,
                            struct opSet *msg )
{
    APTR resilist, resitext, resicount, updateButton, printButton, moreButton, exitButton;

    if (obj = (Object *)DoSuperNew(cl, obj,
        MUIA_HelpNode, ResidentsText,
        MUIA_Window_ID, MakeID('R','E','S','I'),
        WindowContents, VGroup,

            Child, resilist = MyNListviewObject(MakeID('R','E','L','V'), "BAR,BAR,BAR P=" MUIX_R ",BAR", &resilist_con2hook, &resilist_des2hook, &resilist_dsp2hook, &resilist_cmp2hook, TRUE),
            Child, MyBelowListview(&resitext, &resicount),

            Child, MyVSpace(4),

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

        rwd->rwd_ResidentList = resilist;
        rwd->rwd_ResidentText = resitext;
        rwd->rwd_ResidentCount = resicount;
        rwd->rwd_MoreButton = moreButton;

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

        set(obj, MUIA_Window_Title, MyGetWindowTitle("RESIDENTS", rwd->rwd_Title, sizeof(rwd->rwd_Title)));
        set(obj, MUIA_Window_ActiveObject, resilist);
        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(resilist,     MUIM_Notify, MUIA_NList_Active,        MUIV_EveryTime, obj,                     1, MUIM_ResidentsWin_ListChange);
        DoMethod(resilist,     MUIM_Notify, MUIA_NList_DoubleClick,   MUIV_EveryTime, obj,                     1, MUIM_ResidentsWin_More);
        DoMethod(updateButton, MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_ResidentsWin_Update);
        DoMethod(printButton,  MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_ResidentsWin_Print);
        DoMethod(moreButton,   MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_ResidentsWin_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 ResidentsWinData *rwd = INST_DATA(cl, obj);

    set(obj, MUIA_Window_Open, FALSE);
    DoMethod(rwd->rwd_ResidentList, MUIM_NList_Clear);

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

static ULONG __saveds mUpdate( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct ResidentsWinData *rwd = INST_DATA(cl, obj);
    struct ResidentsCallbackUserData ud;

    ApplicationSleep(TRUE);
    set(rwd->rwd_ResidentList, MUIA_NList_Quiet, TRUE);
    DoMethod(rwd->rwd_ResidentList, MUIM_NList_Clear);

    ud.ud_List = rwd->rwd_ResidentList;
    ud.ud_Count = 0;

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

    SetCountText(rwd->rwd_ResidentCount, ud.ud_Count);
    MySetContents(rwd->rwd_ResidentText, "");

    set(rwd->rwd_ResidentList, MUIA_NList_Quiet, FALSE);
    set(rwd->rwd_ResidentList, MUIA_NList_Active, MUIV_NList_Active_Off);
    set(rwd->rwd_MoreButton, MUIA_Disabled, TRUE);
    ApplicationSleep(FALSE);

    return 0;
}

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

    return 0;
}

static ULONG __saveds mMore( struct IClass *cl,
                             Object *obj,
                             Msg msg )
{
    struct ResidentsWinData *rwd = INST_DATA(cl, obj);
    struct ResidentEntry *re;

    if (re = (struct ResidentEntry *)GetActiveEntry(rwd->rwd_ResidentList)) {
        APTR detailWin;

        if (detailWin = ResidentsDetailWindowObject,
                MUIA_Window_ParentWindow, obj,
                MUIA_Window_MaxChildWindowCount, (opts.SingleWindows) ? 1 : 0,
            End) {
            set(detailWin, MUIA_ResidentsDetailWin_Resident, re);
            set(detailWin, MUIA_Window_Open, TRUE);
        }
    }

    return 0;
}

static ULONG __saveds mListChange( struct IClass *cl,
                                   Object *obj,
                                   Msg msg )
{
    struct ResidentsWinData *rwd = INST_DATA(cl, obj);
    struct ResidentEntry *re;

    if (re = (struct ResidentEntry *)GetActiveEntry(rwd->rwd_ResidentList)) {
        MySetContents(rwd->rwd_ResidentText, "%s \"%s\"", re->re_Address, re->re_Name);
        if (!clientstate) set(rwd->rwd_MoreButton, MUIA_Disabled, FALSE);
    }

    return 0;
}

ULONG __asm __saveds ResidentsWinDispatcher( 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_ResidentsWin_Update:     return (mUpdate(cl, obj, (APTR)msg));
        case MUIM_ResidentsWin_Print:      return (mPrint(cl, obj, (APTR)msg));
        case MUIM_ResidentsWin_More:       return (mMore(cl, obj, (APTR)msg));
        case MUIM_ResidentsWin_ListChange: return (mListChange(cl, obj, (APTR)msg));
    }

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

void PrintResidents( char *filename )
{
    BPTR handle;

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

    HandlePrintStop();
}

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

