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

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

MakeHook(liblist_con2hook, liblist_con2func);

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

    return 0;
}

MakeHook(liblist_des2hook, liblist_des2func);

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

    if (le) {
        msg->strings[0] = le->le_Address;
        msg->strings[1] = le->le_Name;
        msg->strings[2] = le->le_Type;
        msg->strings[3] = le->le_Pri;
        msg->strings[4] = le->le_Version;
        msg->strings[5] = le->le_OpenCount;
        msg->strings[6] = le->le_RAMPtrCount;
    } else {
        msg->strings[0] = "Address";
        msg->strings[1] = "ln_Name";
        msg->strings[2] = "ln_Type";
        msg->strings[3] = "ln_Pri";
        msg->strings[4] = "Version";
        msg->strings[5] = "OCnt";
        msg->strings[6] = "RPC";
        msg->preparses[0] = MUIX_B;
        msg->preparses[1] = MUIX_B;
        msg->preparses[2] = MUIX_B;
        msg->preparses[3] = MUIX_B;
        msg->preparses[4] = MUIX_B;
        msg->preparses[5] = MUIX_B;
        msg->preparses[6] = MUIX_B;
    }

    return 0;
}

MakeHook(liblist_dsp2hook, liblist_dsp2func);

static LONG liblist_cmp2colfunc( struct LibraryEntry *le1,
                                 struct LibraryEntry *le2,
                                 ULONG column )
{
    LONG pri1, pri2;

    switch (column) {
        case 0: return stricmp(le1->le_Address, le2->le_Address);
        case 1: return stricmp(le1->le_Name, le2->le_Name);
        case 2: return stricmp(le1->le_Type, le2->le_Type);
        case 3: IsDec(le1->le_Pri, &pri1); IsDec(le2->le_Pri, &pri2); return pri2 - pri1;
        case 4: return stricmp(le1->le_Version, le2->le_Version);
        case 5: return stricmp(le1->le_OpenCount, le2->le_OpenCount);
        case 6: return stricmp(le1->le_RAMPtrCount, le2->le_RAMPtrCount);
    }
}

static __asm __saveds LONG liblist_cmp2func(register __a2 Object *obj, register __a1 struct NList_CompareMessage *msg, register __a0 struct Hook *hook)
{
    LONG cmp;
    struct LibraryEntry *le1, *le2;
    ULONG col1, col2;

    le1 = (struct LibraryEntry *)msg->entry1;
    le2 = (struct LibraryEntry *)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 = liblist_cmp2colfunc(le2, le1, col1);
    } else {
        cmp = liblist_cmp2colfunc(le1, le2, col1);
    }

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

    if (msg->sort_type2 & MUIV_NList_TitleMark2_TypeMask) {
        cmp = liblist_cmp2colfunc(le2, le1, col2);
    } else {
        cmp = liblist_cmp2colfunc(le1, le2, col2);
    }

    return cmp;
}

MakeHook(liblist_cmp2hook, liblist_cmp2func);

void FlushLibraries( void )
{
    struct Library *lib, *next_lib;

    Forbid();

    ITERATE_CHANGING_LIST(&SysBase->LibList, struct Library *, lib, next_lib) {
        if (lib->lib_OpenCnt == 0) RemLibrary(lib);
    }

    Permit();
}

static void ReceiveList( void (* callback)( struct LibraryEntry *le, void *userData ),
                         void *userData )
{
    struct LibraryEntry *le;

    if (le = tbAllocVecPooled(globalPool, sizeof(struct LibraryEntry))) {
        if (SendDaemon("GetLibList")) {
            while (ReceiveDecodedEntry((UBYTE *)le, sizeof(struct LibraryEntry))) {
                callback(le, userData);
            }
        }

        tbFreeVecPooled(globalPool, le);
    }
}

static void IterateList( void (* callback)( struct LibraryEntry *le, void *userData ),
                         void *userData )
{
    struct Library *lib;
    struct MinList tmplist;
    struct LibraryEntry *le, *_le;

    NewList((struct List *)&tmplist);

    Forbid();

    ITERATE_LIST(&SysBase->LibList, struct Library *, lib) {
        if (le = AllocVec(sizeof(struct LibraryEntry), MEMF_PUBLIC)) {
            ULONG cnt;

            le->le_Addr = lib;

            _snprintf(le->le_Address, sizeof(le->le_Address), "$%08lx", lib);
            stccpy(le->le_Name, lib->lib_Node.ln_Name, sizeof(le->le_Name));
            _snprintf(le->le_Pri, sizeof(le->le_Pri), "%4ld", lib->lib_Node.ln_Pri);
            _snprintf(le->le_Version, sizeof(le->le_Version), "%ld.%ld", lib->lib_Version, lib->lib_Revision);
            _snprintf(le->le_OpenCount, sizeof(le->le_OpenCount), "%6lD", lib->lib_OpenCnt);

            cnt = GetRamPointerCount(lib);
            if (cnt == (lib->lib_NegSize / LIB_VECTSIZE)) {
                stccpy(le->le_RAMPtrCount, "***", sizeof(le->le_RAMPtrCount));
            } else {
                _snprintf(le->le_RAMPtrCount, sizeof(le->le_RAMPtrCount), "%4lD", cnt);
            }

            stccpy(le->le_Type, GetNodeType(lib->lib_Node.ln_Type), sizeof(le->le_Type));

            AddTail((struct List *)&tmplist, (struct Node *)le);
        }
    }

    Permit();

    ITERATE_CHANGING_LIST(&tmplist, struct LibraryEntry *, le, _le) {
        callback(le, userData);
        FreeVec(le);
    }
}

static void UpdateCallback( struct LibraryEntry *le,
                            void *userData )
{
    struct LibrariesCallbackUserData *ud = (struct LibrariesCallbackUserData *)userData;

    InsertSortedEntry(ud->ud_List, le);
    ud->ud_Count++;
}

static void PrintCallback( struct LibraryEntry *le,
                           void *userData )
{
    PrintFOneLine((BPTR)userData, " %s %4s %4s %4s %s %s\n", le->le_Address, le->le_Pri, le->le_OpenCount, le->le_RAMPtrCount, le->le_Name, le->le_Version);
}

static void SendCallback( struct LibraryEntry *le,
                          void *userData )
{
    SendEncodedEntry((UBYTE *)le, sizeof(struct LibraryEntry));
}

static ULONG __saveds mNew( struct IClass *cl,
                            Object *obj,
                            struct opSet *msg )
{
    APTR liblist, libtext, libcount, updateButton, printButton, removeButton, priorityButton, closeButton, moreButton, funcButton, exitButton;

    if (obj = (Object *)DoSuperNew(cl, obj,
        MUIA_HelpNode, DevicesText,
        MUIA_Window_ID, MakeID('L','I','B','S'),
        WindowContents, VGroup,

            Child, liblist = MyNListviewObject(MakeID('L','I','L','V'), "BAR,BAR,BAR P=" MUIX_C ",BAR P=" MUIX_R ",BAR P=" MUIX_C ",BAR P=" MUIX_R ",BAR P=" MUIX_R, &liblist_con2hook, &liblist_des2hook, &liblist_dsp2hook, &liblist_cmp2hook, TRUE),
            Child, MyBelowListview(&libtext, &libcount),

            Child, MyVSpace(4),

            Child, HGroup, MUIA_Group_SameSize, TRUE,
                Child, priorityButton = MakeButton(txtPriority),
                Child, removeButton   = MakeButton(txtRemove),
                Child, closeButton    = MakeButton(txtClose),
                Child, funcButton     = MakeButton(txtFunctions),
            End,

            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 LibrariesWinData *lwd = INST_DATA(cl, obj);
        APTR parent;

        lwd->lwd_LibraryList = liblist;
        lwd->lwd_LibraryText = libtext;
        lwd->lwd_LibraryCount = libcount;
        lwd->lwd_RemoveButton = removeButton;
        lwd->lwd_PriorityButton = priorityButton;
        lwd->lwd_CloseButton = closeButton;
        lwd->lwd_MoreButton = moreButton;
        lwd->lwd_FunctionsButton = funcButton;

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

        set(obj, MUIA_Window_Title, MyGetWindowTitle("LIBRARIES", lwd->lwd_Title, sizeof(lwd->lwd_Title)));
        set(obj, MUIA_Window_ActiveObject, liblist);
        set(removeButton, MUIA_Disabled, TRUE);
        set(priorityButton, MUIA_Disabled, TRUE);
        set(closeButton, MUIA_Disabled, TRUE);
        set(moreButton, MUIA_Disabled, TRUE);
        set(funcButton, 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(liblist,         MUIM_Notify, MUIA_NList_Active,        MUIV_EveryTime, obj,                     1, MUIM_LibrariesWin_ListChange);
        DoMethod(liblist,         MUIM_Notify, MUIA_NList_DoubleClick,   MUIV_EveryTime, obj,                     1, MUIM_LibrariesWin_More);
        DoMethod(updateButton,    MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_LibrariesWin_Update);
        DoMethod(printButton,     MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_LibrariesWin_Print);
        DoMethod(removeButton,    MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_LibrariesWin_Remove);
        DoMethod(moreButton,      MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_LibrariesWin_More);
        DoMethod(closeButton,     MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_LibrariesWin_Close);
        DoMethod(priorityButton,  MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_LibrariesWin_Priority);
        DoMethod(funcButton,      MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_LibrariesWin_Functions);
        DoMethod(exitButton,      MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     3, MUIM_Set, MUIA_Window_CloseRequest, TRUE);
        DoMethod(liblist,         MUIM_NList_Sort3, MUIV_NList_Sort3_SortType_1, MUIV_NList_SortTypeAdd_None, MUIV_NList_Sort3_SortType_Both);
    }

    return (ULONG)obj;
}

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

    set(obj, MUIA_Window_Open, FALSE);
    DoMethod(lwd->lwd_LibraryList, MUIM_NList_Clear);

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

static ULONG __saveds mUpdate( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct LibrariesWinData *lwd = INST_DATA(cl, obj);
    struct LibrariesCallbackUserData ud;

    ApplicationSleep(TRUE);
    set(lwd->lwd_LibraryList, MUIA_NList_Quiet, TRUE);
    DoMethod(lwd->lwd_LibraryList, MUIM_NList_Clear);

    ud.ud_List = lwd->lwd_LibraryList;
    ud.ud_Count = 0;

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

    SetCountText(lwd->lwd_LibraryCount, ud.ud_Count);
    MySetContents(lwd->lwd_LibraryText, "");

    set(lwd->lwd_LibraryList, MUIA_NList_Quiet, FALSE);
    set(lwd->lwd_PriorityButton, MUIA_Disabled, TRUE);
    set(lwd->lwd_RemoveButton, MUIA_Disabled, TRUE);
    set(lwd->lwd_CloseButton, MUIA_Disabled, TRUE);
    set(lwd->lwd_MoreButton, MUIA_Disabled, TRUE);
    set(lwd->lwd_FunctionsButton, MUIA_Disabled, TRUE);
    ApplicationSleep(FALSE);

    return 0;
}

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

    return 0;
}

static ULONG __saveds mRemove( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct LibrariesWinData *lwd = INST_DATA(cl, obj);
    struct LibraryEntry *le;

    if (le = (struct LibraryEntry *)GetActiveEntry(lwd->lwd_LibraryList)) {
        if (MyRequest(msgYesNo, msgWantToRemoveLibrary, le->le_Name)) {
            MyDoCommand("RemoveLibrary \"%s\"", le->le_Name);
            DoMethod(obj, MUIM_LibrariesWin_Update);
        }
    }

    return 0;
}

static ULONG __saveds mPriority( struct IClass *cl,
                                 Object *obj,
                                 Msg msg )
{
    struct LibrariesWinData *lwd = INST_DATA(cl, obj);
    struct LibraryEntry *le;

    if (le = (struct LibraryEntry *)GetActiveEntry(lwd->lwd_LibraryList)) {
        LONG pri;

        pri = atol(le->le_Pri);
        if (GetPriority(le->le_Name, &pri)) {
            if (MyDoCommand("SetPriority LIBRARY \"%s\" %ld", le->le_Name, pri)) {
                _snprintf(le->le_Pri, sizeof(le->le_Pri), "%4ld", pri);
                RedrawActiveEntry(lwd->lwd_LibraryList);
            }
        }
    }

    return 0;
}

static ULONG __saveds mClose( struct IClass *cl,
                              Object *obj,
                              Msg msg )
{
    struct LibrariesWinData *lwd = INST_DATA(cl, obj);
    struct LibraryEntry *le;

    if (le = (struct LibraryEntry *)GetActiveEntry(lwd->lwd_LibraryList)) {
        LONG cnt;

        if (IsDec(le->le_OpenCount, &cnt) && cnt > 0) {
            ULONG answer;

            if (answer = MyRequest(msgOnceAllCancel, msgWantToCloseLibrary, le->le_Name)) {
                if (answer == 1) {
                    MyDoCommand("CloseLibrary %s", le->le_Address);
                    cnt--;
                } else if (answer == 2) {
                    while (cnt > 0) {
                        if (!MyDoCommand("CloseLibrary %s", le->le_Address)) break;
                        cnt--;
                    }
                }

                _snprintf(le->le_OpenCount, sizeof(le->le_OpenCount), "%6lD", cnt);
                RedrawActiveEntry(lwd->lwd_LibraryList);
            }
        } else {
            MyRequest(msgErrorContinue, msgLibOpenCountIsZero);
        }
    }

    return 0;
}

static ULONG __saveds mMore( struct IClass *cl,
                             Object *obj,
                             Msg msg )
{
    struct LibrariesWinData *lwd = INST_DATA(cl, obj);
    struct LibraryEntry *le;

    if (le = (struct LibraryEntry *)GetActiveEntry(lwd->lwd_LibraryList)) {
        APTR detailWin;

        if (detailWin = LibrariesDetailWindowObject,
                MUIA_Window_ParentWindow, obj,
                MUIA_Window_MaxChildWindowCount, (opts.SingleWindows) ? 1 : 0,
            End) {
            set(detailWin, MUIA_LibrariesDetailWin_Library, le);
            set(detailWin, MUIA_Window_Open, TRUE);
        }
    }

    return 0;
}

static ULONG __saveds mFunctions( struct IClass *cl,
                                  Object *obj,
                                  Msg msg )
{
    struct LibrariesWinData *lwd = INST_DATA(cl, obj);
    struct LibraryEntry *le;

    if (le = (struct LibraryEntry *)GetActiveEntry(lwd->lwd_LibraryList)) {
        APTR funcWin;

        if (funcWin = FunctionsWindowObject,
                MUIA_Window_ParentWindow, obj,
            End) {
            DoMethod(funcWin, MUIM_FunctionsWin_ShowFunctions, MUIV_FunctionsWin_NodeType_Library, le->le_Addr, le->le_Name);
        }
    }

    return 0;
}

static ULONG __saveds mListChange( struct IClass *cl,
                                   Object *obj,
                                   Msg msg )
{
    struct LibrariesWinData *lwd = INST_DATA(cl, obj);
    struct LibraryEntry *le;

    if (le = (struct LibraryEntry *)GetActiveEntry(lwd->lwd_LibraryList)) {
        MySetContents(lwd->lwd_LibraryText, "%s \"%s\"", le->le_Address, le->le_Name);
        set(lwd->lwd_PriorityButton, MUIA_Disabled, FALSE);
        set(lwd->lwd_RemoveButton, MUIA_Disabled, FALSE);
        set(lwd->lwd_CloseButton, MUIA_Disabled, FALSE);
        if (!clientstate) {
            set(lwd->lwd_MoreButton, MUIA_Disabled, FALSE);
            set(lwd->lwd_FunctionsButton, MUIA_Disabled, FALSE);
        }
    }

    return 0;
}

ULONG __asm __saveds LibrariesWinDispatcher( 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_LibrariesWin_Update:     return (mUpdate(cl, obj, (APTR)msg));
        case MUIM_LibrariesWin_Print:      return (mPrint(cl, obj, (APTR)msg));
        case MUIM_LibrariesWin_Remove:     return (mRemove(cl, obj, (APTR)msg));
        case MUIM_LibrariesWin_Priority:   return (mPriority(cl, obj, (APTR)msg));
        case MUIM_LibrariesWin_Close:      return (mClose(cl, obj, (APTR)msg));
        case MUIM_LibrariesWin_More:       return (mMore(cl, obj, (APTR)msg));
        case MUIM_LibrariesWin_Functions:  return (mFunctions(cl, obj, (APTR)msg));
        case MUIM_LibrariesWin_ListChange: return (mListChange(cl, obj, (APTR)msg));
    }

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

void PrintLibraries( char *filename )
{
    BPTR handle;

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

    HandlePrintStop();
}

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

