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

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

MakeHook(timerlist_con2hook, timerlist_con2func);

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

    return 0;
}

MakeHook(timerlist_des2hook, timerlist_des2func);

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

    if (te) {
        msg->strings[0] = te->te_Address;
        msg->strings[1] = te->te_Name;
        msg->strings[2] = te->te_Timeout;
        msg->strings[3] = te->te_Unit;
        msg->strings[4] = te->te_ReplyPort;
    } else {
        msg->strings[0] = MUIX_B "Address";
        msg->strings[1] = MUIX_B "Task";
        msg->strings[2] = MUIX_B "Time";
        msg->strings[3] = MUIX_B "Unit";
        msg->strings[4] = MUIX_B "mn_ReplyPort";
    }

    return 0;
}

MakeHook(timerlist_dsp2hook, timerlist_dsp2func);

static LONG timerlist_cmp2colfunc( struct TimerEntry *te1,
                                   struct TimerEntry *te2,
                                   ULONG column )
{
    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_Timeout, te2->te_Timeout);
        case 3: return stricmp(te1->te_Unit, te2->te_Unit);
        case 4: return stricmp(te1->te_ReplyPort, te2->te_ReplyPort);
    }
}

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

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

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

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

    return cmp;
}

MakeHook(timerlist_cmp2hook, timerlist_cmp2func);

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

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

        tbFreeVecPooled(globalPool, te);
    }
}

static void IterateList( void (* callback)( struct TimerEntry *te, void *userData ),
                         void *userData )
{
    struct TimerEntry *te;

    if (te = tbAllocVecPooled(globalPool, sizeof(struct TimerEntry))) {
        struct MsgPort *mp;

        if (mp = CreateMsgPort()) {
            struct timerequest *tr;

            if (tr = CreateIORequest(mp, sizeof(struct timerequest))) {
                if (OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest *)tr, 0) == 0) {
                    struct EClockVal eclock;
                    ULONG eclk;
                    struct timerequest *search;

                    TimerBase = tr->tr_node.io_Device;

                    tr->tr_node.io_Command = TR_ADDREQUEST;
                    tr->tr_time.tv_secs = 0;
                    tr->tr_time.tv_micro = 10000;

                    Forbid();

                    SendIO((struct IORequest *)tr);

                    for(search = tr;
                        search->tr_node.io_Message.mn_Node.ln_Pred;
                        search = (struct timerequest *)search->tr_node.io_Message.mn_Node.ln_Pred);

                    eclk = ReadEClock(&eclock);
                    eclk /= 100;

                    search = (struct timerequest *)search->tr_node.io_Message.mn_Node.ln_Succ;
                    while (search->tr_node.io_Message.mn_Node.ln_Succ) {
                        if (search != tr) {
                            ULONG  time, std, min, sec;

                            time = search->tr_time.tv_micro;
                            if (time > eclock.ev_lo) {
                                time -= eclock.ev_lo;
                                time /= eclk;
                            } else {
                                time = 0;
                            }

                            sec = search->tr_time.tv_secs + (time / 100);
                            min = sec / 60;
                            sec %= 60;
                            std = min / 60;
                            min %= 60;

                            te->te_Addr = search;
                            _snprintf(te->te_Address, sizeof(te->te_Address), "$%08lx", search);
                            stccpy(te->te_Name, nonetest(GetTaskName(search->tr_node.io_Message.mn_ReplyPort->mp_SigTask)), sizeof(te->te_Name));
                            _snprintf(te->te_ReplyPort, sizeof(te->te_ReplyPort), "$%08lx", search->tr_node.io_Message.mn_ReplyPort);
                            stccpy(te->te_Unit, "MicroHz", sizeof(te->te_Unit));
                            _snprintf(te->te_Timeout, sizeof(te->te_Timeout), "%ld:%02ld'%02ld.%02ld\"", std, min, sec, time % 100);

                            callback(te, userData);
                        }

                        search = (struct timerequest *)search->tr_node.io_Message.mn_Node.ln_Succ;
                    }

                    Permit();

                    WaitIO((struct IORequest *)tr);
                    CloseDevice((struct IORequest *)tr);
                }

                if (OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)tr, 0) == 0) {
                    struct EClockVal eclock;
                    ULONG eclk;
                    struct timerequest *search;

                    TimerBase = tr->tr_node.io_Device;

                    tr->tr_node.io_Command = TR_ADDREQUEST;
                    tr->tr_time.tv_secs = 0;
                    tr->tr_time.tv_micro = 20000;

                    Forbid();

                    SendIO((struct IORequest *)tr);

                    for(search = tr;
                        search->tr_node.io_Message.mn_Node.ln_Pred;
                        search = (struct timerequest *)search->tr_node.io_Message.mn_Node.ln_Pred);

                    eclk = ReadEClock(&eclock);

                    search = (struct timerequest *) search->tr_node.io_Message.mn_Node.ln_Succ;
                    while (search->tr_node.io_Message.mn_Node.ln_Succ) {
                        if (search != tr) {
                            ULONG  time, std, min, sec;

                            time = search->tr_time.tv_micro;
                            if (time > eclock.ev_lo) {
                                time = ((time - eclock.ev_lo) * 100) / eclk;
                            } else {
                                time = 0;
                            }

                            sec = search->tr_time.tv_secs + (time / 100);
                            min = sec / 60;
                            sec %= 60;
                            std = min / 60;
                            min %= 60;

                            te->te_Addr = search;
                            _snprintf(te->te_Address, sizeof(te->te_Address), "$%08lx", search);
                            stccpy(te->te_Name, nonetest(GetTaskName(search->tr_node.io_Message.mn_ReplyPort->mp_SigTask)), sizeof(te->te_Name));
                            _snprintf(te->te_ReplyPort, sizeof(te->te_ReplyPort), "$%08lx", search->tr_node.io_Message.mn_ReplyPort);
                            stccpy(te->te_Unit, "VBlank", sizeof(te->te_Unit));
                            _snprintf(te->te_Timeout, sizeof(te->te_Timeout), "%ld:%02ld'%02ld.%02ld\"", std, min, sec, time % 100);

                            callback(te, userData);
                       }

                       search = (struct timerequest *) search->tr_node.io_Message.mn_Node.ln_Succ;
                    }

                    Permit();

                    WaitIO((struct IORequest *)tr);
                    CloseDevice((struct IORequest *)tr);
                }

                DeleteIORequest((struct IORequest *)tr);
            }

            DeleteMsgPort(mp);
        }

        tbFreeVecPooled(globalPool, te);
    }
}

static void UpdateCallback( struct TimerEntry *te,
                            void *userData )
{
    struct TimersCallbackUserData *ud = (struct TimersCallbackUserData *)userData;

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

static void PrintCallback( struct TimerEntry *te,
                           void *userData )
{
    PrintFOneLine((BPTR)userData, " %s %s %13s %-7s %s\n", te->te_Address, te->te_ReplyPort, te->te_Timeout, te->te_Unit, te->te_Name);
}

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

static ULONG __saveds mNew( struct IClass *cl,
                            Object *obj,
                            struct opSet *msg )
{
    APTR timerlist, timertext, timercount, updateButton, printButton, exitButton;

    if (obj = (Object *)DoSuperNew(cl, obj,
        MUIA_HelpNode, TimerText,
        MUIA_Window_ID, MakeID('T','I','M','R'),
        WindowContents, VGroup,

            Child, timerlist = MyNListviewObject(MakeID('T','I','L','V'), "BAR,BAR,BAR,BAR P=" MUIX_C ",BAR", &timerlist_con2hook, &timerlist_des2hook, &timerlist_dsp2hook, &timerlist_cmp2hook, TRUE),
            Child, MyBelowListview(&timertext, &timercount),

            Child, MyVSpace(4),

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

        twd->twd_TimerList = timerlist;
        twd->twd_TimerText = timertext;
        twd->twd_TimerCount = timercount;

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

        set(obj, MUIA_Window_Title, MyGetWindowTitle("TIMER REQUESTS", twd->twd_Title, sizeof(twd->twd_Title)));
        set(obj, MUIA_Window_ActiveObject, timerlist);

        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(timerlist,    MUIM_Notify, MUIA_NList_Active,        MUIV_EveryTime, obj,                     1, MUIM_TimersWin_ListChange);
        DoMethod(updateButton, MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_TimersWin_Update);
        DoMethod(printButton,  MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_TimersWin_Print);
        DoMethod(exitButton,   MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     3, MUIM_Set, MUIA_Window_CloseRequest, TRUE);
        DoMethod(timerlist,    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 TimersWinData *twd = INST_DATA(cl, obj);

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

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

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

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

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

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

    SetCountText(twd->twd_TimerCount, ud.ud_Count);
    MySetContents(twd->twd_TimerText, "");

    set(twd->twd_TimerList, MUIA_NList_Quiet, FALSE);
    ApplicationSleep(FALSE);

    return 0;
}

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

    return 0;
}

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

    if (te = (struct TimerEntry *)GetActiveEntry(twd->twd_TimerList)) {
        MySetContents(twd->twd_TimerText, "%s \"%s\"", te->te_Address, te->te_Name);
    }

    return 0;
}

ULONG __asm __saveds TimersWinDispatcher( 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_TimersWin_Update:     return (mUpdate(cl, obj, (APTR)msg));
        case MUIM_TimersWin_Print:      return (mPrint(cl, obj, (APTR)msg));
        case MUIM_TimersWin_ListChange: return (mListChange(cl, obj, (APTR)msg));
    }

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

void PrintTimer( char *filename )
{
    BPTR handle;

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

    HandlePrintStop();
}

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

