/*****************************************************************************
 *
 *      diqdevg.c
 *
 *      Acquire an IDirectInputDevice as a generic device.
 *
 *      We build a private data format with all the axes first, then
 *      all the buttons.
 *
 *      We display the axes parenthesized, followed by a space-separated
 *      list of buttons.
 *
 *****************************************************************************/

#include "diquick.h"

/*****************************************************************************
 *
 *      DEVGINFO
 *
 *      Structure used to track generic device state.
 *
 *****************************************************************************/

typedef struct DEVGINFO {
    DWORD cbState;                  /* Size of state buffer */
    LPVOID pvState;                 /* State buffer */
    int cAxes;                      /* Number of axes (start at offset 0) */
    int cButtons;                   /* Number of buttons (start after axes) */
} DEVGINFO, *PDEVGINFO;

/*****************************************************************************
 *
 *      Devg_UpdateStatus
 *
 *      Warning!  ptszBuf is only 256 characters long!
 *
 *****************************************************************************/

STDMETHODIMP
Devg_UpdateStatus(PDEVDLGINFO pddi, LPTSTR ptszBuf)
{
    HRESULT hres;
    PDEVGINFO pdevg = pddi->pvAcq;
    IDirectInputDevice *pdev = pddi->pdid;

    hres = IDirectInputDevice_GetDeviceState(pdev, pdevg->cbState,
                                                   pdevg->pvState);
    if (SUCCEEDED(hres)) {
        LPVOID pvData = pdevg->pvState;
        int iButton;

        if (pdevg->cAxes) {
            int iAxis;

            for (iAxis = 0; iAxis < pdevg->cAxes; iAxis++) {
                LPLONG pl = pvData;
                pvData = pvAddPvCb(pvData, sizeof(LONG));
                ptszBuf += wsprintf(ptszBuf, iAxis == 0 ? TEXT("\050%d") :
                                                          TEXT(", %d"), *pl);
            }
            ptszBuf += wsprintf(ptszBuf, TEXT("\051"));
        }

        for (iButton = 0; iButton < pdevg->cButtons; iButton++) {
            LPBYTE pb = pvData;
            pvData = pvAddPvCb(pvData, sizeof(BYTE));
            if (*pb & 0x80) {
                ptszBuf += wsprintf(ptszBuf, TEXT(" %d"), iButton);
            }
        }
    }
    *ptszBuf = TEXT('\0');

    return hres;
}

/*****************************************************************************
 *
 *      Devg_Destroy
 *
 *****************************************************************************/

STDMETHODIMP_(void)
Devg_Destroy(PDEVDLGINFO pddi)
{
    PDEVGINFO pdevg = pddi->pvAcq;

    if (pdevg) {
        if (pdevg->pvState) {
            LocalFree(pdevg->pvState);
        }
       LocalFree(pdevg);
    }
}

/*****************************************************************************
 *
 *      Devg_DataFormatEnumProc
 *
 *      Callback function for each object.  If it's a button or axis,
 *      we put it in the data format.  But only if it generates data!
 *
 *****************************************************************************/

typedef struct DEVGENUM {
    DWORD dwNumObjs;
    DIDATAFORMAT df;
    PDEVGINFO pdevg;
    PDEVDLGINFO pddi;
} DEVGENUM, *PDEVGENUM;

BOOL CALLBACK
Devg_DataFormatEnumProc(LPCDIDEVICEOBJECTINSTANCE pinst, LPVOID pv)
{
    PDEVGENUM pge = pv;
    LPDIOBJECTDATAFORMAT podf;
    DIDEVICEOBJECTINSTANCE doi;
    DWORD cbObj;


    if( pge->df.dwNumObjs == pge->dwNumObjs -1 )
    {
        HLOCAL hloc;
        pge->dwNumObjs *=2;
        hloc =  LocalReAlloc((HLOCAL)pge->df.rgodf, pge->dwNumObjs * pge->df.dwObjSize , LMEM_MOVEABLE+LMEM_ZEROINIT);
        
        if(hloc)
        {
            pge->df.rgodf = hloc;
        }else
        {
            goto done;
        }
    }

    ConvertDoi(pge->pddi, &doi, pinst);

    /*
     *  Ignore no-data elements.
     */
    if (doi.dwType & DIDFT_NODATA) {
    } else {

        if (doi.dwType & (DIDFT_AXIS | DIDFT_POV)) {
            pge->pdevg->cAxes++;
            cbObj = cbX(LONG);
        } else if (doi.dwType & DIDFT_BUTTON) {
            pge->pdevg->cButtons++;
            cbObj = cbX(BYTE);
        } else {
            /*
             *  Theoretical impossibility!
             */
            goto done;
        }

        podf = &pge->df.rgodf[pge->df.dwNumObjs];

        podf->dwOfs = pge->df.dwDataSize;
#ifdef USE_HID_USAGE_DATA_FORMATS
        if (doi.wUsagePage) {
                podf->dwType = (doi.dwType &
                                (DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV)) |
                               DIDFT_ANYINSTANCE;
                podf->pguid = (PV)DIMAKEUSAGEDWORD(doi.wUsagePage, doi.wUsage);
                podf->dwFlags |= DIDOI_GUIDISUSAGE;
        } else {
                podf->dwType = doi.dwType;
        }
#else
        podf->dwType = doi.dwType;
#endif

        pge->df.dwDataSize += cbObj;
        pge->df.dwNumObjs++;            
    }

done:;
    return DIENUM_CONTINUE;
}

/*****************************************************************************
 *
 *      Devg_SetDataFormat
 *
 *      Build a custom data format for the device.
 *
 *****************************************************************************/

STDMETHODIMP
Devg_SetDataFormat(PDEVDLGINFO pddi)
{
    HRESULT hres;
    DEVGENUM ge;

    ge.pddi = pddi;
    ge.pdevg = LocalAlloc(LPTR, cbX(DEVGINFO));
    if (ge.pdevg) {
        DIDEVCAPS_DX3 caps;

        pddi->pvAcq = ge.pdevg;

        /*
         *  Get worst-case axis and button count.
         */
        caps.dwSize = cbX(caps);
        hres = IDirectInputDevice_GetCapabilities(pddi->pdid,
                                                  (PV)&caps);
        if (SUCCEEDED(hres)) {

            ge.df.dwSize = cbX(ge.df);
            ge.df.dwObjSize = cbX(DIOBJECTDATAFORMAT);
            ge.df.dwFlags = 0;
            ge.df.dwNumObjs = 0;
            ge.df.dwDataSize = 0;

            ge.dwNumObjs = caps.dwAxes + caps.dwPOVs + caps.dwButtons;
            ge.df.rgodf = LocalAlloc(LPTR,
                    (caps.dwAxes + caps.dwPOVs + caps.dwButtons) *
                                                ge.df.dwObjSize);

            if (ge.df.rgodf) {
                if (SUCCEEDED(hres =
                        IDirectInputDevice_EnumObjects(pddi->pdid,
                            Devg_DataFormatEnumProc, &ge,
                            DIDFT_AXIS | DIDFT_POV | DIDFT_ALIAS | DIDFT_VENDORDEFINED )) &&
                    SUCCEEDED(hres =
                        IDirectInputDevice_EnumObjects(pddi->pdid,
                            Devg_DataFormatEnumProc, &ge, DIDFT_BUTTON| DIDFT_ALIAS | DIDFT_VENDORDEFINED))) {
                    ge.df.dwDataSize = (ge.df.dwDataSize + 3) & ~3;

                    ge.pdevg->cbState = ge.df.dwDataSize;
                    ge.pdevg->pvState = LocalAlloc(LPTR, ge.pdevg->cbState);

                    if (ge.pdevg->pvState) {
                        hres = IDirectInputDevice_SetDataFormat(
                                        pddi->pdid, &ge.df);
                    } else {
                        hres = E_OUTOFMEMORY;
                    }
                }

                LocalFree(ge.df.rgodf);
            } else {
                hres = E_OUTOFMEMORY;
            }

        }
    } else {
        hres = E_OUTOFMEMORY;
    }
    return hres;
}

/*****************************************************************************
 *
 *      c_acqvtblDev
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA

ACQVTBL c_acqvtblDev = {
    Devg_UpdateStatus,
    Devg_SetDataFormat,
    Devg_Destroy,
    0,
};

#pragma END_CONST_DATA