/**
 * 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"

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

MakeHook(syslist_con2hook, syslist_con2func);

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

    return 0;
}

MakeHook(syslist_des2hook, syslist_des2func);

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

    if (se) {
        msg->strings[0] = se->se_Description;
        msg->strings[1] = se->se_Line;
    }

    return 0;
}

MakeHook(syslist_dsp2hook, syslist_dsp2func);





static STRPTR MyIdHardware( ULONG num )
{
    return IdHardwareTags(num, IDTAG_Localize, FALSE, TAG_DONE);
}

static int MyIdCheck( ULONG num )
{
    return (int)IdHardwareTags(num, IDTAG_NULL4NA, TRUE, TAG_DONE);
}

static void DateToInt( struct DateStamp *ds,
                       ULONG *u )
{
    *u = SMult32(ds->ds_Days, 86400) +
         SMult32(ds->ds_Minute, 60) +
         SDivMod32(ds->ds_Tick, TICKS_PER_SECOND);
}

static void IntToDate( ULONG r,
                       struct DateStamp *ds )
{
    ds->ds_Days = UDivMod32(r, 86400);

    r = getreg(REG_D1);
    ds->ds_Minute = UDivMod32(r, 60);

    r = getreg(REG_D1);
    ds->ds_Tick = UMult32(r, TICKS_PER_SECOND);
}

static void addentry( void (* callback)( struct SystemEntry *se, void *userData ),
                      void *userData,
                      UBYTE *desc,
                      UBYTE *linefmt, ... )
{
    struct SystemEntry *se;

    if (se = tbAllocVecPooled(globalPool, sizeof(struct SystemEntry))) {
        va_list args;

        if (desc) stccpy(se->se_Description, desc, sizeof(se->se_Description));

        if (linefmt) {
            va_start(args, linefmt);
            _vsnprintf(se->se_Line, sizeof(se->se_Line), linefmt, args);
            va_end(args);
        }

        callback(se, userData);

        tbFreeVecPooled(globalPool, se);
    }
}

static void ReceiveList( void (* callback)( struct SystemEntry *se, void *userData ),
                         void *userData )
{
    struct SystemEntry *se;

    if (se = tbAllocVecPooled(globalPool, sizeof(struct SystemEntry))) {
        if (SendDaemon("GetSysList")) {
            while (ReceiveDecodedEntry((UBYTE *)se, sizeof(struct SystemEntry))) {
                callback(se, userData);
            }
        }

        tbFreeVecPooled(globalPool, se);
    }
}

static void IterateList( void (* callback)( struct SystemEntry *se, void *userData ),
                         void *userData )
{
    UBYTE tmp[PATH_LENGTH];
    ULONG cacr, pcr;
    UWORD attn;
    BPTR lock;
    ULONG lastalert;
    BOOL fblit;

    IdHardwareUpdate(); // Liste auf dem aktuellsten Stand

    cacr = GetCACR(); /* 68020..68060 */
    pcr  = GetPCR();  /* 68060 */
    attn = SysBase->AttnFlags;

    addentry(callback, userData, NULL, MUIX_C MUIX_PH "-- HARDWARE --" MUIX_PT);

    addentry(callback, userData, NULL, NULL);

    addentry(callback, userData, "System", "%s", IdHardware(IDHW_SYSTEM,NULL));

    addentry(callback, userData, NULL, NULL);

    if (MyIdCheck(IDHW_CPUREV)) {
        addentry(callback, userData, "Processor", "CPU: %s / %s (ID $%04lx, Rev %s)", MyIdHardware(IDHW_CPU), MyIdHardware(IDHW_CPUCLOCK), pcr >> 16, MyIdHardware(IDHW_CPUREV));
    } else {
        addentry(callback, userData, "Processor", "CPU: %s / %s", MyIdHardware(IDHW_CPU), MyIdHardware(IDHW_CPUCLOCK));
    }

    if (MyIdCheck(IDHW_FPU)) {
        addentry(callback, userData, NULL, "FPU: %s / %s", MyIdHardware(IDHW_FPU), MyIdHardware(IDHW_FPUCLOCK));
    }

    if (MyIdCheck(IDHW_MMU)) {
        addentry(callback, userData, NULL, "MMU: %s", MyIdHardware(IDHW_MMU));
    }

    if (MyIdCheck(IDHW_POWERPC)) {
        addentry(callback, userData, NULL, "PPC: %s / %s", MyIdHardware(IDHW_POWERPC), MyIdHardware(IDHW_PPCCLOCK));
    }

    addentry(callback, userData, NULL, NULL);

    addentry(callback, userData, "Chipset", "%s, RamSey %s, Gary %s", MyIdHardware(IDHW_CHIPSET), MyIdHardware(IDHW_RAMSEY), MyIdHardware(IDHW_GARY));

    addentry(callback, userData, NULL, "ChunkyPlanar %s, BattClock %s", MyIdHardware(IDHW_CHUNKYPLANAR), MyIdHardware(IDHW_BATTCLOCK));

    Forbid();
    fblit = (FindPort("FBlit") != NULL);
    Permit();

    addentry(callback, userData, NULL, "FBlit %s", (fblit) ? "found" : "not running");

    addentry(callback, userData, "Mother RAM", "%s Bit, %s Access, %s CAS, %sx Bandwidth", MyIdHardware(IDHW_RAMWIDTH), MyIdHardware(IDHW_RAMACCESS), MyIdHardware(IDHW_RAMCAS), MyIdHardware(IDHW_RAMBANDWIDTH));

    addentry(callback, userData, "Frequencies", "VBlank %s, Power %s, E %s", MyIdHardware(IDHW_VBLANKFREQ), MyIdHardware(IDHW_POWERFREQ), MyIdHardware(IDHW_ECLOCK));

    addentry(callback, userData, NULL, NULL);
    addentry(callback, userData, NULL, NULL);

    addentry(callback, userData, NULL, MUIX_C MUIX_PH "-- REGISTERS --" MUIX_PT);

    addentry(callback, userData, NULL, NULL);

    addentry(callback, userData, NULL, "SSP:  $%08lx", SysBase->SysStkLower);

    if (attn & AFF_68010) {
        ULONG cache = CacheControl(0, 0);

        addentry(callback, userData, NULL, "VBR:  $%08lx",GetVBR());

        if (attn & AFF_68020) {
            addentry(callback, userData, NULL, "CACR: $%08lx",cacr);
        }

        if (attn & AFF_68060) {
            addentry(callback, userData, NULL, "PCR:  $%08lx",pcr);
        }

        addentry(callback, userData, NULL, NULL);

        if (attn & AFF_68020) {
            if (cache & CACRF_EnableI) {
                stccpy(tmp, "Enabled ", sizeof(tmp));
                if ((attn & AFF_68030) && !(attn & AFF_68040) && (cache & CACRF_IBE)) _strcatn(tmp, "Burst ", PATH_LENGTH);
                if (cache & CACRF_FreezeI) _strcatn(tmp, "Frozen ", PATH_LENGTH);
                if ((attn & AFF_68060) && (cacr & (1 << 13))) _strcatn(tmp, "Half ", PATH_LENGTH);
            } else {
                stccpy(tmp, "Disabled", sizeof(tmp));
            }
            addentry(callback, userData, "Inst Cache", tmp);
        }

        if (attn & AFF_68030) {
            if (cache & CACRF_EnableD) {
                stccpy(tmp, "Enabled ", sizeof(tmp));
                if ((attn & AFF_68030) && !(attn & AFF_68040) && (cache & CACRF_DBE)) _strcatn(tmp, "Burst ", PATH_LENGTH);
                if (cache & CACRF_FreezeD) _strcatn(tmp, "Frozen ", PATH_LENGTH);
                if ((attn & AFF_68060) && (cacr & (1 << 27))) _strcatn(tmp, "Half ", PATH_LENGTH);
                if ((attn & AFF_68030) && !(attn & AFF_68040) && (cache & CACRF_WriteAllocate)) _strcatn(tmp, "Allocate ", PATH_LENGTH);
            } else {
                stccpy(tmp, "Disabled", sizeof(tmp));
            }
            addentry(callback, userData, "Data Cache", tmp);
        }

        if ((attn & AFF_68040) && !(attn & AFF_68060) && (cache & CACRF_CopyBack)) {
            addentry(callback, userData, NULL, "CopyBack");
        }

        if (cache & CACRF_EnableE){
            addentry(callback, userData, NULL, "External Cache");
        }

        if (attn & AFF_68060) {
            if (pcr & (1 << 0)) {
                addentry(callback, userData, NULL, "Superscalar");
            }

            if (cacr & (1 << 23)) {
                addentry(callback, userData, NULL, "Branch Cache");
            }

            if (cacr & (1 << 29)) {
                addentry(callback, userData, NULL, "Store Buffer");
            }

            if (cacr & (1 << 28)) {
                addentry(callback, userData, NULL, "CPUSH invalidation disabled");
            }

            if (pcr & (1 << 1)) {
                addentry(callback, userData, NULL, "FPU disabled");
            }
        }
    }

    addentry(callback, userData, NULL, NULL);
    addentry(callback, userData, NULL, NULL);

    if (lock = Lock("RAM:", SHARED_LOCK)) {
        struct InfoData *id;

        if (id = tbAllocVecPooled(globalPool, sizeof(struct InfoData))) {
            if (Info(lock, id)) {
                struct DeviceList *ramDev;
                ULONG currentTime, bootTime, upTime;
                struct DateStamp ds;
                struct DateTime dt;
                UBYTE dateStr[LEN_DATSTRING], timeStr[LEN_DATSTRING];

                ramDev = BADDR(id->id_VolumeNode);
                DateToInt(&ramDev->dl_VolumeDate, &bootTime);

                DateStamp(&ds);
                DateToInt(&ds, &currentTime);

                upTime = currentTime - bootTime;

                addentry(callback, userData, NULL, MUIX_C MUIX_PH "-- UPTIME --");

                addentry(callback, userData, NULL, NULL);

                IntToDate(currentTime, &dt.dat_Stamp);
                dt.dat_Format = FORMAT_DOS;
                dt.dat_Flags = 0;
                dt.dat_StrDay = NULL;
                dt.dat_StrDate = dateStr;
                dt.dat_StrTime = timeStr;
                DateToStr(&dt);

                addentry(callback, userData, "Current time", "%s %s", dateStr, timeStr);

                IntToDate(bootTime, &dt.dat_Stamp);
                dt.dat_Format = FORMAT_DOS;
                dt.dat_Flags = 0;
                dt.dat_StrDay = NULL;
                dt.dat_StrDate = dateStr;
                dt.dat_StrTime = timeStr;
                DateToStr(&dt);

                addentry(callback, userData, "Boot time", "%s %s", dateStr, timeStr);

                IntToDate(upTime, &dt.dat_Stamp);
                dt.dat_Format = FORMAT_DOS;
                dt.dat_Flags = 0;
                dt.dat_StrDay = NULL;
                dt.dat_StrDate = NULL;
                dt.dat_StrTime = timeStr;
                DateToStr(&dt);

                addentry(callback, userData, "Uptime", "%4ld days, %s hours", dt.dat_Stamp.ds_Days, timeStr);
            }

            tbFreeVecPooled(globalPool, id);
        }

        UnLock(lock);
    }

    addentry(callback, userData, NULL, NULL);
    addentry(callback, userData, NULL, NULL);

    addentry(callback, userData, NULL, MUIX_C MUIX_PH "-- VERSIONS --" MUIX_PT);

    addentry(callback, userData, NULL, NULL);

    addentry(callback, userData, "AmigaOS", "%s (= %s), SetPatch %s, ROM %s", MyIdHardware(IDHW_OSNR), MyIdHardware(IDHW_OSVER), MyIdHardware(IDHW_SETPATCHVER), MyIdHardware(IDHW_ROMSIZE));

    addentry(callback, userData, "Exec", "%s", MyIdHardware(IDHW_EXECVER));

    addentry(callback, userData, "Workbench", "%s", MyIdHardware(IDHW_WBVER));

    addentry(callback, userData, "Graphics", "%s", MyIdHardware(IDHW_GFXSYS));

    addentry(callback, userData, "Audio", "%s", MyIdHardware(IDHW_AUDIOSYS));

    addentry(callback, userData, "TCP/IP", "%s", MyIdHardware(IDHW_TCPIP));

    addentry(callback, userData, "PowerPC-OS", "%s", MyIdHardware(IDHW_PPCOS));

    addentry(callback, userData, NULL, NULL);
    addentry(callback, userData, NULL, NULL);

    addentry(callback, userData, NULL, MUIX_C MUIX_PH "-- MEMORY --" MUIX_PT);

    addentry(callback, userData, NULL, NULL);

    addentry(callback, userData, NULL, " Chip       Fast      TOTAL");

    addentry(callback, userData, "Plain", "%9s  %9s  %9s", MyIdHardware(IDHW_PLNCHIPRAM), MyIdHardware(IDHW_PLNFASTRAM), MyIdHardware(IDHW_PLNRAM));

    addentry(callback, userData, "Virtual", "%9s  %9s  %9s", MyIdHardware(IDHW_VMMCHIPRAM), MyIdHardware(IDHW_VMMFASTRAM), MyIdHardware(IDHW_VMMRAM));

    addentry(callback, userData, "TOTAL", "%9s  %9s  %9s", MyIdHardware(IDHW_CHIPRAM), MyIdHardware(IDHW_FASTRAM), MyIdHardware(IDHW_RAM));

    addentry(callback, userData, NULL, NULL);

    addentry(callback, userData, NULL, "SlowRAM: %s", MyIdHardware(IDHW_SLOWRAM));

    lastalert = IdHardwareNum(IDHW_LASTALERT, NULL);
    if (lastalert != 0xFFFFFFFF) {
        APTR alertTask;

        addentry(callback, userData, NULL, NULL);
        addentry(callback, userData, NULL, NULL);

        addentry(callback, userData, NULL, MUIX_C MUIX_PH "-- LAST ALERT --" MUIX_PT);

        addentry(callback, userData, NULL, NULL);

        addentry(callback, userData, "Alert Code", "%08lx", lastalert);

        alertTask = (APTR)SysBase->LastAlert[1];
        if (!alertTask) {
            ULONG *guruData = (ULONG *)0x100;

            alertTask = (APTR)guruData[1];
        }
        addentry(callback, userData, "Alert Task", "$%08lx", alertTask);

        IdAlertTags(lastalert, IDTAG_DeadStr, tmp, TAG_DONE);
        addentry(callback, userData, "Deadend", tmp);

        IdAlertTags(lastalert, IDTAG_SubsysStr, tmp, TAG_DONE);
        addentry(callback, userData, "Sub System", tmp);

        IdAlertTags(lastalert, IDTAG_GeneralStr, tmp, TAG_DONE);
        addentry(callback, userData, "General", tmp);

        IdAlertTags(lastalert, IDTAG_SpecStr, tmp, TAG_DONE);
        addentry(callback, userData, "Specific", tmp);
    }
}

static void UpdateCallback( struct SystemEntry *se,
                            void *userData )
{
    InsertBottomEntry(userData, se);
}

static void PrintCallback( struct SystemEntry *se,
                           void *userData )
{
    UBYTE *desc, *line;

    desc = se->se_Description;
    while (*desc=='\33') desc += 2;

    line = se->se_Line;
    while (*line=='\33') line += 2;

    PrintFOneLine((BPTR)userData, " %15s  %s\n", desc, line);
}

static void SendCallback( struct SystemEntry *se,
                          void *userData )
{
    SendEncodedEntry((UBYTE *)se, sizeof(struct SystemEntry));
}

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

    if (obj = (Object *)DoSuperNew(cl, obj,
        MUIA_HelpNode, SystemText,
        MUIA_Window_ID, MakeID('S','Y','S','T'),
        WindowContents, VGroup,

            Child, sysList = NListviewObject,
               MUIA_CycleChain, TRUE,
               MUIA_NListview_NList, NListObject,
                MUIA_Font, MUIV_Font_Fixed,
                   MUIA_NList_Input, FALSE,
                   MUIA_NList_Format, "BAR P=" MUIX_R ",BAR",
                   MUIA_NList_ConstructHook2, &syslist_con2hook, 
                   MUIA_NList_DestructHook2, &syslist_des2hook, 
                   MUIA_NList_DisplayHook2, &syslist_dsp2hook,
               End,
            End,

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

        swd->swd_SystemList = sysList;

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

        set(obj, MUIA_Window_Title, MyGetWindowTitle("SYSTEM", swd->swd_Title, sizeof(swd->swd_Title)));
        set(obj, MUIA_Window_ActiveObject, sysList);

        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(updateButton, MUIM_Notify, MUIA_Pressed,             FALSE, obj,                     1, MUIM_SystemWin_Update);
        DoMethod(printButton,  MUIM_Notify, MUIA_Pressed,             FALSE, obj,                     1, MUIM_SystemWin_Print);
        DoMethod(exitButton,   MUIM_Notify, MUIA_Pressed,             FALSE, obj,                     3, MUIM_Set, MUIA_Window_CloseRequest, TRUE);
    }

    return (ULONG)obj;
}

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

    set(obj, MUIA_Window_Open, FALSE);
    DoMethod(swd->swd_SystemList, MUIM_NList_Clear);

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

static ULONG __saveds mUpdate( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct SystemWinData *swd = INST_DATA(cl, obj);

    ApplicationSleep(TRUE);
    set(swd->swd_SystemList, MUIA_List_Quiet, TRUE);
    DoMethod(swd->swd_SystemList, MUIM_List_Clear);

    if (clientstate) {
        ReceiveList(UpdateCallback, swd->swd_SystemList);
    } else {
        IterateList(UpdateCallback, swd->swd_SystemList);
    }

    set(swd->swd_SystemList, MUIA_List_Quiet, FALSE);
    ApplicationSleep(FALSE);

    return 0;
}

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

    return 0;
}

ULONG __asm __saveds SystemWinDispatcher( 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_SystemWin_Update: return (mUpdate(cl, obj, (APTR)msg));
        case MUIM_SystemWin_Print:  return (mPrint(cl, obj, (APTR)msg));
    }

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

void PrintSystem( char *filename )
{
    BPTR handle;

    if (handle = HandlePrintStart(filename)) {
        IterateList(PrintCallback, (void *)handle);
    }

    HandlePrintStop();
}

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

