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

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

MakeHook(smodelist_con2hook, smodelist_con2func);

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

    return 0;
}

MakeHook(smodelist_des2hook, smodelist_des2func);

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

    if (sme) {
        msg->strings[0] = sme->sme_Id;
        msg->strings[1] = sme->sme_Name;
        msg->strings[2] = sme->sme_Width;
        msg->strings[3] = sme->sme_Height;
        msg->strings[4] = sme->sme_Depth;
    } else {
        msg->strings[0] = MUIX_B "ModeID";
        msg->strings[1] = MUIX_B "Name";
        msg->strings[2] = MUIX_B "Width";
        msg->strings[3] = MUIX_B "Height";
        msg->strings[4] = MUIX_B "Depth";
    }

    return 0;
}

MakeHook(smodelist_dsp2hook, smodelist_dsp2func);

static LONG smodelist_cmp2colfunc( struct ScreenModeEntry *sme1,
                                struct ScreenModeEntry *sme2,
                                ULONG column )
{
    switch (column) {
        case 0: return stricmp(sme1->sme_Id, sme2->sme_Id);
        case 1: return stricmp(sme1->sme_Name, sme2->sme_Name);
        case 2: return stricmp(sme1->sme_Width, sme2->sme_Width);
        case 3: return stricmp(sme1->sme_Height, sme2->sme_Height);
        case 4: return stricmp(sme1->sme_Depth, sme2->sme_Depth);
    }
}

static __asm __saveds LONG smodelist_cmp2func(register __a2 Object *obj, register __a1 struct NList_CompareMessage *msg, register __a0 struct Hook *hook)
{
    LONG cmp;
    struct ScreenModeEntry *sme1, *sme2;
    ULONG col1, col2;

    sme1 = (struct ScreenModeEntry *)msg->entry1;
    sme2 = (struct ScreenModeEntry *)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 = smodelist_cmp2colfunc(sme2, sme1, col1);
    } else {
        cmp = smodelist_cmp2colfunc(sme1, sme2, col1);
    }

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

    if (msg->sort_type2 & MUIV_NList_TitleMark2_TypeMask) {
        cmp = smodelist_cmp2colfunc(sme2, sme1, col2);
    } else {
        cmp = smodelist_cmp2colfunc(sme1, sme2, col2);
    }

    return cmp;
}

MakeHook(smodelist_cmp2hook, smodelist_cmp2func);

static void ReceiveList( void (* callback)( struct ScreenModeEntry *sme, void *userData ),
                         void *userData )
{
    struct ScreenModeEntry *sme;

    if (sme = tbAllocVecPooled(globalPool, sizeof(struct ScreenModeEntry))) {
        if (SendDaemon("GetSModeList")) {
            while (ReceiveDecodedEntry((UBYTE *)sme, sizeof(struct ScreenModeEntry))) {
                callback(sme, userData);
            }
        }

        tbFreeVecPooled(globalPool, sme);
    }
}

static void IterateList( void (* callback)( struct ScreenModeEntry *sme, void *userData ),
                         void *userData )
{
    struct ScreenModeEntry *sme;

    if (sme = tbAllocVecPooled(globalPool, sizeof(struct ScreenModeEntry))) {
        ULONG id = INVALID_ID;

        while ((id = NextDisplayInfo(id)) != INVALID_ID) {
            struct NameInfo idname;
            struct DimensionInfo iddim;

            sme->sme_ModeID = id;
            _snprintf(sme->sme_Id, sizeof(sme->sme_Id), "$%08lx", id);

            GetDisplayInfoData(NULL, (UBYTE *)&iddim, sizeof(struct DimensionInfo), DTAG_DIMS, id);
            _snprintf(sme->sme_Width, sizeof(sme->sme_Width), "%5ld", iddim.Nominal.MaxX-iddim.Nominal.MinX + 1);
            _snprintf(sme->sme_Height, sizeof(sme->sme_Height), "%5ld", iddim.Nominal.MaxY-iddim.Nominal.MinY + 1);
            _snprintf(sme->sme_Depth, sizeof(sme->sme_Depth), "%5ld", (iddim.MaxDepth == 32) ? 24 : iddim.MaxDepth);

            if (GetDisplayInfoData(NULL, (UBYTE *)&idname, sizeof(struct NameInfo), DTAG_NAME, id)) {
                stccpy(sme->sme_Name, idname.Name, sizeof(sme->sme_Name));
            } else {
                struct MonitorInfo idmon;
                struct DisplayInfo iddisp;

                if (GetDisplayInfoData(NULL, (UBYTE *)&idmon, sizeof(struct MonitorInfo), DTAG_MNTR, id)) {
                    if (GetDisplayInfoData(NULL, (UBYTE *)&iddisp, sizeof(struct DisplayInfo), DTAG_DISP, id)) {
                        ULONG prop = iddisp.PropertyFlags;
                        WORD  xres = iddim.Nominal.MaxX - iddim.Nominal.MinX;

                        NameCopy(sme->sme_Name, idmon.Mspc->ms_Node.xln_Name);
                        if (sme->sme_Name[0] == 'E') {
                            if (!strcmp(sme->sme_Name, "EURO72:")) stccpy(sme->sme_Name, "EURO:72Hz ", sizeof(sme->sme_Name));
                            if (!strcmp(sme->sme_Name, "EURO36:")) stccpy(sme->sme_Name, "EURO:36Hz ", sizeof(sme->sme_Name));
                        }

                        if (xres > 1200)     _strcatn(sme->sme_Name, "SuperHighRes", sizeof(sme->sme_Name));
                        else if (xres > 600) _strcatn(sme->sme_Name, "HighRes", sizeof(sme->sme_Name));
                        else if (xres > 300) _strcatn(sme->sme_Name, "LowRes", sizeof(sme->sme_Name));
                        else                 _strcatn(sme->sme_Name, "ExtraLowRes", sizeof(sme->sme_Name));

                        if (prop & DIPF_IS_DUALPF        ) {
                            _strcatn(sme->sme_Name," DualPF", sizeof(sme->sme_Name));
                            if (prop & DIPF_IS_PF2PRI) _strcatn(sme->sme_Name, "2", sizeof(sme->sme_Name));
                        }
                        if (prop & DIPF_IS_HAM           ) _strcatn(sme->sme_Name, " HAM", sizeof(sme->sme_Name));
                        if (prop & DIPF_IS_EXTRAHALFBRITE) _strcatn(sme->sme_Name, " EHB", sizeof(sme->sme_Name));
                        if (prop & DIPF_IS_LACE          ) _strcatn(sme->sme_Name, " Interlace", sizeof(sme->sme_Name));
                        if (prop & DIPF_IS_SCANDBL       ) _strcatn(sme->sme_Name, " DblScan", sizeof(sme->sme_Name));
                    } else {
                        stccpy(sme->sme_Name, idmon.Mspc->ms_Node.xln_Name, sizeof(sme->sme_Name));
                    }
                } else {
                    stccpy(sme->sme_Name, "< ??? >", sizeof(sme->sme_Name));
                }
            }

            callback(sme, userData);
        }

        tbFreeVecPooled(globalPool, sme);
    }
}

static void UpdateCallback( struct ScreenModeEntry *sme,
                            void *userData )
{
    struct ScreenModesCallbackUserData *ud = (struct ScreenModesCallbackUserData *)userData;

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

static void PrintCallback( struct ScreenModeEntry *sme,
                           void *userData )
{
    PrintFOneLine((BPTR)userData, " %s %5s %5s %2s  %s\n", sme->sme_Id, sme->sme_Width, sme->sme_Height, sme->sme_Depth, sme->sme_Name);
}

static void SendCallback( struct ScreenModeEntry *sme,
                          void *userData )
{
    SendEncodedEntry((UBYTE *)sme, sizeof(struct ScreenModeEntry));
}

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

    if (obj = (Object *)DoSuperNew(cl, obj,
        MUIA_HelpNode, ScreenModeText,
        MUIA_Window_ID, MakeID('S','M','D','E'),
        WindowContents, VGroup,

            Child, smodelist = MyNListviewObject(MakeID('S','M','L','V'), "BAR,BAR,BAR,BAR,BAR", &smodelist_con2hook, &smodelist_des2hook, &smodelist_dsp2hook, &smodelist_cmp2hook, TRUE),
            Child, MyBelowListview(&smodetext, &smodecount),

            Child, MyVSpace(4),

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

        smwd->smwd_ScreenModeList = smodelist;
        smwd->smwd_ScreenModeText = smodetext;
        smwd->smwd_ScreenModeCount = smodecount;
        smwd->smwd_MoreButton = moreButton;

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

        set(obj, MUIA_Window_Title, MyGetWindowTitle("SCREENMODES", smwd->smwd_Title, sizeof(smwd->smwd_Title)));
        set(obj, MUIA_Window_ActiveObject, smodelist);
        set(moreButton, 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(smodelist,    MUIM_Notify, MUIA_NList_Active,        MUIV_EveryTime, obj,                     1, MUIM_ScreenModesWin_ListChange);
        DoMethod(smodelist,    MUIM_Notify, MUIA_NList_DoubleClick,   MUIV_EveryTime, obj,                     1, MUIM_ScreenModesWin_More);
        DoMethod(updateButton, MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_ScreenModesWin_Update);
        DoMethod(printButton,  MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_ScreenModesWin_Print);
        DoMethod(moreButton,   MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     1, MUIM_ScreenModesWin_More);
        DoMethod(exitButton,   MUIM_Notify, MUIA_Pressed,             FALSE,          obj,                     3, MUIM_Set, MUIA_Window_CloseRequest, TRUE);
        DoMethod(smodelist,    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 ScreenModesWinData *smwd = INST_DATA(cl, obj);

    set(obj, MUIA_Window_Open, FALSE);
    DoMethod(smwd->smwd_ScreenModeList, MUIM_NList_Clear);

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

static ULONG __saveds mUpdate( struct IClass *cl,
                               Object *obj,
                               Msg msg )
{
    struct ScreenModesWinData *smwd = INST_DATA(cl, obj);
    struct ScreenModesCallbackUserData ud;

    ApplicationSleep(TRUE);
    set(smwd->smwd_ScreenModeList, MUIA_NList_Quiet, TRUE);
    DoMethod(smwd->smwd_ScreenModeList, MUIM_NList_Clear);

    ud.ud_List = smwd->smwd_ScreenModeList;
    ud.ud_Count = 0;

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

    SetCountText(smwd->smwd_ScreenModeCount, ud.ud_Count);
    MySetContents(smwd->smwd_ScreenModeText, "");

    set(smwd->smwd_ScreenModeList, MUIA_NList_Quiet, FALSE);
    set(smwd->smwd_MoreButton, MUIA_Disabled, TRUE);
    ApplicationSleep(FALSE);

    return 0;
}

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

    return 0;
}

static ULONG __saveds mMore( struct IClass *cl,
                             Object *obj,
                             Msg msg )
{
    struct ScreenModesWinData *smwd = INST_DATA(cl, obj);
    struct ScreenModeEntry *sme;

    if (sme = (struct ScreenModeEntry *)GetActiveEntry(smwd->smwd_ScreenModeList)) {
        APTR detailWin;

        if (detailWin = ScreenModesDetailWindowObject,
                MUIA_Window_ParentWindow, obj,
                MUIA_Window_MaxChildWindowCount, (opts.SingleWindows) ? 1 : 0,
            End) {
            set(detailWin, MUIA_ScreenModesDetailWin_ScreenMode, sme);
            set(detailWin, MUIA_Window_Open, TRUE);
        }
    }

    return 0;
}

static ULONG __saveds mListChange( struct IClass *cl,
                                   Object *obj,
                                   Msg msg )
{
    struct ScreenModesWinData *smwd = INST_DATA(cl, obj);
    struct ScreenModeEntry *sme;

    if (sme = (struct ScreenModeEntry *)GetActiveEntry(smwd->smwd_ScreenModeList)) {
        MySetContents(smwd->smwd_ScreenModeText, "%s \"%s\"", sme->sme_Id, sme->sme_Name);
        if (!clientstate) set(smwd->smwd_MoreButton, MUIA_Disabled, FALSE);
    }

    return 0;
}

ULONG __asm __saveds ScreenModesWinDispatcher( 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_ScreenModesWin_Update:     return (mUpdate(cl, obj, (APTR)msg));
        case MUIM_ScreenModesWin_Print:      return (mPrint(cl, obj, (APTR)msg));
        case MUIM_ScreenModesWin_More:       return (mMore(cl, obj, (APTR)msg));
        case MUIM_ScreenModesWin_ListChange: return (mListChange(cl, obj, (APTR)msg));
    }

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

void PrintSMode( char *filename )
{
    BPTR handle;

    if (handle = HandlePrintStart(filename)) {
        PrintFOneLine(handle, "\n  ModeID    Wdth  Hght  Dpth  ScreenMode\n\n");
        IterateList(PrintCallback, (void *)handle);
    }

    HandlePrintStop();
}

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

