/**
 * 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 LocksCallbackUserData {
    APTR ud_List;
    APTR ud_Application;
    APTR ud_Status;
    UBYTE *ud_Pattern;
    ULONG ud_Shown;
    ULONG ud_Hidden;
    BPTR ud_Handle;
};

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

MakeHook(locklist_con2hook, locklist_con2func);

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

    return 0;
}

MakeHook(locklist_des2hook, locklist_des2func);

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

    if (le) {
        msg->strings[0] = le->le_Address;
        msg->strings[1] = le->le_Access;
        msg->strings[2] = le->le_Path;
    } else {
        msg->strings[0] = MUIX_B "Address";
        msg->strings[1] = MUIX_B "Access";
        msg->strings[2] = MUIX_B "Path";
    }

    return 0;
}

MakeHook(locklist_dsp2hook, locklist_dsp2func);

static LONG locklist_cmp2colfunc( struct LockEntry *le1,
                                  struct LockEntry *le2,
                                  ULONG column )
{
    switch (column) {
        case 0: return stricmp(le1->le_Address, le2->le_Address);
        case 1: return stricmp(le1->le_Access, le2->le_Access);
        case 2: return stricmp(le1->le_Path, le2->le_Path);
    }
}

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

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

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

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

    return cmp;
}

MakeHook(locklist_cmp2hook, locklist_cmp2func);

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

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

        tbFreeVecPooled(globalPool, le);
    }
}

static void IterateList( void (* callback)( struct LockEntry *le, void *userData ),
                         void *userData )
{
    UBYTE *ppattern, *tmp;
    struct LockEntry *le;

    ppattern = tbAllocVecPooled(globalPool, PATTERN_LENGTH);
    tmp = tbAllocVecPooled(globalPool, PATH_LENGTH);
    le = tbAllocVecPooled(globalPool, sizeof(struct LockEntry));

    if (ppattern != NULL && tmp != NULL && le != NULL) {
        struct LocksCallbackUserData *ud = (struct LocksCallbackUserData *)userData;
        BOOL patternOk;

        if (ud->ud_Pattern == NULL || (ud->ud_Pattern != NULL && ud->ud_Pattern[0] == 0x00)) {
            patternOk = (ParsePatternNoCase("#?", ppattern, PATTERN_LENGTH) != -1);
        } else {
            patternOk = (ParsePatternNoCase(ud->ud_Pattern, ppattern, PATTERN_LENGTH) != -1);
        }

        if (patternOk) {
            struct FileInfoBlock *fib;

            if (fib = AllocDosObject(DOS_FIB, TAG_DONE)) {
                struct DosList *dev;

                NoReqOn();

                dev = DOSLIST;
                while (dev) {
                    if (dev->dol_Type == DLT_VOLUME) {
                        BPTR lock;

                        b2cstrn(dev->dol_Name, tmp, PATH_LENGTH);
                        strcat (tmp, ":");

                        if (lock = Lock(tmp, SHARED_LOCK)) {
                            BOOL erster;
                            BPTR next;

                            erster = TRUE;
                            next = (BPTR)NULL;

                            while ((next != (BPTR)NULL && next != lock) || erster) {
                                struct FileLock *flock;

                                if (erster) next = lock;
                                flock = (struct FileLock *)BADDR(next);

                                if (TypeOfMem(flock) == 0) break;
                                if (flock->fl_Volume != MKBADDR(dev)) break;

                                if (Examine(next, fib)) {
                                    if (erster) {
                                        stccpy(le->le_Access, "OWN", sizeof(le->le_Access));
                                    } else if (flock->fl_Access == SHARED_LOCK) {
                                        stccpy(le->le_Access, "SHARED", sizeof(le->le_Access));
                                    } else if (flock->fl_Access == EXCLUSIVE_LOCK) {
                                        stccpy(le->le_Access, "EXCLUSIVE", sizeof(le->le_Access));
                                    } else {
                                        _snprintf(le->le_Access, sizeof(le->le_Access), "invalid (%ld)", flock->fl_Access);
                                    }

                                    if (flock->fl_Access == EXCLUSIVE_LOCK) {
                                        LONG oldAccess;

                                        Forbid();

                                        oldAccess = flock->fl_Access;
                                        flock->fl_Access = SHARED_LOCK;

                                        NameFromLock(next, le->le_Path, sizeof(le->le_Path));

                                        flock->fl_Access = oldAccess;

                                        Permit();
                                    } else {
                                        NameFromLock(next, le->le_Path, sizeof(le->le_Path));
                                    }

                                    if (MatchPatternNoCase(ppattern, le->le_Path)) {
                                        le->le_Addr = next;
                                        _snprintf(le->le_Address, sizeof(le->le_Access), "$%08lx", flock);
                                        ud->ud_Shown++;
                                    } else {
                                        le->le_Addr = (BPTR)NULL;
                                        ud->ud_Hidden++;
                                    }

                                    callback(le, userData);
                                }

                                erster = FALSE;
                                next = flock->fl_Link;
                            }

                            UnLock(lock);
                        }
                    }

                    dev = (struct DosList *)BADDR(dev->dol_Next);
                }

                NoReqOff();

                FreeDosObject(DOS_FIB, fib);
            }
        }
    }

    if (ppattern) tbFreeVecPooled(globalPool, ppattern);
    if (tmp) tbFreeVecPooled(globalPool, tmp);
    if (le) tbFreeVecPooled(globalPool, le);
}

static void UpdateCallback( struct LockEntry *le,
                            void *userData )
{
    struct LocksCallbackUserData *ud = (struct LocksCallbackUserData *)userData;

    if (le && le->le_Addr) InsertSortedEntry(ud->ud_List, le);

    if ((ud->ud_Shown + ud->ud_Hidden) % 32 == 0) {
        MySetContents(ud->ud_Status, "%ld shown, %ld hidden...", ud->ud_Shown, ud->ud_Hidden);
        DoMethod(ud->ud_Application, MUIM_Application_InputBuffered);
    }
}

static void PrintCallback( struct LockEntry *le,
                           void *userData )
{
    struct LocksCallbackUserData *ud = (struct LocksCallbackUserData *)userData;

    if (le->le_Addr) PrintFOneLine(ud->ud_Handle, " %s  %-12s  %s\n", le->le_Address, le->le_Access, le->le_Path);
}

static void SendCallback( struct LockEntry *le,
                          void *userData )
{
    if (le->le_Addr) SendEncodedEntry((UBYTE *)le, sizeof(struct LockEntry));
}

static void CountCallback( struct LockEntry *le,
                           void *userData )
{
    // no op, locks are already counted in IterateList()
}

static void RemoveCallback( struct LockEntry *le,
                            void *userData )
{
    if (le->le_Addr) UnLock(le->le_Addr);
}

static ULONG __saveds mNew( struct IClass *cl,
                            Object *obj,
                            struct opSet *msg )
{
    APTR locklist, locktext, locktext2, lockpattern, updateButton, printButton, removeButton, exitButton;

    if (obj = (Object *)DoSuperNew(cl, obj,
        MUIA_HelpNode, LocksText,
        MUIA_Window_ID, MakeID('L','O','C','K'),
        WindowContents, VGroup,

            Child, locklist = MyNListviewObject(MakeID('L','O','L','V'), "BAR,BAR,BAR", &locklist_con2hook, &locklist_des2hook, &locklist_dsp2hook, &locklist_cmp2hook, TRUE),

            Child, locktext = MyTextObject5(SPACE40),

            Child, locktext2 = MyTextObject(),

            Child, HGroup,
                Child, KeyLabel(txtPattern, 'p'),
                Child, lockpattern = StringObject,
                    MUIA_String_Contents, "#?",
                    MUIA_ControlChar, 'p',
                    MUIA_String_MaxLen, TEXT_LENGTH,
                    MUIA_CycleChain, TRUE,
                    StringFrame,
                End,
            End,

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

        lwd->lwd_LockList = locklist;
        lwd->lwd_LockText = locktext;
        lwd->lwd_StatusText = locktext2;
        lwd->lwd_PatternString = lockpattern;
        lwd->lwd_RemoveButton = removeButton;

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

        set(obj, MUIA_Window_Title, MyGetWindowTitle("LOCKS", lwd->lwd_Title, sizeof(lwd->lwd_Title)));
        set(obj, MUIA_Window_ActiveObject, lockpattern);
        set(locklist, MUIA_NList_MultiSelect, MUIV_NList_MultiSelect_Default);
        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(locklist,     MUIM_Notify, MUIA_NList_Active,        MUIV_EveryTime, obj,                     1, MUIM_LocksWin_ListChange);
        DoMethod(lockpattern,  MUIM_Notify, MUIA_String_Acknowledge,  MUIV_EveryTime, obj,                     1, MUIM_LocksWin_Update);
        DoMethod(updateButton, MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_LocksWin_Update);
        DoMethod(printButton,  MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_LocksWin_Print);
        DoMethod(removeButton, MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_LocksWin_Remove);
        DoMethod(exitButton,   MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     3, MUIM_Set, MUIA_Window_CloseRequest, TRUE);
        DoMethod(locklist,     MUIM_NList_Sort3, MUIV_NList_Sort3_SortType_2, 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 LocksWinData *lwd = INST_DATA(cl, obj);

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

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

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

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

    ud.ud_List = lwd->lwd_LockList;
    get(obj, MUIA_ApplicationObject, &ud.ud_Application);
    ud.ud_Status = lwd->lwd_StatusText;
    ud.ud_Shown = 0;
    ud.ud_Hidden = 0;
    get(lwd->lwd_PatternString, MUIA_String_Contents, &ud.ud_Pattern);

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

    lwd->lwd_LocksShown = ud.ud_Shown;
    lwd->lwd_LocksHidden = ud.ud_Hidden;
    MySetContents(lwd->lwd_StatusText, msgLocksShownHiddenDone, ud.ud_Shown, ud.ud_Hidden);
    set(obj, MUIA_Window_ActiveObject, lwd->lwd_LockList);

    set(lwd->lwd_LockList, MUIA_NList_Quiet, FALSE);
    set(lwd->lwd_RemoveButton, MUIA_Disabled, TRUE);
    ApplicationSleep(FALSE);

    return 0;
}

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

    return 0;
}

static ULONG __saveds mRemove( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct LocksWinData *lwd = INST_DATA(cl, obj);
    UBYTE *tmp;

    if (tmp = tbAllocVecPooled(globalPool, FILENAME_LENGTH)) {
        ULONG id = MUIV_NList_NextSelected_Start;
        ULONG remMode = 1;

        while (TRUE) {
            struct LockEntry *le;

            DoMethod(lwd->lwd_LockList, MUIM_NList_NextSelected, &id);
            if (id == MUIV_NList_NextSelected_End) break;
            if (remMode == 0) break;

            DoMethod(lwd->lwd_LockList, MUIM_NList_GetEntry, id, &le);
            if (le) {
                ULONG pos;

                if (remMode != 2) remMode = MyRequest(msgYesAllNoAbort, msgWantToRemoveLock, le->le_Path);

                get(lwd->lwd_LockList, MUIA_NList_Active, &pos);
                if (pos == id) {
                    set(lwd->lwd_LockList, MUIA_NList_Active, MUIV_List_Active_Off);
                    set(lwd->lwd_RemoveButton, MUIA_Disabled, TRUE);
                }

                if (remMode == 1 || remMode == 2) {
                    if (MyDoCommand("RemoveLock %s", le->le_Address)) {
                        DoMethod(lwd->lwd_LockList, MUIM_NList_Remove, id);
                        id = MUIV_NList_NextSelected_Start;

                        lwd->lwd_LocksShown--;
                        _snprintf(tmp, FILENAME_LENGTH, msgLocksShownHiddenDone, lwd->lwd_LocksShown, lwd->lwd_LocksHidden);
                        set(lwd->lwd_StatusText, MUIA_Text_Contents, tmp);
                    }
                } else if (remMode == 3) {
                    DoMethod(lwd->lwd_LockList, MUIM_NList_Select, id, MUIV_NList_Select_Off, NULL);
                    id = MUIV_NList_NextSelected_Start;
                }
            }
        }

        tbFreeVecPooled(globalPool, tmp);
    }

    return 0;
}

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

    if (le = (struct LockEntry *)GetActiveEntry(lwd->lwd_LockList)) {
        MySetContents(lwd->lwd_LockText, "%s \"%s\"", le->le_Address, le->le_Path);
        set(lwd->lwd_RemoveButton, MUIA_Disabled, FALSE);
    }

    return 0;
}

ULONG __asm __saveds LocksWinDispatcher( 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_LocksWin_Update:     return (mUpdate(cl, obj, (APTR)msg));
        case MUIM_LocksWin_Print:      return (mPrint(cl, obj, (APTR)msg));
        case MUIM_LocksWin_Remove:     return (mRemove(cl, obj, (APTR)msg));
        case MUIM_LocksWin_ListChange: return (mListChange(cl, obj, (APTR)msg));
    }

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

void PrintLocks( char *filename )
{
    BPTR handle;

    if (handle = HandlePrintStart(filename)) {
        struct LocksCallbackUserData ud;

        ud.ud_Shown = 0;
        ud.ud_Hidden = 0;
        ud.ud_Pattern = "#?";
        ud.ud_Handle = handle;

        PrintFOneLine(handle, "\n  Address   Access         Path\n\n");
        IterateList(PrintCallback, &ud);
    }

    HandlePrintStop();
}

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

ULONG CountLocks( UBYTE *pattern )
{
    struct LocksCallbackUserData ud;

    ud.ud_Pattern = pattern;
    ud.ud_Shown = 0;
    ud.ud_Hidden = 0;

    IterateList(CountCallback, &ud);

    return ud.ud_Shown;
}

void RemoveLock( UBYTE *addr )
{
    struct LocksCallbackUserData ud;

    ud.ud_Pattern = addr;
    ud.ud_Shown = 0;
    ud.ud_Hidden = 0;

    IterateList(RemoveCallback, &ud);
}

