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

struct CPUCheckChangeMessage {
    ULONG methodID;
    ULONG value;
};

BOOL timer_ticking = FALSE;

struct ScoutPatchSemaphore {
    struct SignalSemaphore sps_Semaphore;
    ULONG sps_SemaphoreSize;
    UWORD sps_Version;
    UWORD sps_Revision;
    UBYTE sps_SemaphoreName[32];
    ULONG sps_CodeSize;
    ULONG sps_Reserved[5];
    UBYTE sps_Code[1];
};

struct ScoutPatchSemaphore *patchsem = NULL;

VOID __asm (*cleartaskdata) (VOID) = NULL;
LONG  __asm (*gettaskdata) (register __d0 struct Task *) = NULL;
struct Task * __asm (*getaddedtask) (VOID) = NULL;
char  *switchstate = NULL;

LONG totalmicros,cpuseconds = 1,cpumicros = 0;

#define SCOUT_SEMA_NAME         "« Scout's CPU patch »"
#define CHEATTASK_STACKSIZE     1024
#define CHEATTASK_NAME          "« Scout's cheat task »"
struct Task *cheattask = NULL;

#define MAX_TASK_NUM            1024

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

MakeHook(taskslist_con2hook, taskslist_con2func);

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

    return 0;
}

MakeHook(taskslist_des2hook, taskslist_des2func);

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

    if (te) {
        msg->strings[0] = te->te_Address;
        msg->strings[1] = te->te_Name;
        msg->strings[2] = te->te_CPU;
        msg->strings[3] = te->te_Type;
        msg->strings[4] = te->te_Pri;
        msg->strings[5] = te->te_Num;
        msg->strings[6] = te->te_State;
        msg->strings[7] = te->te_SigWait;
        msg->strings[8] = te->te_FreeStack;
    } else {
        msg->strings[0] = MUIX_B "Address";
        msg->strings[1] = MUIX_B "ln_Name";
        msg->strings[2] = MUIX_B "CPU %";
        msg->strings[3] = MUIX_B "ln_Type";
        msg->strings[4] = MUIX_B "ln_Pri";
        msg->strings[5] = MUIX_B "NUM";
        msg->strings[6] = MUIX_B "State";
        msg->strings[7] = MUIX_B "SigWait";
        msg->strings[8] = MUIX_B "Free Stack";
    }

    return 0;
}

MakeHook(taskslist_dsp2hook, taskslist_dsp2func);

static LONG taskslist_cmp2colfunc( struct TaskEntry *te1,
                                   struct TaskEntry *te2,
                                   ULONG column )
{
    LONG pri1, pri2;

    switch (column) {
        case 0: return stricmp(te1->te_Address, te2->te_Address);
        case 1: return stricmp(te1->te_Name, te2->te_Name);
        case 2: return stricmp(te1->te_CPU, te2->te_CPU);
        case 3: return stricmp(te1->te_Type, te2->te_Type);
        case 4: IsDec(te1->te_Pri, &pri1); IsDec(te2->te_Pri, &pri2); return pri2 - pri1;
        case 5: return stricmp(te1->te_Num, te2->te_Num);
        case 6: return stricmp(te1->te_State, te2->te_State);
        case 7: return stricmp(te1->te_SigWait, te2->te_SigWait);
        case 8: return stricmp(te1->te_FreeStack, te2->te_FreeStack);
    }
}

static __asm __saveds LONG taskslist_cmp2func(register __a2 Object *obj, register __a1 struct NList_CompareMessage *msg, register __a0 struct Hook *hook)
{
    LONG cmp;
    struct TaskEntry *te1, *te2;
    ULONG col1, col2;

    te1 = (struct TaskEntry *)msg->entry1;
    te2 = (struct TaskEntry *)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 = taskslist_cmp2colfunc(te2, te1, col1);
    } else {
        cmp = taskslist_cmp2colfunc(te1, te2, col1);
    }

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

    if (msg->sort_type2 & MUIV_NList_TitleMark2_TypeMask) {
        cmp = taskslist_cmp2colfunc(te2, te1, col2);
    } else {
        cmp = taskslist_cmp2colfunc(te1, te2, col2);
    }

    return cmp;
}

MakeHook(taskslist_cmp2hook, taskslist_cmp2func);

static __asm __saveds void cpuinterval_callfunc( register __a0 struct Hook *hook,
                                                 register __a2 Object *obj,
                                                 register __a1 UBYTE **contents )
{
    struct TasksWinData *twd = INST_DATA(OCLASS(obj), obj);
    ULONG i;
    LONG micros = 0;
    UBYTE text[8];

    stccpy(text, *contents, sizeof(text));

    i = 0;
    while (text[i] != '\0' && text[i] != '.') i++;

    if (text[i] == '.') {
        ULONG t, j;

        t = 1;
        text[i] = '\0';
        while (isdigit(text[++i])) {
            if (j = (ULONG)(text[i] - '0')) {
                t *= 10;
                micros += (1000000 / t) * j;
            }
        }
    }

    cpumicros = micros;
    cpuseconds = atol((char *)&text);
    strcpy(updatetimetext, *contents);

    if (twd->twd_TimerHandlerAdded) {
        DoMethod(twd->twd_Application, MUIM_Application_RemInputHandler, &twd->twd_TimerHandler);
        twd->twd_TimerHandler.ihn_Millis = (cpuseconds * 1000) + (cpumicros / 1000);
        DoMethod(twd->twd_Application, MUIM_Application_AddInputHandler, &twd->twd_TimerHandler);
    }
}

MakeHook(cpuinterval_callhook, cpuinterval_callfunc);

static __asm __saveds ULONG realstring_editfunc( register __a0 struct Hook *hook, register __a2 struct SGWork *sgw, register __a1 ULONG *msg )
{
    ULONG return_code;
    ULONG i = 0, punkte = 0, notzeros = 0;

    return_code = ~0L;

    if (*msg == SGH_KEY) {
        if (sgw->EditOp == EO_REPLACECHAR || sgw->EditOp == EO_INSERTCHAR) {
            while (sgw->WorkBuffer[i]) {
                if (sgw->WorkBuffer[i++] == '.') punkte++;
            }
            if (punkte > 1 || (!isdigit(sgw->Code) && sgw->Code != '.')) {
                sgw->Actions |= SGA_BEEP;
                sgw->Actions &= ~SGA_USE;
            }
        } else if (sgw->EditOp == EO_ENTER) {
            while (sgw->WorkBuffer[i]) {
                if (sgw->WorkBuffer[i] >= '1' && sgw->WorkBuffer[i] <= '9') notzeros++;
                i++;
            }
            if (notzeros > 0) {
                sgw->Actions |= SGA_BEEP;
                sgw->Actions &= ~SGA_END;
            }
        }
    } else {
        return_code = 0;
    }

    return return_code;
}

MakeHook(realstring_edithook, realstring_editfunc);

static void cheattask_func( void )
{
   while (TRUE) {}
}

static struct Task *AddCheatTask( void )
{
    struct Task *result;

    if (result = tbAllocVecPooled(globalPool, CHEATTASK_STACKSIZE + sizeof(struct Task))) {
        result->tc_SPLower = (void *)(sizeof(struct Task) + (ULONG)result);
        result->tc_SPUpper = (void *)(CHEATTASK_STACKSIZE + sizeof(struct Task) + (ULONG)result);
        result->tc_SPReg = result->tc_SPUpper;

        result->tc_Node.ln_Name = CHEATTASK_NAME;
        result->tc_Node.ln_Pri = -128;
        result->tc_Node.ln_Type = NT_TASK;

        AddTask(result, (APTR)&cheattask_func, NULL);
    }

    return result;
}

static void RemoveCheatTask( void )
{
    if (cheattask) {
        RemTask(cheattask);
        tbFreeVecPooled(globalPool, cheattask);
        cheattask = NULL;
    }
}

static struct Task *TaskExists( struct Task *tasktofind )
{
    struct Task *task, *result = NULL;

    if (tasktofind == myprocess) return myprocess;

    Forbid();

    task = FIRSTTASKREADY;
    while (task->tc_Node.ln_Succ) {
        if (task == tasktofind) {
            result = task;
            break;
        }
        task = (struct Task *)task->tc_Node.ln_Succ;
    }

    if (result == NULL) {
        task = FIRSTTASKWAIT;
        while (task->tc_Node.ln_Succ) {
            if (task == tasktofind) {
                result = task;
                break;
            }
            task = (struct Task *) task->tc_Node.ln_Succ;
        }
    }

    Permit();

    return result;
}

UBYTE *GetTaskState( UBYTE state )
{
    switch (state) {
        case TS_FROZEN:  return txtTaskStateFrozen;
        case TS_ADDED:   return txtTaskStateAdded;
        case TS_RUN:     return txtTaskStateRunning;
        case TS_READY:   return txtTaskStateReady;
        case TS_WAIT:    return txtTaskStateWaiting;
        case TS_EXCEPT:  return txtTaskStateExcept;
        case TS_REMOVED: return txtTaskStateRemoved;
        case TS_INVALID: return txtTaskStateInvalid;
        default:         return txtTaskStateUnknown;
    }
}

UBYTE *GetNodeType( UBYTE type )
{
    switch (type) {
        case NT_TASK:         return txtNodeTypeTask;
        case NT_INTERRUPT:    return txtNodeTypeInterrupt;
        case NT_DEVICE:       return txtNodeTypeDevice;
        case NT_MSGPORT:      return txtNodeTypeMsgPort;
        case NT_MESSAGE:      return txtNodeTypeMessage;
        case NT_FREEMSG:      return txtNodeTypeFreeMsg;
        case NT_REPLYMSG:     return txtNodeTypeReplyMsg;
        case NT_RESOURCE:     return txtNodeTypeResource;
        case NT_LIBRARY:      return txtNodeTypeLibrary;
        case NT_MEMORY:       return txtNodeTypeMemory;
        case NT_SOFTINT:      return txtNodeTypeSoftInt;
        case NT_FONT:         return txtNodeTypeFont;
        case NT_PROCESS:      return txtNodeTypeProcess;
        case NT_SEMAPHORE:    return txtNodeTypeSemaphore;
        case NT_SIGNALSEM:    return txtNodeTypeSignalSem;
        case NT_BOOTNODE:     return txtNodeTypeBootNode;
        case NT_KICKMEM:      return txtNodeTypeKickMem;
        case NT_GRAPHICS:     return txtNodeTypeGraphics;
        case NT_DEATHMESSAGE: return txtNodeTypeDeathMessage;
        case NT_USER:         return txtNodeTypeUser;
        case NT_EXTENDED:     return txtNodeTypeExtended;
        case NT_UNKNOWN:      return txtNodeTypeUnknown;
        default:              return txtNodeTypeInvalid;
    };
}

UBYTE *GetTaskName( struct Task *task )
{
   if (task) {
        struct Process *pr = (struct Process *)task;

        if (task->tc_Node.ln_Type == NT_PROCESS && pr->pr_CLI != (BPTR)NULL) {
            struct CommandLineInterface *cli;
            UBYTE *pointer;

            cli = (struct CommandLineInterface *)BADDR(pr->pr_CLI);
            pointer = BADDR(cli->cli_CommandName);

            if (pointer != NULL && pointer[0] != 0x00) {
                ULONG i = 0;

                pointer++;
                while (pointer[i]) {
                    if (pointer[i++] != ' ') return pointer;
                }
            }
        }

        if ((task->tc_Node.ln_Type == NT_PROCESS || task->tc_Node.ln_Type == NT_TASK) && task->tc_Node.ln_Succ != NULL) {
            return task->tc_Node.ln_Name;
        } else {
            return txtNoTask;
        }
    }

    return NULL;
}

static void GetCPUUsage( struct Task *task,
                         struct TaskEntry *te )
{
    if (totalmicros = *((ULONG *) ((char *) &TotalMicros1 + 4 - (char *) &MySwitch + CodeAddress))) {
        ULONG cpu, zehntel;

        cpu = (*gettaskdata)(task);

        zehntel = UDivMod32(totalmicros, 10000);
        if (zehntel == 0) zehntel = 1;

        zehntel = UDivMod32(cpu, zehntel);
        cpu = UDivMod32(zehntel, 100);
        zehntel -= UMult32(cpu, 100);
        if (cpu > 99) {
            cpu = 100;
            zehntel = 0;
        }
        _snprintf(te->te_CPU, sizeof(te->te_CPU), "%3ld.%02ld", cpu, zehntel);
    }
}

static void UpdateTaskEntry( struct Task *task,
                             struct TaskEntry *te,
                             BOOL cpuflag )
{
    LONG stackFree;

    _snprintf(te->te_Pri, sizeof(te->te_Pri), "%4ld", task->tc_Node.ln_Pri);
    stccpy(te->te_State, GetTaskState((UBYTE)task->tc_State), sizeof(te->te_State));
    _snprintf(te->te_SigWait, sizeof(te->te_SigWait), "$%08lx", task->tc_SigWait);
    stackFree = (char *)task->tc_SPReg - (char *)task->tc_SPLower;
    if (stackFree >= 512) {
        _snprintf(te->te_FreeStack, sizeof(te->te_FreeStack), "%8lD", stackFree);
    } else {
        _snprintf(te->te_FreeStack, sizeof(te->te_FreeStack), MUIX_PH "%8lD" MUIX_PT, stackFree);
    }

    stccpy(te->te_CPU, "  0.00", sizeof(te->te_CPU));

    if (cpuflag && patchsem != NULL) GetCPUUsage(task, te);
}

static void GetTaskEntry( struct Task *task,
                          struct TaskEntry *te,
                          BOOL cpuflag )
{
    te->te_Addr = task;

    _snprintf(te->te_Address, sizeof(te->te_Address), "$%08lx", task);
    stccpy(te->te_Name, nonetest(GetTaskName(task)), sizeof(te->te_Name));
    healstring(te->te_Name);
    stccpy(te->te_Type, GetNodeType(task->tc_Node.ln_Type), sizeof(te->te_Type));

    if (task->tc_Node.ln_Type == NT_TASK || ((struct Process *)task)->pr_TaskNum == 0) {
        stccpy(te->te_Num, "---", sizeof(te->te_Num));
    } else {
        _snprintf(te->te_Num, sizeof(te->te_Num), "%3ld", ((struct Process *)task)->pr_TaskNum);
    }

    UpdateTaskEntry(task, te, cpuflag);
}

static void ReceiveList( void (* callback)( struct TaskEntry *te, void *userData ),
                         void *userData )
{
    struct TaskEntry *te;

    if (te = tbAllocVecPooled(globalPool, sizeof(struct TaskEntry))) {
        if (SendDaemon("GetTaskList")) {
            while (ReceiveDecodedEntry((UBYTE *)te, sizeof(struct TaskEntry))) {
                callback(te, userData);
            }
        }

        tbFreeVecPooled(globalPool, te);
    }
}

static void IterateList( void (* callback)( struct TaskEntry *te, void *userData ),
                         void *userData )
{
    ULONG *buffer;
    struct TaskEntry *te;

    buffer = tbAllocVecPooled(globalPool, (MAX_TASK_NUM + 1) * sizeof(ULONG));
    te = tbAllocVecPooled(globalPool, sizeof(struct TaskEntry));

    if (buffer != NULL && te != NULL) {
        struct Task *task;
        LONG cnt, pos;

        Forbid();

        cnt = MAX_TASK_NUM;
        pos = 0;

        buffer[pos++] = (ULONG)SysBase->ThisTask;
        cnt--;

        task = FIRSTTASKREADY;
        while (task->tc_Node.ln_Succ != NULL && cnt > 0) {
            buffer[pos++] = (ULONG)task;
            cnt--;
            task = (struct Task *)task->tc_Node.ln_Succ;
        }

        task = FIRSTTASKWAIT;
        while (task->tc_Node.ln_Succ != NULL && cnt > 0) {
            buffer[pos++] = (ULONG)task;
            cnt--;
            task = (struct Task *)task->tc_Node.ln_Succ;
        }

        Permit();

        cnt = 0;
        while (task = (struct Task *)buffer[cnt]) {
            GetTaskEntry(task, te, FALSE);

            callback(te, userData);

            cnt++;
            if (cnt >= MAX_TASK_NUM) break;
        }
    }

    if (buffer) tbFreeVecPooled(globalPool, buffer);
    if (te) tbFreeVecPooled(globalPool, te);
}

static void UpdateCallback( struct TaskEntry *te,
                            void *userData )
{
    struct TasksCallbackUserData *ud = (struct TasksCallbackUserData *)userData;

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

static void PrintCallback( struct TaskEntry *te,
                           void *userData )
{
    PrintFOneLine((BPTR)userData, " %s %-7.7s %4s %3s %-7.7s %s %s\n", te->te_Address, te->te_Type, te->te_Pri, te->te_Num, te->te_State, te->te_SigWait, te->te_Name);
}

static void SendCallback( struct TaskEntry *te,
                          void *userData )
{
    SendEncodedEntry((UBYTE *)te, sizeof(struct TaskEntry));
}

static ULONG __saveds mNew( struct IClass *cl,
                            Object *obj,
                            struct opSet *msg )
{
    static UBYTE *CYA_CpuUsageText[] = { NULL, NULL, NULL, NULL };

    APTR taskslist, taskstext, taskscount, freezeButton, activateButton, signalButton, breakButton, updateButton, printButton, removeButton, priorityButton, moreButton, exitButton;
    APTR cpucount, cpuCycle, taskstext2;

    CYA_CpuUsageText[0] = txtCPUOff;
    CYA_CpuUsageText[1] = txtCPUFull;
    CYA_CpuUsageText[2] = txtCPUPercent;

    if (obj = (Object *)DoSuperNew(cl, obj,
        MUIA_HelpNode, TasksText,
        MUIA_Window_ID, MakeID('T','A','S','K'),
        WindowContents, VGroup,

            Child, taskslist = MyNListviewObject(MakeID('T','A','L','V'), "BAR,BAR,BAR P=" MUIX_R ",BAR P=" MUIX_C ",BAR P=" MUIX_R ",BAR P=" MUIX_R ",BAR P=" MUIX_C ",BAR,BAR P=" MUIX_R, &taskslist_con2hook, &taskslist_des2hook, &taskslist_dsp2hook, &taskslist_cmp2hook, TRUE),
            Child, MyBelowListview(&taskstext, &taskscount),

            Child, MyVSpace(4),

            Child, HGroup, MUIA_Group_SameSize, TRUE,
                Child, printButton    = MakeButton(txtPrint),
                Child, freezeButton   = MakeButton(txtFreeze),
                Child, activateButton = MakeButton(txtActivate),
                Child, HGroup,
                    Child, MyLabel2(" CPU:"),
                    Child, cpucount = GaugeObject,
                        MUIA_Frame, MUIV_Frame_Gauge,
                        MUIA_Gauge_Horiz, TRUE,
                        MUIA_Gauge_Max, 100,
                        MUIA_Gauge_InfoText, MUIX_PT "-----",
                        MUIA_Gauge_Current, 0,
                    End,
                End,
                Child, cpuCycle = CycleObject,
                    MUIA_CycleChain, TRUE,
                    MUIA_Weight, 0,
                    MUIA_Cycle_Entries, CYA_CpuUsageText,
                    MUIA_Cycle_Active, (opts.CpuDisplay) ? *opts.CpuDisplay : 0,
                End,
                Child, HGroup,
                    Child, MyLabel2(txtSeconds),
                    Child, taskstext2 = StringObject,
                        StringFrame,
                        MUIA_CycleChain, TRUE,
                        MUIA_String_BufferPos, 0,
                        MUIA_String_MaxLen, 6,
                        MUIA_String_EditHook, &realstring_edithook,
                        MUIA_String_Contents, updatetimetext,
                        MUIA_String_Format, MUIV_String_Format_Center,
                    End,
                End,
            End,
            Child, HGroup, MUIA_Group_SameSize, TRUE,
                Child, updateButton   = MakeButton(txtUpdate),
                Child, removeButton   = MakeButton(txtRemove),
                Child, signalButton   = MakeButton(txtSignal),
                Child, breakButton    = MakeButton(txtBreak),
                Child, priorityButton = MakeButton(txtPriority),
                Child, moreButton     = MakeButton(txtMore),
                Child, exitButton     = MakeButton(txtExit),
            End,
        End,
        TAG_MORE, msg->ops_AttrList))
    {
        struct TasksWinData *twd = INST_DATA(cl, obj);
        APTR parent;

        twd->twd_TaskList = taskslist;
        twd->twd_TaskText = taskstext;
        twd->twd_TaskCount = taskscount;
        twd->twd_RemoveButton = removeButton;
        twd->twd_PriorityButton = priorityButton;
        twd->twd_MoreButton = moreButton;
        twd->twd_SignalButton = signalButton;
        twd->twd_BreakButton = breakButton;
        twd->twd_FreezeButton = freezeButton;
        twd->twd_ActivateButton = activateButton;
        twd->twd_CPUGauge = cpucount;
        twd->twd_CPUCycle = cpuCycle;
        twd->twd_CPUCheck = (opts.CpuDisplay) ? *opts.CpuDisplay : 0;
        twd->twd_RefreshString = taskstext;
        twd->twd_TimerHandler.ihn_Object = obj;
        twd->twd_TimerHandler.ihn_Flags = MUIIHNF_TIMER;
        twd->twd_TimerHandler.ihn_Millis = (cpuseconds * 1000) + (cpumicros / 1000);
        twd->twd_TimerHandler.ihn_Method = MUIM_TasksWin_CPUCheckQuick;
        twd->twd_TimerHandlerAdded = FALSE;

        parent = (APTR)GetTagData(MUIA_Window_ParentWindow, (ULONG)NULL, msg->ops_AttrList);
        get(parent, MUIA_ApplicationObject, &twd->twd_Application);

        set(obj, MUIA_Window_Title, MyGetWindowTitle("TASKS & PROCESSES", twd->twd_Title, sizeof(twd->twd_Title)));
        set(obj, MUIA_Window_ActiveObject, taskslist);
        set(removeButton, MUIA_Disabled, TRUE);
        set(priorityButton, MUIA_Disabled, TRUE);
        set(moreButton, MUIA_Disabled, TRUE);
        set(freezeButton, MUIA_Disabled, TRUE);
        set(activateButton, MUIA_Disabled, TRUE);
        set(signalButton, MUIA_Disabled, TRUE);
        set(breakButton, MUIA_Disabled, TRUE);
        if (clientstate) {
            set(cpuCycle, MUIA_Disabled, TRUE);
            set(taskstext2, 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(taskslist,      MUIM_Notify, MUIA_NList_Active,        MUIV_EveryTime, obj,                     1, MUIM_TasksWin_ListChange);
        DoMethod(taskslist,      MUIM_Notify, MUIA_NList_DoubleClick,   MUIV_EveryTime, obj,                     1, MUIM_TasksWin_More);
        DoMethod(updateButton,   MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_TasksWin_Update);
        DoMethod(printButton,    MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_TasksWin_Print);
        DoMethod(removeButton,   MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_TasksWin_Remove);
        DoMethod(moreButton,     MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_TasksWin_More);
        DoMethod(priorityButton, MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_TasksWin_Priority);
        DoMethod(freezeButton,   MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_TasksWin_Freeze);
        DoMethod(activateButton, MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_TasksWin_Activate);
        DoMethod(signalButton,   MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_TasksWin_Signal);
        DoMethod(breakButton,    MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_TasksWin_Break);
        DoMethod(exitButton,     MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     3, MUIM_Set, MUIA_Window_CloseRequest, TRUE);
        DoMethod(cpuCycle,       MUIM_Notify, MUIA_Cycle_Active,        MUIV_EveryTime, obj,                     2, MUIM_TasksWin_CPUCheckChange, MUIV_TriggerValue);
        DoMethod(taskstext2,     MUIM_Notify, MUIA_String_Acknowledge,  MUIV_EveryTime, obj,                     3, MUIM_CallHook, &cpuinterval_callhook, MUIV_TriggerValue);
        DoMethod(taskslist,      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 TasksWinData *twd = INST_DATA(cl, obj);

    if (twd->twd_TimerHandlerAdded) {
        DoMethod(twd->twd_Application, MUIM_Application_RemInputHandler, &twd->twd_TimerHandler);
        twd->twd_TimerHandlerAdded = FALSE;
    }

    set(obj, MUIA_Window_Open, FALSE);
    DoMethod(twd->twd_TaskList, MUIM_NList_Clear);

    RemoveCheatTask();

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

static ULONG __saveds mUpdate( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct TasksWinData *twd = INST_DATA(cl, obj);
    struct TasksCallbackUserData ud;

    ApplicationSleep(TRUE);
    set(twd->twd_TaskList, MUIA_NList_Quiet, TRUE);
    DoMethod(twd->twd_TaskList, MUIM_NList_Clear);

    ud.ud_List = twd->twd_TaskList;
    ud.ud_Count = 0;

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

    SetCountText(twd->twd_TaskCount, ud.ud_Count);
    MySetContents(twd->twd_TaskText, "");

    set(twd->twd_TaskList, MUIA_NList_Quiet, FALSE);
    set(twd->twd_TaskList, MUIA_NList_Active, MUIV_NList_Active_Off);
    set(twd->twd_PriorityButton, MUIA_Disabled, TRUE);
    set(twd->twd_RemoveButton, MUIA_Disabled, TRUE);
    set(twd->twd_MoreButton, MUIA_Disabled, TRUE);
    set(twd->twd_FreezeButton, MUIA_Disabled, TRUE);
    set(twd->twd_ActivateButton, MUIA_Disabled, TRUE);
    set(twd->twd_SignalButton, MUIA_Disabled, TRUE);
    set(twd->twd_BreakButton, MUIA_Disabled, TRUE);

    DoMethod(obj, MUIM_TasksWin_CPUCheckChange, twd->twd_CPUCheck);

    ApplicationSleep(FALSE);

    return 0;
}

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

    return 0;
}

static ULONG __saveds mRemove( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct TasksWinData *twd = INST_DATA(cl, obj);
    struct TaskEntry *te;

    if (te = (struct TaskEntry *)GetActiveEntry(twd->twd_TaskList)) {
        if (MyRequest(msgYesNo, msgWantToRemoveTask, te->te_Type, te->te_Name)) {
            BOOL withPorts;

            withPorts = MyRequest(msgYesNo, msgWantToRemoveTaskWithStuff, te->te_Name);

            MyDoCommand("RemoveTask %s %ld", te->te_Address, withPorts);
            DoMethod(obj, MUIM_TasksWin_Update);
        }
    }

    return 0;
}

static ULONG __saveds mPriority( struct IClass *cl,
                                 Object *obj,
                                 Msg msg )
{
    struct TasksWinData *twd = INST_DATA(cl, obj);
    struct TaskEntry *te;

    if (te = (struct TaskEntry *)GetActiveEntry(twd->twd_TaskList)) {
        LONG pri;

        pri = atol(te->te_Pri);
        if (GetPriority(te->te_Name, &pri)) {
            if (MyDoCommand("SetTaskPri %s %ld", te->te_Address, pri)) {
                _snprintf(te->te_Pri, sizeof(te->te_Pri), "%4ld", pri);
                RedrawActiveEntry(twd->twd_TaskList);
            }
        }
    }

    return 0;
}

static ULONG __saveds mMore( struct IClass *cl,
                             Object *obj,
                             Msg msg )
{
    struct TasksWinData *twd = INST_DATA(cl, obj);
    struct TaskEntry *te;

    if (te = (struct TaskEntry *)GetActiveEntry(twd->twd_TaskList)) {
        struct Task *task;
        Forbid();

        if (task = MyFindTask(te->te_Address)) {
            APTR detailWin;

            if (detailWin = TasksDetailWindowObject,
                    MUIA_Window_ParentWindow, obj,
                    MUIA_Window_MaxChildWindowCount, (opts.SingleWindows) ? 1 : 0,
                End) {
                set(detailWin, MUIA_TasksDetailWin_Task, te);
                set(detailWin, MUIA_Window_Open, TRUE);
            }

            Permit();
        } else {
            Permit();

            MyRequest(msgErrorContinue, msgCantFindTask);
            DoMethod(obj, MUIM_TasksWin_Update);
        }
    }

    return 0;
}

static ULONG __saveds mListChange( struct IClass *cl,
                                   Object *obj,
                                   Msg msg )
{
    struct TasksWinData *twd = INST_DATA(cl, obj);
    struct TaskEntry *te;

    if (te = (struct TaskEntry *)GetActiveEntry(twd->twd_TaskList)) {
        MySetContents(twd->twd_TaskText, "%s \"%s\"", te->te_Address, te->te_Name);
        set(twd->twd_PriorityButton, MUIA_Disabled, FALSE);
        set(twd->twd_RemoveButton, MUIA_Disabled, FALSE);
        if (!clientstate) {
            set(twd->twd_MoreButton, MUIA_Disabled, FALSE);
            set(twd->twd_CPUCycle, MUIA_Disabled, FALSE);
        }
        set(twd->twd_FreezeButton, MUIA_Disabled, FALSE);
        set(twd->twd_ActivateButton, MUIA_Disabled, FALSE);
        set(twd->twd_SignalButton, MUIA_Disabled, FALSE);
        set(twd->twd_BreakButton, MUIA_Disabled, FALSE);
    }

    return 0;
}

static ULONG __saveds mFreeze( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct TasksWinData *twd = INST_DATA(cl, obj);
    struct TaskEntry *te;

    if (te = (struct TaskEntry *)GetActiveEntry(twd->twd_TaskList)) {
        MyDoCommand("FreezeTask %s", te->te_Address);
        stccpy(te->te_State, GetTaskState(TS_FROZEN), sizeof(te->te_State));
        RedrawActiveEntry(twd->twd_TaskList);
    }

    return 0;
}

static ULONG __saveds mActivate( struct IClass *cl,
                                 Object *obj,
                                 Msg msg )
{
    struct TasksWinData *twd = INST_DATA(cl, obj);
    struct TaskEntry *te;

    if (te = (struct TaskEntry *)GetActiveEntry(twd->twd_TaskList)) {
        MyDoCommand("ActivateTask %s", te->te_Address);
        stccpy(te->te_State, GetTaskState(TS_WAIT), sizeof(te->te_State));
        RedrawActiveEntry(twd->twd_TaskList);
    }

    return 0;
}

static ULONG __saveds mSignal( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct TasksWinData *twd = INST_DATA(cl, obj);
    struct TaskEntry *te;

    if (te = (struct TaskEntry *)GetActiveEntry(twd->twd_TaskList)) {
        ULONG sigs;
        APTR sigwin;

        IsUHex(te->te_SigWait, &sigs);
        if (sigwin = SignalWindowObject,
                MUIA_Window_ParentWindow, obj,
            End) {
            if (DoMethod(sigwin, MUIM_SignalWin_GetSignals, te->te_Name, &sigs)) {
                if (MyDoCommand("SignalTask %s $%08lx", te->te_Address, sigs)) {
                    Delay(25);
                    RedrawActiveEntry(twd->twd_TaskList);
                } else {
                    DoMethod(obj, MUIM_TasksWin_Update);
                }
            }
        }
    }

    return 0;
}

static ULONG __saveds mBreak( struct IClass *cl,
                              Object *obj,
                              Msg msg )
{
    struct TasksWinData *twd = INST_DATA(cl, obj);
    struct TaskEntry *te;

    if (te = (struct TaskEntry *)GetActiveEntry(twd->twd_TaskList)) {
        if (MyDoCommand("BreakTask %s", te->te_Address)) {
            Delay(25);
            RedrawActiveEntry(twd->twd_TaskList);
        } else {
            DoMethod(obj, MUIM_TasksWin_Update);
        }
    }

    return 0;
}

static ULONG __saveds mCPUCheckChange( struct IClass *cl,
                                       Object *obj,
                                       Msg msg )
{
    struct TasksWinData *twd = INST_DATA(cl, obj);
    struct CPUCheckChangeMessage *cccm = (struct CPUCheckChangeMessage *)msg;

    twd->twd_CPUCheck = cccm->value;

    if (cccm->value != 0) {
        ULONG semasize, codesize;

        Forbid();
        patchsem = (struct ScoutPatchSemaphore *)FindSemaphore(SCOUT_SEMA_NAME);
        Permit();

        codesize = (ULONG)((char *)&CodeAddress - (char *)&MySwitch + 8);
        semasize = sizeof(struct ScoutPatchSemaphore) + codesize;

        if (patchsem != NULL &&
            patchsem->sps_SemaphoreSize == semasize &&
            patchsem->sps_CodeSize == codesize &&
            ((patchsem->sps_Version > version_ulong) || (patchsem->sps_Version == version_ulong && patchsem->sps_Revision >= revision_ulong))) {
            CodeAddress = &patchsem->sps_Code[0];
        } else {
            if (patchsem) {
                MyRequest(msgErrorContinue, msgFoundOldPatchSemaphore, patchsem->sps_Version, patchsem->sps_Revision, version_ulong, revision_ulong);
                RemSemaphore(patchsem);
                FreeMem(patchsem, patchsem->sps_SemaphoreSize);
            }

            AsmTimerBase = TimerIORequest->tr_node.io_Device;

            if (patchsem = AllocMem(semasize, MEMF_ANY | MEMF_PUBLIC | MEMF_REVERSE)) {
                CodeAddress = &patchsem->sps_Code[0];
                OldSwitch = *((ULONG *)(-52 + (char *)SysBase));
                OldAddTask = *((ULONG *)(-280 + (char *)SysBase));

                // der Code darf erst nach dem Auslesen der Sprünge passieren, sonst werden für den Assembler-Teil falsche Adressen kopiert
                CopyMem(&MySwitch, patchsem->sps_Code, codesize);

                InitSemaphore(patchsem);
                patchsem->sps_Semaphore.ss_Link.ln_Name = &patchsem->sps_SemaphoreName[0];
                stccpy(patchsem->sps_SemaphoreName, SCOUT_SEMA_NAME, sizeof(patchsem->sps_SemaphoreName));
                patchsem->sps_SemaphoreSize = semasize;
                patchsem->sps_Version = version_ulong;
                patchsem->sps_Revision = revision_ulong;
                patchsem->sps_CodeSize = codesize;
                AddSemaphore(patchsem);

                SetPatches();
            } else {
                MyRequest(msgErrorContinue, msgCantInstallPatchSemaphore);
            }
        }
    }

    if (patchsem) {
        cleartaskdata = (void (* __asm)(void))((char *)&ClearTaskData - (char *)&MySwitch + CodeAddress);
        gettaskdata = (LONG (* __asm)(register __d0 struct Task *))((char *)&GetTaskData - (char *)&MySwitch + CodeAddress);
        getaddedtask = (struct Task * (* __asm)(void))((char *)&GetAddedTask - (char *)&MySwitch + CodeAddress);
        switchstate = (char *)&SwitchState - (char *)&MySwitch + CodeAddress;

        *switchstate = (char)cccm->value;
        // HandleTimerRequest(FALSE);
        (*cleartaskdata)();

        if (cccm->value == 2) {
            if (!cheattask) cheattask = AddCheatTask();
        } else {
            RemoveCheatTask();
            set(twd->twd_CPUGauge, MUIA_Gauge_Current, 0);
            set(twd->twd_CPUGauge, MUIA_Gauge_InfoText, MUIX_PT "-----");
        }

        // DoMethod(obj, MUIM_TasksWin_CPUCheck);

        if (cccm->value == 0) {
            if (twd->twd_TimerHandlerAdded) {
                DoMethod(twd->twd_Application, MUIM_Application_RemInputHandler, &twd->twd_TimerHandler);
                twd->twd_TimerHandlerAdded = FALSE;
            }
        } else {
            if (!twd->twd_TimerHandlerAdded) {
                DoMethod(twd->twd_Application, MUIM_Application_AddInputHandler, &twd->twd_TimerHandler);
                twd->twd_TimerHandlerAdded = TRUE;
            }
        }

        DoMethod(obj, MUIM_TasksWin_CPUCheck);
    }

    return 0;
}

static ULONG __saveds mCPUCheck( struct IClass *cl,
                                 Object *obj,
                                 Msg msg )
{
    struct TasksWinData *twd = INST_DATA(cl, obj);
    struct TasksCallbackUserData ud;
    struct Task *task;
    BOOL changed;
    ULONG i, oldTaskCnt, sortCol;

    get(twd->twd_TaskList, MUIA_NList_Entries, &oldTaskCnt);
    get(twd->twd_TaskList, MUIA_NList_SortType, &sortCol);
    sortCol &= MUIV_NList_TitleMark_ColMask;

    ud.ud_List = twd->twd_TaskList;
    ud.ud_Count = oldTaskCnt;

    (*cleartaskdata)();

    while (task = (*getaddedtask)()) {
        if (TaskExists(task)) {
            GetTaskEntry(task, &twd->twd_NewEntry, (twd->twd_CPUCheck != 0));
            UpdateCallback(&twd->twd_NewEntry, &ud);
        }
    }

    changed = FALSE;

    for (i = 0; ; i++) {
        struct TaskEntry *old;

        DoMethod(twd->twd_TaskList, MUIM_NList_GetEntry, i, &old);

        if (old) {
            if (task = TaskExists(old->te_Addr)) {
                CopyMem(old, &twd->twd_CompareEntry, sizeof(struct TaskEntry));
                UpdateTaskEntry(task, old, (twd->twd_CPUCheck));
                if (memcmp(old, &twd->twd_CompareEntry, sizeof(struct TaskEntry))) {
                    if (sortCol != 2) DoMethod(twd->twd_TaskList, MUIM_NList_Redraw, i);
                    changed = TRUE;
                }
            } else {
                DoMethod(twd->twd_TaskList, MUIM_NList_Remove, i);
                i--;
                ud.ud_Count--;
            }
        } else {
            break;
        }
    }

    if (ud.ud_Count != oldTaskCnt) SetCountText(twd->twd_TaskCount, ud.ud_Count);
    if (changed && sortCol == 2) DoMethod(twd->twd_TaskList, MUIM_List_Sort);

    if (twd->twd_CPUCheck == 2) {
        ULONG cheatcpu;

        cheatcpu = (*gettaskdata)(cheattask);
        if (totalmicros) {
            set(twd->twd_CPUGauge, MUIA_Gauge_InfoText, "%ld%%");
            set(twd->twd_CPUGauge, MUIA_Gauge_Current, UDivMod32(UMult32(totalmicros - cheatcpu, 100), totalmicros));
        }
    }

    return 0;
}

static ULONG __saveds mCPUCheckQuick( struct IClass *cl,
                                      Object *obj,
                                      Msg msg )
{
    if (patchsem != NULL && *switchstate != 0) {
        DoMethod(obj, MUIM_TasksWin_CPUCheck);
    }

    return TRUE;
}

ULONG __asm __saveds TasksWinDispatcher( 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_TasksWin_Update:         return (mUpdate(cl, obj, (APTR)msg));
        case MUIM_TasksWin_Print:          return (mPrint(cl, obj, (APTR)msg));
        case MUIM_TasksWin_Remove:         return (mRemove(cl, obj, (APTR)msg));
        case MUIM_TasksWin_Priority:       return (mPriority(cl, obj, (APTR)msg));
        case MUIM_TasksWin_More:           return (mMore(cl, obj, (APTR)msg));
        case MUIM_TasksWin_ListChange:     return (mListChange(cl, obj, (APTR)msg));
        case MUIM_TasksWin_Freeze:         return (mFreeze(cl, obj, (APTR)msg));
        case MUIM_TasksWin_Activate:       return (mActivate(cl, obj, (APTR)msg));
        case MUIM_TasksWin_Signal:         return (mSignal(cl, obj, (APTR)msg));
        case MUIM_TasksWin_Break:          return (mBreak(cl, obj, (APTR)msg));
        case MUIM_TasksWin_CPUCheckChange: return (mCPUCheckChange(cl, obj, (APTR)msg));
        case MUIM_TasksWin_CPUCheck:       return (mCPUCheck(cl, obj, (APTR)msg));
        case MUIM_TasksWin_CPUCheckQuick:  return (mCPUCheckQuick(cl, obj, (APTR)msg));
    }

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

void PrintTasks( char *filename )
{
    BPTR handle;

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

    HandlePrintStop();
}

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

