/*==========================================================================
 *
 *  Copyright (C) 1994-1999 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:	ddrefrsh.c
 *  Content:	DirectDraw Refresh Rate support
 *
 *              On Win98, we don't have detailed information regarding what
 *              refresh rates the montor supports.  We can get some information
 *              from the monitor (EDID data), but we cannot absolutely rely on
 *              it, so we require that the user manually verify each refresh 
 *              rate before we allow it to be used.
 *		
 *  History:
 *   Date	By	Reason
 *   ====	==	======
 *   02-apr-99	smac	Created it
 *
 ***************************************************************************/
#include "ddrawpr.h"
#include "edid.h"

#undef DPF_MODNAME
#define DPF_MODNAME "Refresh"

#ifdef WIN95

static WORD SupportedRefreshRates[] = {60, 75, 85, 100, 120};
#define NUM_SUPPORTED_REFRESH_RATES ( sizeof( SupportedRefreshRates ) / sizeof( WORD ) )


/*
 * GetEDIDData
 */
HRESULT GetEDIDData( LPDDRAWI_DIRECTDRAW_GBL pddd, VESA_EDID * pEDIDData )
{
    memset( pEDIDData, 0, sizeof( VESA_EDID ) );
    if( DD16_GetMonitorEDIDData( pddd->cDriverName, (LPVOID)pEDIDData) )
    {
        return DD_OK;
    }

    if( !( pddd->dwFlags & DDRAWI_DISPLAYDRV ) )
    {
        // HACK: Use primary display EDID data for passthrough devices
        if( DD16_GetMonitorEDIDData( g_szPrimaryDisplay, (LPVOID)pEDIDData) )
        {
            return DD_OK;
        }
    }

    return DDERR_UNSUPPORTED;
}


/*
 * CheckEdidBandiwdth
 *
 * Takes a resoltion/refrsh rate and calculates the bandwidth required for 
 * this, and then updates lpHighestRefresh and lpHighestBandwidth to keep
 * track of the highest refresh rate and badnwidth info that we've encountered.
 */
void CheckEdidBandwidth( DWORD dwWidth, DWORD dwHeight, DWORD dwRefreshRate,
                         LPDWORD lpHighestRefresh, LPDWORD lpHighestBandwidth )
{
    DWORD dwBandwidth;

    dwBandwidth = dwWidth * dwHeight * dwRefreshRate;
    if( dwBandwidth > *lpHighestBandwidth )
    {
        *lpHighestBandwidth = dwBandwidth;
    }
    if( dwRefreshRate > *lpHighestRefresh )
    {
        *lpHighestRefresh = dwRefreshRate;
    }
}
         

/*
 * StdTimeXRES
 */
int StdTimeXRES(WORD StdTime)
{
    if (StdTime == 0 || StdTime == 0x0101)
        return 0;
    else
        return ((StdTime & veStdTime_HorzResMask) + 31) * 8;
}


/*
 * StdTimeYRES
 */
int StdTimeYRES(WORD StdTime)
{
    if (StdTime == 0 || StdTime == 0x0101)
        return 0;

    switch (StdTime & veStdTime_AspectRatioMask)
    {
        case veStdTime_AspectRatio1to1:  return StdTimeXRES(StdTime);
        case veStdTime_AspectRatio4to3:  return StdTimeXRES(StdTime) * 3 / 4;
        case veStdTime_AspectRatio5to4:  return StdTimeXRES(StdTime) * 4 / 5;
        case veStdTime_AspectRatio16to9: return StdTimeXRES(StdTime) * 9 / 16;
    }
    return 0;
}


/*
 * StdTimeRATE
 */
int StdTimeRATE(WORD StdTime)
{
    if (StdTime == 0 || StdTime == 0x0101)
        return 0;
    else
        return ((StdTime & veStdTime_RefreshRateMask) >> 8) + 60;
}


__inline UINT DetTimeXRES(BYTE *DetTime)
{
    return (UINT)DetTime[2] + (((UINT)DetTime[4] & 0xF0) << 4);
}

__inline UINT DetTimeYRES(BYTE *DetTime)
{
    return (UINT)DetTime[5] + (((UINT)DetTime[7] & 0xF0) << 4);
}

__inline UINT DetTimeXBLANK(BYTE *DetTime)
{
    return (UINT)DetTime[3] + (((UINT)DetTime[4] & 0x0F) << 4);
}

__inline UINT DetTimeYBLANK(BYTE *DetTime)
{
    return (UINT)DetTime[6] + (((UINT)DetTime[7] & 0x0F) << 0);
}

int DetTimeRATE(BYTE *DetTime)
{
    ULONG clk;
    ULONG x;
    ULONG y;

    clk = *(WORD*)DetTime;
    x = DetTimeXRES(DetTime) + DetTimeXBLANK(DetTime);
    y = DetTimeYRES(DetTime) + DetTimeYBLANK(DetTime);

    if (clk == 0 || x == 0 || y == 0)
        return 0;

    return (int)((clk * 10000) / (x * y));
}


/*
 * GetDetailedTime
 */
void GetDetailedTime(BYTE *DetTime, LPDWORD lpHighestRefresh, LPDWORD lpHighestBandwidth )
{
    char ach[14];
    int i;
    DWORD dw;

    dw = *(DWORD *)DetTime;

    if( dw == 0xFD000000 )       // Monitor limits
    {
        if( (DWORD)(DetTime[6]) > *lpHighestRefresh )
        {
            *lpHighestRefresh = (DWORD)(DetTime[6]);
        }
    }
    else if (dw == 0xFA000000)       // more standard timings
    {
        WORD * StdTime = (WORD *)&DetTime[5];

        CheckEdidBandwidth( StdTimeXRES( StdTime[0] ),
            StdTimeYRES( StdTime[0] ),
            StdTimeRATE( StdTime[0] ),
            lpHighestRefresh, lpHighestBandwidth );
        CheckEdidBandwidth( StdTimeXRES( StdTime[1] ),
            StdTimeYRES( StdTime[1] ),
            StdTimeRATE( StdTime[1] ),
            lpHighestRefresh, lpHighestBandwidth );
        CheckEdidBandwidth( StdTimeXRES( StdTime[2] ),
            StdTimeYRES( StdTime[2] ),
            StdTimeRATE( StdTime[2] ),
            lpHighestRefresh, lpHighestBandwidth );
        CheckEdidBandwidth( StdTimeXRES( StdTime[3] ),
            StdTimeYRES( StdTime[3] ),
            StdTimeRATE( StdTime[3] ),
            lpHighestRefresh, lpHighestBandwidth );
        CheckEdidBandwidth( StdTimeXRES( StdTime[4] ),
            StdTimeYRES( StdTime[4] ),
            StdTimeRATE( StdTime[4] ),
            lpHighestRefresh, lpHighestBandwidth );
        CheckEdidBandwidth( StdTimeXRES( StdTime[5] ),
            StdTimeYRES( StdTime[5] ),
            StdTimeRATE( StdTime[5] ),
            lpHighestRefresh, lpHighestBandwidth );
    }
    else if( ( dw != 0xFF000000 ) &&      // Serial number
             ( dw != 0xFE000000 ) &&      // Monitor String
             ( dw != 0xFC000000 ) &&      // Monitor Name
             ( dw != 0xFB000000 ) &&      // ColorPoint data
             ( DetTimeRATE( DetTime) ) )
    {
        CheckEdidBandwidth( DetTimeXRES( DetTime ),
            DetTimeYRES( DetTime ),
            DetTimeRATE( DetTime ),
            lpHighestRefresh, lpHighestBandwidth );
    }
}


/*
 * EvaluateMonitor
 *
 * Determines the amount of bandwidth that the monitor can handle.
 */
void EvaluateMonitor( VESA_EDID *lpEdidData, DWORD *lpHighestRefresh, DWORD *lpHighestBandwidth )
{
    BYTE chk;
    int i;

    *lpHighestRefresh = 0;
    *lpHighestBandwidth = 0;

    /*
     * Do some sanity checking to make sure that the EDID data looks sane
     */

    for( chk = i = 0; i < 128; i++)
    {
        chk += ((BYTE *)lpEdidData)[i];
    }
    if (chk != 0)
    {
        // Bad checksum
        return;
    }

    /*
     * First get the bandwidth from the established timings
     */
    if( lpEdidData->veEstTime1 & veEstTime1_720x400x70Hz)
    {
        CheckEdidBandwidth( 720, 400, 70, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime1 & veEstTime1_720x400x88Hz)
    {
        CheckEdidBandwidth( 720, 400, 88, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime1 & veEstTime1_640x480x60Hz)
    {
        CheckEdidBandwidth( 640, 480, 60, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime1 & veEstTime1_640x480x67Hz)
    {
        CheckEdidBandwidth( 640, 480, 67, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime1 & veEstTime1_640x480x72Hz)
    {
        CheckEdidBandwidth( 640, 480, 72, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime1 & veEstTime1_640x480x75Hz)
    {
        CheckEdidBandwidth( 640, 480, 75, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime1 & veEstTime1_800x600x60Hz)
    {
        CheckEdidBandwidth( 800, 600, 60, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime2 & veEstTime2_800x600x72Hz)
    {
        CheckEdidBandwidth( 800, 600, 72, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime2 & veEstTime2_800x600x75Hz)
    {
        CheckEdidBandwidth( 800, 600, 75, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime2 & veEstTime2_1024x768x60Hz)
    {
        CheckEdidBandwidth( 1024, 768, 60, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime2 & veEstTime2_1024x768x70Hz)
    {
        CheckEdidBandwidth( 1024, 768, 70, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime2 & veEstTime2_1024x768x75Hz)
    {
        CheckEdidBandwidth( 1024, 768, 75, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime2 & veEstTime2_1280x1024x75Hz)
    {
        CheckEdidBandwidth( 1280, 1024, 75, lpHighestRefresh, lpHighestBandwidth );
    }
    if( lpEdidData->veEstTime3 & veEstTime3_1152x870x75Hz)
    {
        CheckEdidBandwidth( 1152, 870, 75, lpHighestRefresh, lpHighestBandwidth );
    }

    /*
     * Now get the bandwidth from the standard timings
     */
    CheckEdidBandwidth( StdTimeXRES( lpEdidData->veStdTimeID1 ),
        StdTimeYRES( lpEdidData->veStdTimeID1 ),
        StdTimeRATE( lpEdidData->veStdTimeID1 ),
        lpHighestRefresh, lpHighestBandwidth );
    CheckEdidBandwidth( StdTimeXRES( lpEdidData->veStdTimeID2 ),
        StdTimeYRES( lpEdidData->veStdTimeID2 ),
        StdTimeRATE( lpEdidData->veStdTimeID2 ),
        lpHighestRefresh, lpHighestBandwidth );
    CheckEdidBandwidth( StdTimeXRES( lpEdidData->veStdTimeID3 ),
        StdTimeYRES( lpEdidData->veStdTimeID3 ),
        StdTimeRATE( lpEdidData->veStdTimeID3 ),
        lpHighestRefresh, lpHighestBandwidth );
    CheckEdidBandwidth( StdTimeXRES( lpEdidData->veStdTimeID4 ),
        StdTimeYRES( lpEdidData->veStdTimeID4 ),
        StdTimeRATE( lpEdidData->veStdTimeID4 ),
        lpHighestRefresh, lpHighestBandwidth );
    CheckEdidBandwidth( StdTimeXRES( lpEdidData->veStdTimeID5 ),
        StdTimeYRES( lpEdidData->veStdTimeID5 ),
        StdTimeRATE( lpEdidData->veStdTimeID5 ),
        lpHighestRefresh, lpHighestBandwidth );
    CheckEdidBandwidth( StdTimeXRES( lpEdidData->veStdTimeID6 ),
        StdTimeYRES( lpEdidData->veStdTimeID6 ),
        StdTimeRATE( lpEdidData->veStdTimeID6 ),
        lpHighestRefresh, lpHighestBandwidth );
    CheckEdidBandwidth( StdTimeXRES( lpEdidData->veStdTimeID7 ),
        StdTimeYRES( lpEdidData->veStdTimeID7 ),
        StdTimeRATE( lpEdidData->veStdTimeID7 ),
        lpHighestRefresh, lpHighestBandwidth );
    CheckEdidBandwidth( StdTimeXRES( lpEdidData->veStdTimeID8 ),
        StdTimeYRES( lpEdidData->veStdTimeID8 ),
        StdTimeRATE( lpEdidData->veStdTimeID8 ),
        lpHighestRefresh, lpHighestBandwidth );

    /*
     * Now get the detailed timing information
     */
    GetDetailedTime( lpEdidData->veDetailTime1, lpHighestRefresh, lpHighestBandwidth );
    GetDetailedTime( lpEdidData->veDetailTime2, lpHighestRefresh, lpHighestBandwidth );
    GetDetailedTime( lpEdidData->veDetailTime3, lpHighestRefresh, lpHighestBandwidth );
    GetDetailedTime( lpEdidData->veDetailTime4, lpHighestRefresh, lpHighestBandwidth );
}


//=============================================================================
//
// Function Description:
//
//   Finds an item in a registry-based most recently used (MRU) list, and
//   either retrieves the contents of that item, or updates (add if it doesn't
//   exist) the item.
//
// Arguments:
//
//   [IN/OUT] item - Contains at least the unique portion of the item to
//                   search for [IN/OUT]. If writing the item, should contain
//                   the entire item [IN].
//   [IN] writeItem - Set to TRUE if updating/adding an item to the MRU list.
//
// Return Value:
//
//   TRUE - If writeItem is TRUE, then the item was written to the registry.
//          Otherwise the item was found and its contents stored in findItem.
//   FALSE - Failure; no more information available.
//
// Created:
//
//   04/08/1999 johnstep
//
//=============================================================================

//-----------------------------------------------------------------------------
// Define global MRU list values here, for simplicity:
//
//   gMruRegKey - Registry key where MRU list is stored
//   gMruRegOrderValue - Name of MRU list order value
//   gMruBaseChar - Base index into MRU list
//   gMruMaxChar - Maximum index into MRU list
//   gMruItemSize - Size of findItem.
//   gMruUniqueOffset - Offset of unique portion of item. This unique portion
//                      is what will be used to compare items.
//   gMruUniqueSize - Size of unique portion of item.
//-----------------------------------------------------------------------------

static const CHAR *gMruRegKey =
    REGSTR_PATH_DDRAW "\\" REGSTR_KEY_RECENTMONITORS;
static const CHAR *gMruRegOrderValue = REGSTR_VAL_DDRAW_MONITORSORDER;
#define gMruBaseChar '0'
#define gMruMaxChar '9'
#define gMruItemSize sizeof (DDMONITORINFO)
#define gMruUniqueOffset 0
#define gMruUniqueSize offsetof(DDMONITORINFO, Mode640x480)

BOOL
MruList(
    VOID *item,
    const BOOL writeItem
    )
{
    BOOL success = FALSE;
    HKEY hkey;

    // Create or open the root key, with permission to query and set values;
    // only continue if successful:
    
    if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, gMruRegKey,
        0, NULL, 0, KEY_QUERY_VALUE | KEY_SET_VALUE,
        NULL, &hkey, NULL) == ERROR_SUCCESS)
    {
        CHAR mruOrder[gMruMaxChar - gMruBaseChar + 2];
        DWORD type;
        DWORD count = sizeof mruOrder;
        UINT i;

        {
            CHAR temp[sizeof mruOrder];

            // If we read the order value successfully, copy the valid chars
            // into mruOrder, removing duplicates:
        
            if (RegQueryValueEx(hkey, gMruRegOrderValue, NULL, &type,
                (BYTE *) temp, &count) == ERROR_SUCCESS)
            {
                UINT j = 0;

                for (--count, i = 0; i < count; i++)
                {
                    if ((temp[i] >= gMruBaseChar) &&
                        (temp[i] <= gMruMaxChar))
                    {
                        UINT k;

                        for (k = 0; k < j; k++)
                        {
                            if (mruOrder[k] == temp[i])
                            {
                                break;
                            }
                        }
                        if (k == j)
                        {
                            mruOrder[j++] = temp[i];
                        }
                    }
                }
                count = j;
            }
            else
            {
                count = 0;
            }
        }

        // Only continue if we found at least one valid value in the order
        // list, or if we're writing the item:
        
        if ((count > 0) || writeItem)
        {
            CHAR regValue[2];
            BYTE regItem[gMruItemSize];

            regValue[1] = '\0';

            // Search for the item in the MRU list:
            
            for (i = 0; i < count; i++)
            {
                DWORD size = sizeof regItem;

                regValue[0] = mruOrder[i];

                if ((RegQueryValueEx(hkey, regValue, NULL, &type,
                    (BYTE *) &regItem, &size) == ERROR_SUCCESS) &&
                    (size == sizeof regItem))
                {
                    if (memcmp(
                        (BYTE *) &regItem + gMruUniqueOffset,
                        (BYTE *) item + gMruUniqueOffset,
                        gMruUniqueSize) == 0)
                    {
                        break;
                    }
                }
            }

            // Keep going if we found the item or in any case if we need to
            // write the item:
            
            if ((i < count) || writeItem)
            {
                UINT j;

                // If we didn't find the item, then we must be writing, so
                // adjust the index appropriately. If the list is already
                // full, we just use the last item (LRU item), otherwise, add
                // a new item:
                
                if (i == count)
                {
                    if (count == (gMruMaxChar - gMruBaseChar + 1))
                    {
                        i--;
                    }
                    else
                    {
                        // Adding a new item; search for the lowest unused
                        // valid char:

                        for (mruOrder[i] = gMruBaseChar;
                             mruOrder[i] < gMruMaxChar;
                             mruOrder[i]++)
                        {
                            for (j = 0; j < i; j++)
                            {
                                if (mruOrder[j] == mruOrder[i])
                                {
                                    break;
                                }
                            }
                            if (j == i)
                            {
                                break;
                            }
                        }
                        count++;
                    }
                }
            
                // Update the MRU order list if necessary. We update if we
                // found or are adding an item after the first, or if this is
                // the first item in the list:
                
                if (i > 0 || (count == 1))
                {
                    // Bubble found item to head of order list:

                    for (j = i; j > 0; j--)
                    {
                        CHAR temp = mruOrder[j];
                        mruOrder[j] = mruOrder[j - 1];
                        mruOrder[j - 1] = temp;
                    }

                    // Write the order list:
                    
                    mruOrder[count] = '\0';
                    RegSetValueEx(hkey, gMruRegOrderValue, 0,
                        REG_SZ, (BYTE *) mruOrder, count + 1);
                }

                // If we want to write the item, do it now. We will always
                // write to the first item in the order list. If not writing,
                // copy what was read from the registry into item:
                
                if (writeItem)
                {
                    regValue[0] = mruOrder[0];
                    if (RegSetValueEx(hkey, regValue, 0,
                        REG_BINARY, (BYTE *) item, sizeof regItem) ==
                        ERROR_SUCCESS)
                    {
                        success = TRUE;
                    }
                }
                else
                {
                    memcpy(
                        (BYTE *) item + gMruUniqueOffset,
                        regItem + gMruUniqueOffset,
                        sizeof regItem);

                    success = TRUE;
                }
            }
        }

        // Always close the registry key when finished:
        
        RegCloseKey(hkey);
    }

    return success;
}

//-----------------------------------------------------------------------------


/*
 * DDSaveMonitorInfo
 * 
 * Writes the monitor info to the registry.
 */
HRESULT DDSaveMonitorInfo( LPDDRAWI_DIRECTDRAW_INT lpDD_int )
{
    return MruList( (VOID *) lpDD_int->lpLcl->lpGbl->lpMonitorInfo, TRUE ) ?
        DD_OK : DDERR_GENERIC;
}


__inline IsValidRefreshRate( DWORD dwWidth, DWORD dwHeight, int refreshRate,
                             DWORD dwHighestBandwidth )
{
    return
        ( ( refreshRate >= 0 ) &&
        ( ( dwWidth * dwHeight * (DWORD) refreshRate ) <= dwHighestBandwidth ) );
}


/*
 * DDGetMonitorInfo
 * 
 * Reads the monitor info from the registry and verifies that it still applies.
 */
HRESULT DDGetMonitorInfo( 
                LPDDRAWI_DIRECTDRAW_INT lpDD_int )
{
    LPDDMONITORINFO pInfo;
    static DDDEVICEIDENTIFIER DeviceIdentifier;
    HRESULT hr;

    if( ( lpDD_int->lpVtbl == &dd7Callbacks ) &&
        ( lpDD_int->lpLcl->lpGbl->lpMonitorInfo == NULL ) )
    {
        VESA_EDID EDIDData;
        DWORD dwHighestRefresh;
        DWORD dwHighestBandwidth;
        HKEY hKey;
        BOOL bGotLastMonitor = FALSE;

        hr = GetEDIDData( lpDD_int->lpLcl->lpGbl, &EDIDData );
        if( hr != DD_OK )
        {
            // There is no EDID data
            return DDERR_GENERIC;
        }
        EvaluateMonitor( &EDIDData, &dwHighestRefresh, &dwHighestBandwidth );

        hr = DD_GetDeviceIdentifier( (LPDIRECTDRAW) lpDD_int, &DeviceIdentifier, 0 );
        if( hr != DD_OK )
        {
            // Failed to get device identifier for monitor info
            return hr;
        }

        pInfo = (LPDDMONITORINFO) MemAlloc( sizeof( DDMONITORINFO ) );
        if( pInfo == NULL )
        {
            // Out of memory allocating monitor info structure
            return DDERR_OUTOFMEMORY;
        }

        pInfo->Manufacturer = *(WORD *)&EDIDData.veManufactID[0];
        pInfo->Product = *(WORD *)&EDIDData.veProductCode[0];
        pInfo->SerialNumber = EDIDData.veSerialNbr;
        pInfo->DeviceIdentifier = DeviceIdentifier.guidDeviceIdentifier;

        // Read monitor information from registry, if available. We need to
        // compare this to the EDID data to see if the monitor or adapter
        // changed and verify the selected refresh rates are sane:
    
        if( MruList( (VOID *) pInfo, FALSE ) )
        {
            // Validate modes here against EDID data:

            if( !IsValidRefreshRate( 640, 480,
                pInfo->Mode640x480, dwHighestBandwidth ) )
            {
                pInfo->Mode640x480 = -1;
            }
    
            if( !IsValidRefreshRate( 800, 600,
                pInfo->Mode800x600, dwHighestBandwidth ) )
            {
                pInfo->Mode800x600 = -1;
            }
    
            if( !IsValidRefreshRate( 1024, 768,
                pInfo->Mode1024x768, dwHighestBandwidth ) )
            {
                pInfo->Mode1024x768 = -1;
            }
    
            if( !IsValidRefreshRate( 1280, 1024,
                pInfo->Mode1280x1024, dwHighestBandwidth ) )
            {
                pInfo->Mode1280x1024 = -1;
            }
    
            if( !IsValidRefreshRate( 1600, 1200,
                pInfo->Mode1600x1200, dwHighestBandwidth ) )
            {
                pInfo->Mode1600x1200 = -1;
            }
    
            bGotLastMonitor = TRUE;
        }
    
        if( !bGotLastMonitor )
        {
            pInfo->Mode640x480 = -1;
            pInfo->Mode800x600 = -1;
            pInfo->Mode1024x768 = -1;
            pInfo->Mode1280x1024 = -1;
            pInfo->Mode1600x1200 = -1;
        }

        pInfo->ModeReserved1 = -1;
        pInfo->ModeReserved2 = -1;
        pInfo->ModeReserved3 = -1;

        lpDD_int->lpLcl->lpGbl->lpMonitorInfo = pInfo;
    }
    return DD_OK;
}


/*
 * ExpandModeTable
 * 
 * On Win9X, drivers can specify their maximum refresh rate for each mode, 
 * allowing DDraw to add modes for each refresh rate that we care about.
 * This allows drivers to add refresh rate easily without having to 
 * maintain huge tables.  This also allows us to avoid regressions by allowing
 * us to only enumerate these refresh rates on newer interfaces.
 */
HRESULT ExpandModeTable( LPDDRAWI_DIRECTDRAW_GBL pddd )
{
    DWORD i;
    DWORD j;
    DWORD iNumModes = 0;
    LPDDHALMODEINFO pNewModeTable;
    DWORD iModeIndex;
    WORD wMaxRefresh;

    /*
     * Count the number of entries that we'll need
     */
    if( pddd->lpModeInfo != NULL )
    {
        for( i = 0; i < pddd->dwNumModes;  i++ )
        {
            iNumModes++;
            if( pddd->lpModeInfo[i].wFlags & DDMODEINFO_MAXREFRESH )
            {
                for( j = 0; j < NUM_SUPPORTED_REFRESH_RATES; j++ )
                {
                    if( SupportedRefreshRates[j] <= pddd->lpModeInfo[i].wRefreshRate )
                    {
                        iNumModes++;
                    }
                }
            }
        }

        if( iNumModes > pddd->dwNumModes )
        {
            /*
             * We do have to add modes and allocate a new table
             */
            pNewModeTable = (LPDDHALMODEINFO) MemAlloc( sizeof( DDHALMODEINFO ) * iNumModes );
            if( pNewModeTable == NULL )
            {
                /*
                 * Instead of failing here, we'll just clear all of the MAXREFRESHRATE
                 * flags and set the rate to 0.
                 */
                for( i = 0; i < pddd->dwNumModes; i++ )
                {
                    if( pddd->lpModeInfo[i].wFlags & DDMODEINFO_MAXREFRESH )
                    {
                        pddd->lpModeInfo[i].wFlags &= ~DDMODEINFO_MAXREFRESH;
                        pddd->lpModeInfo[i].wRefreshRate = 0;
                    }
                }
            }
            else
            {
                memcpy( pNewModeTable, pddd->lpModeInfo, pddd->dwNumModes * sizeof( DDHALMODEINFO ) );

                /*
                 * Now add the new refresh rates
                 */
                iModeIndex = pddd->dwNumModes;
                for( i = 0; i < pddd->dwNumModes; i++ )
                {
                    if( pddd->lpModeInfo[i].wFlags & DDMODEINFO_MAXREFRESH )
                    {
                        pNewModeTable[i].wFlags &= ~DDMODEINFO_MAXREFRESH;
                        wMaxRefresh = pNewModeTable[i].wRefreshRate;
                        pNewModeTable[i].wRefreshRate = 0;

                        for( j = 0; j < NUM_SUPPORTED_REFRESH_RATES; j++ )
                        {
                            if( SupportedRefreshRates[j] <= wMaxRefresh )
                            {
                                memcpy( &(pNewModeTable[iModeIndex]), &(pNewModeTable[i]), sizeof( DDHALMODEINFO ) );
                                pNewModeTable[iModeIndex].wFlags |= DDMODEINFO_DX7ONLY;
                                pNewModeTable[iModeIndex++].wRefreshRate = SupportedRefreshRates[j];
                            }
                        }
                    }
                }

                MemFree( pddd->lpModeInfo );
                pddd->lpModeInfo = pNewModeTable;
                pddd->dwNumModes = iModeIndex;
            }
        }
    }

    return DD_OK;
}


/*
 * CanMonitorHandleRefreshRate
 * 
 * Has the specified refresh rate been tested and verified that it works?
 */
BOOL CanMonitorHandleRefreshRate( LPDDRAWI_DIRECTDRAW_GBL pddd, DWORD dwWidth, DWORD dwHeight, int wRefresh )
{
    if( wRefresh == 0 )
    {
        return TRUE;
    }

    if( pddd->lpMonitorInfo == NULL )
    {
        return FALSE;
    }

    /*
     * If we are setting this mode because we are testing it, then we should
     * allow it so the user can verify whether it worked or not.
     */
    if( pddd->dwFlags & DDRAWI_TESTINGMODES )
    {
        return TRUE;
    }

    if( ( dwWidth <= 640 ) && ( dwHeight <= 480 ) )
    {
        if( pddd->lpMonitorInfo->Mode640x480 >= wRefresh )
        {
            return TRUE;
        }
    }

    if( ( dwWidth <= 800 ) && ( dwHeight <= 600 ) )
    {
        if( pddd->lpMonitorInfo->Mode800x600 >= wRefresh )
        {
            return TRUE;
        }
    }

    if( ( dwWidth <= 1024 ) && ( dwHeight <= 768 ) )
    {
        if( pddd->lpMonitorInfo->Mode1024x768 >= wRefresh )
        {
            return TRUE;
        }
    }

    if( ( dwWidth <= 1280 ) && ( dwHeight <= 1024 ) )
    {
        if( pddd->lpMonitorInfo->Mode1280x1024 >= wRefresh )
        {
            return TRUE;
        }
    }

    if( ( dwWidth <= 1600 ) && ( dwHeight <= 1200 ) )
    {
        if( pddd->lpMonitorInfo->Mode1600x1200 >= wRefresh )
        {
            return TRUE;
        }
    }

    return FALSE;
}


/*
 * IsModeTested
 *
 * Determines if we already have data for the requested mode.
 */
BOOL IsModeTested( LPDDRAWI_DIRECTDRAW_GBL pddd, DWORD dwWidth, DWORD dwHeight )
{
    if( pddd->lpMonitorInfo == NULL )
    {
        return FALSE;
    }

    if( ( dwWidth <= 640 ) && ( dwHeight <= 480 ) )
    {
        if( pddd->lpMonitorInfo->Mode640x480 != -1 )
        {
            return TRUE;
        }
    }

    else if( ( dwWidth <= 800 ) && ( dwHeight <= 600 ) )
    {
        if( pddd->lpMonitorInfo->Mode800x600 != -1 )
        {
            return TRUE;
        }
    }

    else if( ( dwWidth <= 1024 ) && ( dwHeight <= 768 ) )
    {
        if( pddd->lpMonitorInfo->Mode1024x768 != -1 )
        {
            return TRUE;
        }
    }

    else if( ( dwWidth <= 1280 ) && ( dwHeight <= 1024 ) )
    {
        if( pddd->lpMonitorInfo->Mode1280x1024 != -1 )
        {
            return TRUE;
        }
    }

    else if( ( dwWidth <= 1600 ) && ( dwHeight <= 1200 ) )
    {
        if( pddd->lpMonitorInfo->Mode1600x1200 != -1 )
        {
            return TRUE;
        }
    }

    return FALSE;
}


/*
 * UpdateMonitorInfo
 */
void UpdateMonitorInfo( LPDDRAWI_DIRECTDRAW_GBL pddd, DWORD dwWidth, DWORD dwHeight, int iRefreshRate )
{
    if( pddd->lpMonitorInfo == NULL )
    {
        return;
    }

    if( ( dwWidth <= 640 ) && ( dwHeight <= 480 ) )
    {
        pddd->lpMonitorInfo->Mode640x480 = iRefreshRate;
    }

    else if( ( dwWidth <= 800 ) && ( dwHeight <= 600 ) )
    {
        pddd->lpMonitorInfo->Mode800x600 = iRefreshRate;
    }

    else if( ( dwWidth <= 1024 ) && ( dwHeight <= 768 ) )
    {
        pddd->lpMonitorInfo->Mode1024x768 = iRefreshRate;
    }

    else if( ( dwWidth <= 1280 ) && ( dwHeight <= 1024 ) )
    {
        pddd->lpMonitorInfo->Mode1280x1024 = iRefreshRate;
    }

    else if( ( dwWidth <= 1600 ) && ( dwHeight <= 1200 ) )
    {
        pddd->lpMonitorInfo->Mode1600x1200 = iRefreshRate;
    }
}


/*
 * GetModeToTest
 */
HRESULT GetModeToTest( DWORD dwInWidth, DWORD dwInHeight, 
                       LPDWORD lpdwOutWidth, LPDWORD lpdwOutHeight )
{
    if( ( dwInWidth <= 640 ) && ( dwInHeight <= 480 ) )
    {
        *lpdwOutWidth = 640;
        *lpdwOutHeight = 480;
    }

    else if( ( dwInWidth <= 800 ) && ( dwInHeight <= 600 ) )
    {
        *lpdwOutWidth = 800;
        *lpdwOutHeight = 600;
    }

    else if( ( dwInWidth <= 1024 ) && ( dwInHeight <= 768 ) )
    {
        *lpdwOutWidth = 1024;
        *lpdwOutHeight = 768;
    }

    else if( ( dwInWidth <= 1280 ) && ( dwInHeight <= 1024 ) )
    {
        *lpdwOutWidth = 1280;
        *lpdwOutHeight = 1024;
    }

    else if( ( dwInWidth <= 1600 ) && ( dwInHeight <= 1200 ) )
    {
        *lpdwOutWidth = 1600;
        *lpdwOutHeight = 1200;
    }
    else
    {
        return DDERR_GENERIC;
    }

    return DD_OK;
}


/*
 * GuestimateRefreshRate
 */
int GuestimateRefreshRate( LPDDRAWI_DIRECTDRAW_GBL pddd, DWORD dwWidth, DWORD dwHeight,
                           DWORD dwHighestRefresh, DWORD dwHighestBandwidth )
{
    int i;
    DWORD dwBandwidth;

    if( ( pddd->lpMonitorInfo == NULL ) ||
        ( dwHighestRefresh == 0 ) )
    {
        return 0;
    }

    // Sanity check to see if the monitor can handle the resolution

    if( !MonitorCanHandleMode( pddd, dwWidth, dwHeight, 0 ) )
    {
        return 0;
    }

    // If the monitor did not return any refresh rates higher than 60,
    // something is up so we'd better stick to it.

    if( dwHighestRefresh == 60 )
    {
        return 60;
    }

    // Likwise, we will only go after the 100+ refresh rates if the monitor
    // enumerated a refresh rate of at least 85hz.  This may be an unnecesary
    // restiction, but it seems safe.

    for( i = NUM_SUPPORTED_REFRESH_RATES - 1; i >= 0; i-- )
    {
        if( ( SupportedRefreshRates[i] <= 85 ) ||
            ( dwHighestRefresh >= 85 ) )
        {
            dwBandwidth = dwWidth * dwHeight * SupportedRefreshRates[i];
            if( dwBandwidth <= dwHighestBandwidth )
            {
                return SupportedRefreshRates[i];
            }
        }
    }

    return 0;
}


/*
 * SetTheMode
 */
HRESULT SetTheMode( LPDIRECTDRAW7 lpDD, LPMODETESTCONTEXT pContext )
{
    HRESULT hr;
    DWORD dwBPP;

    /*
     * We set an internal flag indicating that we are running a mode test.
     * This lets CanMonitorHandleRefreshRate know that the requested mode
     * should be used, even though it hasb't successfully been tested yet.
     */
    ((LPDDRAWI_DIRECTDRAW_INT)lpDD)->lpLcl->lpGbl->dwFlags |= DDRAWI_TESTINGMODES;

    //
    // There might be a case in which we decided that the lowest BPP the driver
    // could do for this mode was, say, 8bpp. But that didn't take into consideration
    // what rates the driver said it could do. E.g. if the driver (for whatever 
    // reason) doesn't advertise 8bpp at 60Hz, but DOES advertise 16bpp at 60Hz
    // then we can go ahead and use the 16bpp mode. We are here to test monitor
    // bandwidth, not the DAC. The monitor's bandwidth is entirely determined
    // by the spatial resolution and refresh rate. If we bump the driver to 
    // a higher BPP (one that it says it can do), then we are still testing 
    // the correct monitor setup.
    // We do this in a really dumb way: just keep cranking up from the lowest BPP
    // (which is in the context mode list) in steps of 8 until we succeed the
    // mode change, or exceed 32bpp.
    //
    dwBPP = pContext->lpModeList[pContext->dwCurrentMode].dwBPP;
    do
    {
        hr = DD_SetDisplayMode2( (LPDIRECTDRAW)lpDD,
                pContext->lpModeList[pContext->dwCurrentMode].dwWidth,
                pContext->lpModeList[pContext->dwCurrentMode].dwHeight,
                dwBPP,
                pContext->lpModeList[pContext->dwCurrentMode].dwRefreshRate,
                DDSDM_STANDARDVGAMODE );

        dwBPP += 8;
    } while(FAILED(hr) && (dwBPP <= 32) );

    ((LPDDRAWI_DIRECTDRAW_INT)lpDD)->lpLcl->lpGbl->dwFlags &= ~DDRAWI_TESTINGMODES;

    return hr;
}


/*
 * SetupNextTest
 */
void SetupNextTest( LPDIRECTDRAW7 lpDD, LPMODETESTCONTEXT pContext )
{
    int i;
    
    /*
     * Drop back to the next refrsh rate if there is one, otherwise
     * move on to the next mode.
     */
    for( i = NUM_SUPPORTED_REFRESH_RATES - 1; i >= 0; i-- )
    {
        if( SupportedRefreshRates[i] < 
            pContext->lpModeList[pContext->dwCurrentMode].dwRefreshRate )
        {
            pContext->lpModeList[pContext->dwCurrentMode].dwRefreshRate =
                SupportedRefreshRates[i];
            break;
        }
    }
    if( i < 0 )
    {
        // We've tried everything in this mode, so move on

        UpdateMonitorInfo( ((LPDDRAWI_DIRECTDRAW_INT)lpDD)->lpLcl->lpGbl, 
            pContext->lpModeList[pContext->dwCurrentMode].dwWidth,
            pContext->lpModeList[pContext->dwCurrentMode].dwHeight,
            0 );

        pContext->dwCurrentMode++;
    }
}


/*
 * RunNextTest
 */
HRESULT RunNextTest( LPDIRECTDRAW7 lpDD, LPMODETESTCONTEXT pContext )
{
    HRESULT hr;
    LPDDRAWI_DIRECTDRAW_GBL pddd;

    do
    {
        if( pContext->dwCurrentMode >= pContext->dwNumModes )
        {
            // Restore the mode if we've changed it

            pddd = ((LPDDRAWI_DIRECTDRAW_INT)lpDD)->lpLcl->lpGbl;
            if( pContext->dwOrigModeIndex != pddd->dwModeIndex )
            {
                DD_SetDisplayMode2( (LPDIRECTDRAW)lpDD,
                    pddd->lpModeInfo[pContext->dwOrigModeIndex].dwWidth,
                    pddd->lpModeInfo[pContext->dwOrigModeIndex].dwHeight,
                    pddd->lpModeInfo[pContext->dwOrigModeIndex].dwBPP,
                    pddd->lpModeInfo[pContext->dwOrigModeIndex].wRefreshRate,
                    0 );
            }

            DDSaveMonitorInfo( (LPDDRAWI_DIRECTDRAW_INT)lpDD );

            MemFree( pContext->lpModeList );
            MemFree( pContext );
            ((LPDDRAWI_DIRECTDRAW_INT)lpDD)->lpLcl->lpModeTestContext = NULL;

            return DDERR_TESTFINISHED;
        }
        hr = SetTheMode( lpDD, pContext );
        if( hr != DD_OK )
        {
            SetupNextTest( lpDD, pContext );
        }
    } while( ( hr != DD_OK ) && (hr != DDERR_TESTFINISHED ) );

    if( hr != DDERR_TESTFINISHED )
    {
        pContext->dwTimeStamp =  GetTickCount();
    }

    return hr;
}


#endif

/*
 * DD_StartModeTest
 *
 * Indicates that the app wants to start testing a mode (or modes).
 */
HRESULT DDAPI DD_StartModeTest( LPDIRECTDRAW7 lpDD, LPSIZE lpModesToTest, DWORD dwNumEntries, DWORD dwFlags )
{
#ifdef WIN95
    LPDDRAWI_DIRECTDRAW_INT     this_int;
    LPDDRAWI_DIRECTDRAW_LCL     this_lcl;
    LPDDRAWI_DIRECTDRAW_GBL     this;
    BOOL                        excl_exists;
    BOOL                        is_excl;
    LPMODETESTCONTEXT           pContext;
    DWORD                       i;
    DWORD                       j;
    HRESULT                     hr;
    DWORD                       dwRefreshRate;
    DWORD                       dwModeWidth;
    DWORD                       dwModeHeight;
    VESA_EDID                   EDIDData;
    DWORD                       dwHighestRefresh;
    DWORD                       dwHighestBandwidth;

    ENTER_DDRAW();
#endif


    DPF(2,A,"ENTERAPI: DD_StartModeTest");

#ifdef WINNT

    return DDERR_TESTFINISHED;

#else
    TRY
    {
        this_int = (LPDDRAWI_DIRECTDRAW_INT) lpDD;
        if( !VALID_DIRECTDRAW_PTR( this_int ) )
        {
            LEAVE_DDRAW();
            return DDERR_INVALIDOBJECT;
        }
        this_lcl = this_int->lpLcl;
        this = this_lcl->lpGbl;

        if( this->lpMonitorInfo == NULL )
        {
            // There is no monitor info
            LEAVE_DDRAW();
            return DDERR_NOMONITORINFORMATION;
        }

        if( this_lcl->lpModeTestContext != NULL )
        {
            DPF_ERR( "Mode test already running" );
            LEAVE_DDRAW();
            return DDERR_INVALIDPARAMS;
        }        

        if( dwFlags & ~DDSMT_VALID )
        {
            DPF_ERR( "Invalid Flags specified" );
            LEAVE_DDRAW();
            return DDERR_INVALIDPARAMS;
        }

        if( ( dwFlags & DDSMT_ISTESTREQUIRED ) &&
            ( lpModesToTest == NULL ) )
        {
            DPF_ERR( "No modes specified to test" );
            LEAVE_DDRAW();
            return DDERR_INVALIDPARAMS;
        }

        CheckExclusiveMode( this_lcl, &excl_exists, &is_excl, FALSE, NULL, FALSE);
        if( lpModesToTest != NULL )
        {
            if ( ( dwNumEntries == 0 ) ||
                 ( !VALID_BYTE_ARRAY( lpModesToTest, sizeof( SIZE ) * dwNumEntries ) ) )
            {
                DPF_ERR( "Invalid mode list specified" );
                LEAVE_DDRAW();
                return DDERR_INVALIDPARAMS;
            }
    
            /*
             * The app must own exclusive mode to test the modes
             */
            if ( !is_excl ||  !(this->dwFlags & DDRAWI_FULLSCREEN) )
            {
                DPF_ERR( "Must be in full-screen exclusive mode to test the modes" );
                LEAVE_DDRAW();
                return DDERR_NOEXCLUSIVEMODE;
            }
        }
        else
        {
            /*
             * There must not be another app running which owns exclusive mode
             */
            if( !excl_exists || is_excl )
            {
                this->lpMonitorInfo->Mode640x480 = -1;
                this->lpMonitorInfo->Mode800x600 = -1;
                this->lpMonitorInfo->Mode1024x768 = -1;
                this->lpMonitorInfo->Mode1280x1024 = -1;
                this->lpMonitorInfo->Mode1600x1200 = -1;
                this->lpMonitorInfo->ModeReserved1 = -1;
                this->lpMonitorInfo->ModeReserved2 = -1;
                this->lpMonitorInfo->ModeReserved3 = -1;
                
                hr = DDSaveMonitorInfo( this_int );
                LEAVE_DDRAW();
                return hr;
            }
            else
            {
                DPF_ERR( "Cannot reset monitor info; another app owns exclusive mode" );
                LEAVE_DDRAW();
                return DDERR_NOEXCLUSIVEMODE;
            }

        }
    }
    EXCEPT( EXCEPTION_EXECUTE_HANDLER )
    {
        DPF_ERR( "Exception encountered validating parameters" );
        LEAVE_DDRAW();
        return DDERR_INVALIDPARAMS;
    }

    /*
     * Get and evaluate the EDID data
     */
    hr = GetEDIDData( this, &EDIDData );
    if( hr != DD_OK )
    {
        // There is no EDID data
        LEAVE_DDRAW();
        return DDERR_NOMONITORINFORMATION;
    }
    EvaluateMonitor( &EDIDData, &dwHighestRefresh, &dwHighestBandwidth );

    for( i = 0; i < this->dwNumModes; i++ )
    {
        if( this->lpModeInfo[i].wRefreshRate > 0 )
        {
            break;
        }
    }
    if( i == this->dwNumModes )
    {
        // The driver does not enumerate display mode refresh rates.
        LEAVE_DDRAW();
        return DDERR_NODRIVERSUPPORT;
    }
    
    /*
     * Allocate a context for ourselves
     */
    pContext = (LPMODETESTCONTEXT) MemAlloc( sizeof( MODETESTCONTEXT ) );
    if( pContext == NULL )
    {
        DPF_ERR( "Insufficient memory" );
        LEAVE_DDRAW();
        return DDERR_OUTOFMEMORY;
    }
    pContext->dwNumModes = 0;
    pContext->dwCurrentMode = 0;
    pContext->lpModeList = (LPMODETESTDATA) MemAlloc( sizeof( MODETESTDATA ) * dwNumEntries );
    if( pContext->lpModeList == NULL )
    {
        MemFree( pContext );
        LEAVE_DDRAW();
        return DDERR_OUTOFMEMORY;
    }
    this_lcl->lpModeTestContext = pContext;
    
    /*
     * Guestimate which refresh rates we should try for each mode in the list
     * based on the EDID data
     */
    for( i = 0; i < dwNumEntries; i++ )
    {
        DWORD dwLowestBPP = 0xFFFFFFFF;
        /*
         * Verify that the driver understands the resolution
         */
        for( j = 0; j < this->dwNumModes; j++ )
        {
            if( ( this->lpModeInfo[j].dwHeight == (DWORD) lpModesToTest[i].cy ) &&
                ( this->lpModeInfo[j].dwWidth == (DWORD) lpModesToTest[i].cx ) )
            {
                if( this->lpModeInfo[j].dwBPP < dwLowestBPP )
                {
                    dwLowestBPP = this->lpModeInfo[j].dwBPP;
                }
            }
        }
        if( dwLowestBPP == 0xFFFFFFFF )
        {
            /*
             * The driver doesn't undestand this mode, so the app is dumb 
             * for not enumerating the modes first.
             */
            MemFree( pContext->lpModeList );
            MemFree( pContext );
            this_lcl->lpModeTestContext = NULL;
            DPF_ERR( "Invalid mode specified in mode list" );
            LEAVE_DDRAW();
            return DDERR_INVALIDPARAMS;
        }

        /*
         * Get the actual mode to test.  For example, the app may want
         * to test 320x240, 640x400, and 640x480, but we treat all of these
         * modes the same so we only have to do a single test.
         */
        hr = GetModeToTest( lpModesToTest[i].cx, 
            lpModesToTest[i].cy,
            &dwModeWidth,
            &dwModeHeight );
        if( hr != DD_OK )
        {
            // They are testing a mode higher the 1600x1200
            continue;
        }
        for( j = 0; j < pContext->dwNumModes; j++ )
        {
            if( ( pContext->lpModeList[j].dwWidth == dwModeWidth ) &&
                ( pContext->lpModeList[j].dwHeight == dwModeHeight ) )
            {
                break;
            }
        }
        if( j < pContext->dwNumModes )
        {
            // Duplicate mode
            continue;
        }

        if( !IsModeTested( this, dwModeWidth, dwModeHeight ) )
        {
            dwRefreshRate = GuestimateRefreshRate( this, dwModeWidth, dwModeHeight, 
                dwHighestRefresh, dwHighestBandwidth );
        
            pContext->lpModeList[pContext->dwNumModes].dwWidth = dwModeWidth;
            pContext->lpModeList[pContext->dwNumModes].dwHeight = dwModeHeight;
            pContext->lpModeList[pContext->dwNumModes].dwBPP = dwLowestBPP;
            pContext->lpModeList[pContext->dwNumModes].dwRefreshRate = dwRefreshRate;
            pContext->dwNumModes++;
        }
    }

    /*
     * After all of that, do we still have any modes that need testing?  
     * If not, we can stop now
     */
    if( dwFlags & DDSMT_ISTESTREQUIRED )
    {
        hr = ( pContext->dwNumModes > 0 ) ? DDERR_NEWMODE : DDERR_TESTFINISHED;
        MemFree( pContext->lpModeList );
        MemFree( pContext );
        this_lcl->lpModeTestContext = NULL;
    }
    else
    {
        pContext->dwOrigModeIndex = this->dwModeIndex;
        hr = RunNextTest( lpDD, pContext );
    }

    LEAVE_DDRAW();
    return hr;
#endif
} 


/*
 * DD_EvaluateMode
 *
 * Called at high frequency while the mode test is being performed.  If the user has indicated
 * that a mode succeeded or failed, we move on to the next moe in the test; otherwise, we will 
 * simply check the 15 second timeout value and fail the mode when we hit it..
 */
HRESULT DDAPI DD_EvaluateMode( LPDIRECTDRAW7 lpDD, DWORD dwFlags, DWORD *pSecondsUntilTimeout)
{
#ifdef WIN95
    LPDDRAWI_DIRECTDRAW_INT     this_int;
    LPDDRAWI_DIRECTDRAW_LCL     this_lcl;
    LPDDRAWI_DIRECTDRAW_GBL     this;
    BOOL                        excl_exists;
    BOOL                        is_excl;
    LPMODETESTCONTEXT           pContext;
    DWORD                       i;
    DWORD                       j;
    HRESULT                     hr = DD_OK;
    DWORD                       dwTick;

    ENTER_DDRAW();
#endif

    DPF(2,A,"ENTERAPI: DD_EvaluateMode");

#ifdef WINNT

    return DDERR_INVALIDPARAMS;

#else
    TRY
    {
        this_int = (LPDDRAWI_DIRECTDRAW_INT) lpDD;
        if( !VALID_DIRECTDRAW_PTR( this_int ) )
        {
            LEAVE_DDRAW();
            return DDERR_INVALIDOBJECT;
        }
        this_lcl = this_int->lpLcl;
        this = this_lcl->lpGbl;

        if( this->lpMonitorInfo == NULL )
        {
            // There is no monitor info so we should not be here
            LEAVE_DDRAW();
            return DDERR_NOMONITORINFORMATION;
        }
        
    	pContext = this_lcl->lpModeTestContext;
        if( NULL == pContext )
    	{
            DPF_ERR( "Must call StartModeTest before EvaulateMode" );
            LEAVE_DDRAW();
            return DDERR_INVALIDPARAMS;
    	}

        if( ( NULL != pSecondsUntilTimeout ) && 
            !VALID_BYTE_ARRAY( pSecondsUntilTimeout, sizeof( DWORD ) ) )
    	{
            DPF_ERR( "Invalid pointer to timeout counter" );
            LEAVE_DDRAW();
            return DDERR_INVALIDPARAMS;
    	}

        if( dwFlags & ~DDEM_VALID )
        {
            DPF_ERR( "Invalid Flags specified" );
            LEAVE_DDRAW();
            return DDERR_INVALIDPARAMS;
        }
        if( ( dwFlags & DDEM_MODEPASSED ) &&
            ( dwFlags & DDEM_MODEFAILED ) )
        {
            DPF_ERR( "Invalid Flags specified" );
            LEAVE_DDRAW();
            return DDERR_INVALIDPARAMS;
        }

        /*
         * If we lost exclusive mode, we should stop the test now
         */
        CheckExclusiveMode( this_lcl, &excl_exists, &is_excl, FALSE, NULL, FALSE);
        if (!is_excl ||  !(this->dwFlags & DDRAWI_FULLSCREEN) )
        {
            DPF_ERR( "Exclusive mode lost" );
            MemFree( pContext->lpModeList );
            MemFree( pContext );
            this_lcl->lpModeTestContext = NULL;
            LEAVE_DDRAW();
            return DDERR_TESTFINISHED;
        }
    }
    EXCEPT( EXCEPTION_EXECUTE_HANDLER )
    {
        DPF_ERR( "Exception encountered validating parameters" );
        LEAVE_DDRAW();
        return DDERR_INVALIDPARAMS;
    }

    if( dwFlags & DDEM_MODEPASSED )
    {
        // The current data is good, so save it

        UpdateMonitorInfo( this, 
            pContext->lpModeList[pContext->dwCurrentMode].dwWidth,
            pContext->lpModeList[pContext->dwCurrentMode].dwHeight,
            pContext->lpModeList[pContext->dwCurrentMode].dwRefreshRate );
            
        // Move on to the next test, if there is one
        
        pContext->dwCurrentMode++;
        hr = RunNextTest( lpDD, pContext );
        if( hr == DD_OK )
        {
            hr = DDERR_NEWMODE;
        }
    }
    else
    {
        // Has our timeout expired?

        dwTick = GetTickCount();
        if( dwTick - pContext->dwTimeStamp > 15000 )
        {
            dwFlags |= DDEM_MODEFAILED;
        }

        if( dwFlags & DDEM_MODEFAILED )
        {
            // Drop down to the next refresh rate or the next mode

            SetupNextTest( lpDD, pContext );
            hr = RunNextTest( lpDD, pContext );
            if( hr == DD_OK )
            {
                hr = DDERR_NEWMODE;
            }

            dwTick = GetTickCount();
        }
    }

    if( pSecondsUntilTimeout != NULL )
    {
        if( hr == DDERR_TESTFINISHED )
        {
            *pSecondsUntilTimeout = 0;
        }
        else
        {
            *pSecondsUntilTimeout = 15 - ( ( dwTick - pContext->dwTimeStamp) / 1000 );
        }
    }

    LEAVE_DDRAW();        
    return hr;
#endif
}


//
// This function is designed to allow DX7 apps to see some modes that would otherwise be
// incorrectly masked by the mode enumeration thing.
//
// If a driver exposes a list of modes with full-on refresh rates in EVERY entry in the mode table,
// then we will enum exactly NONE of them to the app, since any rate with a rate cannot be enumerated
// until the mode test has run. Apps that don't run the mode test will see NO modes at all.
//
// The s3 savage 4 is a case in point: it fills in the refresh rate for the current display mode,
// (and no other mode) and doesn't dupe the entry to one with a zero refresh rate.
//
// What we do is: every time we find an instance of a mode (size, bitdepth, masks) that has no 
// zero-rate entry, we append a zero-rate entry to the end of the mode list.
//

//No need to massage on winNT
#ifdef WIN95
void MassageModeTable(LPDDRAWI_DIRECTDRAW_GBL pddd)
{
    DWORD iMode, iCheckZero;
    if( pddd->lpModeInfo != NULL )
    {
RestartLoop:
        for( iMode = 0; iMode < pddd->dwNumModes;  iMode++ )
        {
            if (pddd->lpModeInfo[iMode].wRefreshRate != 0)
            {
                // Found a mode with non-zero rate. Check to see if the mode is also represented
                // by a zero-rate entry. If it is not, then append such an entry
                for( iCheckZero = 0; iCheckZero < pddd->dwNumModes;  iCheckZero++ )
                {
                    if( (pddd->lpModeInfo[iCheckZero].dwWidth    == pddd->lpModeInfo[iMode].dwWidth) &&
                        (pddd->lpModeInfo[iCheckZero].dwHeight   == pddd->lpModeInfo[iMode].dwHeight) &&
                        (pddd->lpModeInfo[iCheckZero].dwBPP      == pddd->lpModeInfo[iMode].dwBPP) &&
                        (pddd->lpModeInfo[iCheckZero].dwRBitMask == pddd->lpModeInfo[iMode].dwRBitMask) &&
                        (pddd->lpModeInfo[iCheckZero].dwGBitMask == pddd->lpModeInfo[iMode].dwGBitMask) &&
                        (pddd->lpModeInfo[iCheckZero].dwBBitMask == pddd->lpModeInfo[iMode].dwBBitMask))
                    {
                        // found a matching mode, in terms of size and depth.
                        // If the refresh rate is zero, then we can break out and go on to the next iMode
                        if (pddd->lpModeInfo[iCheckZero].wRefreshRate == 0)
                        {
                            goto NextMode;
                        }
                    }
                }
                // If we got here, then there was no entry in the mode list for this size+depth
                // that had a zero refresh rate. Append one now.
                // Note how expanding the mode list like this means that if the driver (as it typically
                // will) offers several rates for a given mode, we'll expand the table on the first
                // hit of that mode, but then the expanded table will satisfy us for every subsequent
                // rate of that mode (i.e. now there WILL be a zero-rated entry for that mode (since
                // we just added it)).
                {
                    LPDDHALMODEINFO pmi;

                    pmi = (LPDDHALMODEINFO) MemAlloc(sizeof(*pmi) * (pddd->dwNumModes+1));
                    if (pmi == NULL)
                    {
                        //oh just give up....
                        return;
                    }

                    memcpy(pmi, pddd->lpModeInfo, sizeof(*pmi)*pddd->dwNumModes );
                    MemFree(pddd->lpModeInfo);
                    pddd->lpModeInfo = pmi;

                    // Now put the zero-rated mode in there
                    memcpy( &pddd->lpModeInfo[pddd->dwNumModes], &pddd->lpModeInfo[iMode], sizeof(*pmi));
                    pddd->lpModeInfo[pddd->dwNumModes].wRefreshRate = 0;
                    pddd->lpModeInfo[pddd->dwNumModes].wFlags |= DDMODEINFO_DX7ONLY;

                    pddd->dwNumModes++;

                    //Now we have to restart the whole loop because we changed the lpModeInfo pointer:
                    goto RestartLoop;
                }
            }
NextMode:;
        }
    }
}
#endif //WIN95