/*****************************************************************************
 *
 *  DIDEnum.c
 *
 *  Copyright (c) 1996 Microsoft Corporation.  All Rights Reserved.
 *
 *  Abstract:
 *
 *      The IDirectInput device enumerator.
 *
 *      We don't bother making this an honest OLE enumerator because
 *      there's no point.  There's no way to access it from the outside.
 *
 *  Contents:
 *
 *      CDIDEnum_New
 *      CDIDEnum_Next
 *      CDIDEnum_Release
 *
 *****************************************************************************/

#include "dinputpr.h"

/*****************************************************************************
 *
 *      The sqiffle for this file.
 *
 *****************************************************************************/

#define sqfl sqflDEnum


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @struct _CDIDEnum |
 *
 *          Records the state of a device enumeration.  Note that this
 *          is not free-threaded.
 *
 *  @field  PDIW | pdiW |
 *
 *          The <i IDirectInputW> object that owns the enumeration.
 *
 *  @field  DWORD | dwDevType |
 *
 *          Device type filter.
 *
 *  @field  DWORD | edfl |
 *
 *          Enumeration flags.
 *
 *  @field  int | idosdStatic |
 *
 *          The next static device to enumerate.  Static devices live
 *          in <c c_rgdosdStatic>.
 *
 *  @field  DWORD | dwVer |
 *
 *          Version of DirectX we are emulating.
 *
 *          If we are emulating DirectX 3.0 or less, then don't
 *          reveal joysticks.
 *
 *  @field  int | idosdDynamic |
 *
 *          The next dynamic device to enumerate.  Dyanmic devices
 *          are kept in the <e CDIDEnum.rgdosdDynamic> array.  They
 *          are snapshotted into the enumeration structure to avoid
 *          race conditions if a device comes or goes while we are
 *          in the middle of an enumeration.
 *
 *  @field  PHIDDEVICELIST | phdl |
 *
 *          List of HID devices to be returned by the enumeration.
 *
 *****************************************************************************/

    typedef struct _CDIDEnum
    {
    
        D(DWORD dwSig;)
        PDIW pdiW;
        DWORD dwDevType;
        DWORD edfl;
        int idosdStatic;
        DWORD dwVer;
        int idosdDynamic;
        PHIDDEVICELIST phdl;
        PDIDW pdidW;
    } DENUM, *PDENUM, **PPDENUM;

    #define CDIDENUM_SIGNATURE  0x4D554E45          /* "ENUM" */

    #define AssertPde(pde)          AssertF((pde)->dwSig == CDIDENUM_SIGNATURE)

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @global DIOBJECTSTATICDATA | c_rgdosdStatic[] |
 *
 *          Right now, the list of device is static and hard-coded.
 *          Eventually, we'll
 *          use plug and play to enumerate devices of class "input" and
 *          get information from their config/software keys.
 *
 *****************************************************************************/

    #pragma BEGIN_CONST_DATA

/*
 *  Our array of static joystick instance guids.
 *
 */
GUID rgGUID_Joystick[cJoyMax] = {
    {   0x6F1D2B70,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B71,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B72,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B73,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B74,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B75,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B76,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B77,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B78,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B79,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B7A,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B7B,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B7C,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B7D,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B7E,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
    {   0x6F1D2B7F,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00},
};

    #if cJoyMax != 16
        #error rgGUID_Joystick supports only 16 joysticks.
    #endif

/*
 *  Note that we recycle the SysMouse GUID as the instance GUID too,
 *  since there will never be more than one system mouse installed in
 *  the system.  Similarly for SysKeyboard.
 */

DIOBJECTSTATICDATA c_rgdosdStatic[] = {
    {   &GUID_SysMouse,     DI8DEVTYPE_MOUSE,    CMouse_New,},
    {   &GUID_SysMouseEm,   DI8DEVTYPE_MOUSE,    CMouse_New,},
    {   &GUID_SysMouseEm2,  DI8DEVTYPE_MOUSE,    CMouse_New,},
    {   &GUID_SysKeyboard,  DI8DEVTYPE_KEYBOARD, CKbd_New,},
    {   &GUID_SysKeyboardEm,   DI8DEVTYPE_KEYBOARD, CKbd_New,},
    {   &GUID_SysKeyboardEm2,  DI8DEVTYPE_KEYBOARD, CKbd_New,},

    #ifndef WINNT
    /*
     * On WINNT all joysticks are HID devices.
     * So it is pointless to include predefined
     * Joystick GUIDs
     */
        #define MAKEJOY(n)                                                  \
    {   &rgGUID_Joystick[n],DI8DEVCLASS_GAMECTRL, CJoy_New,           }
        MAKEJOY( 0),
    MAKEJOY( 1),
    MAKEJOY( 2),
    MAKEJOY( 3),
    MAKEJOY( 4),
    MAKEJOY( 5),
    MAKEJOY( 6),
    MAKEJOY( 7),
    MAKEJOY( 8),
    MAKEJOY( 9),
    MAKEJOY(10),
    MAKEJOY(11),
    MAKEJOY(12),
    MAKEJOY(13),
    MAKEJOY(14),
    MAKEJOY(15),

        #undef MAKEJOY
    #endif

};

    #pragma END_CONST_DATA

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | hresFindInstanceGUID |
 *
 *          Locates information given an instance GUID.
 *
 *  @parm   IN PCGUID | pguid |
 *
 *          The instance GUID to be located.
 *
 *  @parm   OUT CREATEDCB * | pcdcb |
 *
 *          Receives pointer to the <f CreateDcb> function for the object.
 *
 *****************************************************************************/

HRESULT EXTERNAL
    hresFindInstanceGUID_(PCGUID pguid, CREATEDCB *pcdcb,
                          LPCSTR s_szProc, int iarg)
{
    HRESULT hres;
    EnterProcS(hresFindInstance, (_ "G", pguid));

    if(SUCCEEDED(hres = hresFullValidGuid(pguid, iarg)))
    {
        int idosd;

        /*
         *  First try the list of static devices.  Since this
         *  list never changes, we don't need to protect it
         *  with a critical section.
         */
        for(idosd = 0; idosd < cA(c_rgdosdStatic); idosd++)
        {
            if(IsEqualGUID(pguid, c_rgdosdStatic[idosd].rguidInstance))
            {
                *pcdcb = c_rgdosdStatic[idosd].CreateDcb;
                goto done;
            }
        }

        /*
         *  So it wasn't one of the static devices.  See if it's
         *  one of the dynamic HID devices we've already found.
         */
        hres = hresFindHIDInstanceGUID(pguid, pcdcb);
        if(FAILED(hres))
        {

            /*
             *  Not on our list of dynamic HID devices.
             *  Re-enumerate them and try again.  Maybe it was
             *  for a device that we recently added.
             */
            DIHid_BuildHidList(TRUE);
            hres = hresFindHIDInstanceGUID(pguid, pcdcb);
        }

        if(FAILED(hres))
        {
        #ifdef WINNT
            /*
             *  NT Bug#351951.
             *  If they are directly asking for one of the predefined joystick 
             *  IDs then see if we have a device mapped to that ID.  If so,
             *  pretend they asked for that GUID instead.
             */

            /*
             *  Weakly Assert the range of predefined static joystick instance GUIDs
             */
            AssertF( ( rgGUID_Joystick[0].Data1 & 0x0f ) == 0 );
            AssertF( ( rgGUID_Joystick[0x0f].Data1 & 0x0f ) == 0x0f );

            /*
             *  Check the GUID is the same as the first static one ignoring LS 4 bits
             */
            if( ( (pguid->Data1 & 0xf0) == (rgGUID_Joystick[0].Data1 & 0xf0) )
              && !memcmp( ((PBYTE)&rgGUID_Joystick)+1, ((PBYTE)pguid)+1, sizeof(*pguid) - 1 ) )
            {
                RPF("%s: Using predefined instance GUIDs is bad and should not work!", s_szProc);
                if( phdiFindJoyId( pguid->Data1 & 0x0f ) )
                {
                    *pcdcb = CHid_New;
                    hres = S_OK;
                }
                else
                {
                    *pcdcb = 0;
                    hres = DIERR_DEVICENOTREG;
                }
            }
            else
            {
                RPF("%s: Warning: GUID is not installed in this system", s_szProc);
                *pcdcb = 0;
                hres = DIERR_DEVICENOTREG;
            }
        #else
            RPF("%s: Warning: GUID is not installed in this system", s_szProc);
            *pcdcb = 0;
            hres = DIERR_DEVICENOTREG;
        #endif
        }
    }

    done:;
    ExitOleProcPpv(pcdcb);
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   void | CDIDEnum_Release |
 *
 *          Free the enumeration object and its associated resources.
 *
 *  @parm   CDIDEnum * | pde |
 *
 *          The enumeration state to be released.
 *
 *****************************************************************************/

void EXTERNAL
    CDIDEnum_Release(PDENUM pde)
{
    EnterProcI(CDIDEnum_Release, (_ "p", pde));

    AssertPde(pde);

    /*
     *  First, release any last enumerated device
     */
    if( pde->pdidW )
    {
        OLE_Release(pde->pdidW);
    }
    OLE_Release(pde->pdiW);
    FreePpv(&pde->phdl);
    FreePv(pde);

}



    #define S_SKIP      hresUs(2)

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | hresIsDeviceTypeMatch |
 *
 *          Test if a device type matches the filter.
 *
 *  @parm   BYTE | bFilter |
 *
 *          The device type or class to filter by.
 *
 *  @parm   BYTE | bDevType |
 *
 *          The device type to be tested.
 *
 *  @returns
 *
 *          Returns <c S_OK> if the device matches the filter 
 *          or <c S_SKIP> if it does not.
 *
 *****************************************************************************/

HRESULT hresIsDeviceTypeMatch
( 
    BYTE bFilter,
    BYTE bDevType
)
{
    HRESULT hres;

    AssertF( bDevType >= DI8DEVTYPE_MIN );
    AssertF( bDevType < DI8DEVTYPE_MAX );

    if( ( bFilter == 0 )
     || ( bFilter == bDevType ) )
    
    {
        hres = S_OK;
    }
    else 
    {
        hres = S_SKIP;
        switch( bFilter )
        {
        case DI8DEVCLASS_DEVICE:
            if( bDevType == DI8DEVTYPE_DEVICE )
            {
                hres = S_OK;
            }
            break;
        case DI8DEVCLASS_POINTER:
            if( ( bDevType == DI8DEVTYPE_MOUSE )
             || ( bDevType == DI8DEVTYPE_SCREENPOINTER ) )
            {
                hres = S_OK;
            }
            break;
        case DI8DEVCLASS_KEYBOARD:
            if( bDevType == DI8DEVTYPE_KEYBOARD )
            {
                hres = S_OK;
            }
            break;
        case DI8DEVCLASS_GAMECTRL:
            if( (( bDevType >= DI8DEVTYPE_GAMEMIN )
              && ( bDevType < DI8DEVTYPE_GAMEMAX )) ||
                ( bDevType == DI8DEVTYPE_SCREENPOINTER ) ||  /* Windows bug 385284 (jacklin) - DI8DEVCLASS_GAMECTRL should   */
                ( bDevType == DI8DEVTYPE_SUPPLEMENTAL ) )    /* include DI8DEVTYPE_SCREENPOINTER and DI8DEVTYPE_SUPPLEMENTAL */
            {
                hres = S_OK;
            }
            break;
        }
    }

    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | CDIDEnum_Next |
 *
 *          Return the next device.
 *
 *          Note that this is not the same as the OLE <mf IEnumXxx::Next>
 *          function.  Not that it'd be hard to convert over; it's just
 *          not needed yet.
 *
 *  @parm   CDIDEnum * | pde |
 *
 *          Maintains enumeration state.
 *
 *  @parm   LPGUID | pguid |
 *
 *          Receives the enumerated GUID.
 *
 *  @parm   LPDIDEVICEINSTANCEW | pddiW |
 *
 *          Receives device attributes.
 *
 *  @returns
 *
 *          Returns <c S_OK> if the object was successfully obtained,
 *          or <c S_FALSE> if there aren't any more objects.
 *
 *          Warning! <f CDIObj_EnumDevicesW> assumes that this function
 *          cannot fail.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDEnum_Next(PDENUM pde, LPDIDEVICEINSTANCEW pddiW)
{
    HRESULT hres;
    EnterProcI(CDIDEnum_Next, (_ "p", pde));

    AssertPde(pde);

    AssertF(pddiW->dwSize == cbX(*pddiW));


    /*
     *  Keep going until something works.
     */
    do
    {
        PDIOBJECTSTATICDATA pdosd;

        /*
         *  Release any previously enumerated or looked at device
         */
        if( pde->pdidW )
        {
            OLE_Release(pde->pdidW);
            pde->pdidW = NULL;
        }

        /*
         *  Pull one from the static list first.
         *  If that is empty, then pull from the dynamic list.
         *  If that is empty, then we're done.
         */
        if(pde->idosdStatic < cA(c_rgdosdStatic))
        {
            pdosd = &c_rgdosdStatic[pde->idosdStatic++];
        } else if(pde->phdl && pde->idosdDynamic < pde->phdl->chdi)
        {
            pdosd = &pde->phdl->rghdi[pde->idosdDynamic].osd;
            pdosd->rguidInstance = &pde->phdl->rghdi[pde->idosdDynamic].guid;
            pde->idosdDynamic++;
        } else
        {
            hres = S_FALSE;
            goto done;
        }

        /*
         *  ISSUE-2001/03/03-MarcAnd Filtered device enumerations are slow
         *  Since DI8DEVTYPEs can be generated on the fly and can be 
         *  overridden we can no longer filter before creating the device.
         *  This is badness.  Ideally, we need to add the WinMM and system 
         *  devices to our dynamic device list, tidy that info up and add 
         *  all the info we need to that list.
         */
        if( 1 ) 
        {

            hres = IDirectInput_CreateDevice(pde->pdiW, pdosd->rguidInstance,
                                             (PV)&pde->pdidW, 0);
            if(SUCCEEDED(hres))
            {
                if(CDIObj_TestDeviceFlags(pde->pdidW, pde->edfl) == S_OK)
                {
                    pddiW->dwSize = cbX(*pddiW);
                    hres = IDirectInputDevice_GetDeviceInfo(pde->pdidW, pddiW);

                    if( SUCCEEDED( hres ) )
                    {
                        AssertF( IsEqualGUID(pdosd->rguidInstance, &pddiW->guidInstance) );

                        hres = hresIsDeviceTypeMatch( GET_DIDEVICE_TYPE(pde->dwDevType), GET_DIDEVICE_TYPE(pddiW->dwDevType) );
                    }

                } else
                {
                    hres = S_SKIP;
                }
            } else
            {
                hres = S_SKIP;
            }
        } else
        {
            hres = S_SKIP;
        }
    } while(hres == S_SKIP);

    done:;

    AssertF(hres == S_OK || hres == S_FALSE);

    ScrambleBit(&pddiW->dwDevType, DIDEVTYPE_RANDOM);

    return hres;
}


STDMETHODIMP
    CDIDEnum_InternalNext(PDENUM pde, LPDIDEVICEINSTANCEW pddiW, LPDIRECTINPUTDEVICE8W *ppdid8W)
{
    HRESULT hres;
    EnterProcI(CDIDEnum_Next, (_ "p", pde));

    AssertPde(pde);

    AssertF(pddiW->dwSize == cbX(*pddiW));


    /*
     *  Keep going until something works.
     */
    do
    {
        PDIOBJECTSTATICDATA pdosd;

        /*
         *  Release any previously enumerated or looked at device
         */
        if( pde->pdidW )
        {
            OLE_Release(pde->pdidW);
            pde->pdidW = NULL;
        }

        /*
         *  Pull one from the static list first.
         *  If that is empty, then pull from the dynamic list.
         *  If that is empty, then we're done.
         */
        if(pde->idosdStatic < cA(c_rgdosdStatic))
        {
            pdosd = &c_rgdosdStatic[pde->idosdStatic++];
        } else if(pde->phdl && pde->idosdDynamic < pde->phdl->chdi)
        {
            pdosd = &pde->phdl->rghdi[pde->idosdDynamic].osd;
            pdosd->rguidInstance = &pde->phdl->rghdi[pde->idosdDynamic].guid;
            pde->idosdDynamic++;
        } else
        {
            hres = S_FALSE;
            goto done;
        }

        hres = IDirectInput_CreateDevice(pde->pdiW, pdosd->rguidInstance,
                                         (PV)&pde->pdidW, 0);
        if(SUCCEEDED(hres))
        {
            if(CDIObj_TestDeviceFlags(pde->pdidW, pde->edfl) == S_OK)
            {

                pddiW->dwSize = cbX(*pddiW);
                hres = IDirectInputDevice_GetDeviceInfo(pde->pdidW, pddiW);
                *ppdid8W = (LPDIRECTINPUTDEVICE8W)pde->pdidW;

                AssertF(fLimpFF(SUCCEEDED(hres),
                                IsEqualGUID(pdosd->rguidInstance,
                                            &pddiW->guidInstance)));

                /*
                 *  Do filtering here (see ISSUE in CDIDEnum_Next for why)
                 */
                hres = hresIsDeviceTypeMatch( GET_DIDEVICE_TYPE(pde->dwDevType), GET_DIDEVICE_TYPE(pddiW->dwDevType) );

            } else
            {
                hres = S_SKIP;
            }
        } else
        {
            hres = S_SKIP;
        }
    } while(hres == S_SKIP);

    done:;

    AssertF(hres == S_OK || hres == S_FALSE);

    ScrambleBit(&pddiW->dwDevType, DIDEVTYPE_RANDOM);

    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | CDIDEnum_New |
 *
 *          Create an enumeration object.
 *
 *          The enumeration object snapshots the system device state
 *          and farms them out one at a time.
 *
 *  @parm   PDIW | pdiW |
 *
 *          Parent <i IDirectInputW> we piggyback off of for device
 *          creation.
 *
 *  @field  DWORD | dwDevType |
 *
 *          Device type filter.
 *
 *  @field  DWORD | edfl |
 *
 *          Enumeration flags.
 *
 *  @field  DWORD | dwVer |
 *
 *          Version of DirectX we are emulating.
 *
 *          This should always be DirectX 8.0.
 *
 *  @parm   CDIDEnum ** | ppde |
 *
 *          Receives the enumeration object.
 *
 *  @returns
 *
 *          Returns <c S_OK> on success or an error code on failure.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDEnum_New(PDIW pdiW, DWORD dwDevType, DWORD edfl, DWORD dwVer, PPDENUM ppde)
{
    HRESULT hres;
    EnterProcI(CDIDEnum_New, (_ "pxx", pdiW, dwDevType, edfl));

    /*
     *  Refresh the HID device list so the enumeration is fresh.
     */
    DIHid_BuildHidList(TRUE);

    hres = AllocCbPpv(cbX(CDIDEnum), ppde);
    if(SUCCEEDED(hres))
    {
        PDENUM pde = *ppde;

        D(pde->dwSig = CDIDENUM_SIGNATURE);
        pde->pdiW = pdiW;
        pde->dwDevType = dwDevType;
        pde->edfl = edfl;
        pde->dwVer = dwVer;
        /*
         *  Make sure last enumerated device pointer is cleared
         */
        pde->pdidW = NULL;
        AssertF(pde->idosdStatic == 0);

        /*
         *  If enumerating only HID devices, then skip over all
         *  the static (non-HID) devices.  This is important so
         *  we don't go into infinite recursion death with WINMM.DLL,
         *  which does an enumeration to find HID joysticks
         *  in the first place.
         */
        if(pde->dwDevType & DIDEVTYPE_HID)
        {
            pde->idosdStatic = cA(c_rgdosdStatic);
        }

        AssertF(pde->idosdDynamic == 0);

        /*
         *  Clone the device list.  This must be done under the
         *  critical section to avoid races.
         */
        DllEnterCrit();
        if(g_phdl)
        {
            hres = AllocCbPpv(cbHdlChdi(g_phdl->chdi), &pde->phdl);
            if(SUCCEEDED(hres))
            {
                CopyMemory(pde->phdl, g_phdl, cbHdlChdi(g_phdl->chdi));
                SquirtSqflPtszV(sqfl, TEXT("%S: Have %d HID devices"),
                                s_szProc, pde->phdl->chdi);
                hres = S_OK;
            }
        } else
        {
            hres = S_OK;
        }
        DllLeaveCrit();

        if(SUCCEEDED(hres))
        {
            OLE_AddRef(pde->pdiW);
            hres = S_OK;
        }

    }

    ExitOleProcPpv(ppde);
    return hres;

}