/*****************************************************************************
 *
 *  PidEff.c
 *  Copyright (c) 1999 Microsoft Corporation.  All Rights Reserved.
 *
 *  Abstract:
 *
 *      Download PID Effect Block.
 *
 *****************************************************************************/
#include "pidpr.h"

#define sqfl            ( sqflEff )

#pragma BEGIN_CONST_DATA

/*
 * The structure c_rgUsgEffects, aids in translating elements in the DIEFFECT
 * structure to PID usages 
 */
static PIDUSAGE c_rgUsgEffect[] =
{
    MAKE_PIDUSAGE(DURATION,               FIELD_OFFSET(DIEFFECT,dwDuration)       ),
    MAKE_PIDUSAGE(SAMPLE_PERIOD,          FIELD_OFFSET(DIEFFECT,dwSamplePeriod)   ),
    MAKE_PIDUSAGE(GAIN,                   FIELD_OFFSET(DIEFFECT,dwGain)           ),
    MAKE_PIDUSAGE(TRIGGER_BUTTON,         FIELD_OFFSET(DIEFFECT,dwTriggerButton)  ), 
    MAKE_PIDUSAGE(TRIGGER_REPEAT_INTERVAL,FIELD_OFFSET(DIEFFECT,dwTriggerRepeatInterval) ),
#if DIRECTINPUT_VERSION  >= 0x600
        MAKE_PIDUSAGE(START_DELAY            ,FIELD_OFFSET(DIEFFECT,dwStartDelay)),
#endif
};

/* 
 * g_Effect provides context to the c_rgUsgEffect struct 
 */
PIDREPORT g_Effect =
{
    HidP_Output,                        // Effect Blocks can only be output reports 
    HID_USAGE_PAGE_PID,                 // Usage Page
    HID_USAGE_PID_SET_EFFECT_REPORT,    // Collection 
    cbX(DIEFFECT),                      // Size of incoming data
    cA(c_rgUsgEffect),                  // number of elements in c_rgUsgEffect
    c_rgUsgEffect                       // how elements of DIEFFECT are translated to PID
}; 

/* 
 *  Effect block index to PID usage 
 */
static PIDUSAGE    c_rgUsgBlockIndex[] =
{
    MAKE_PIDUSAGE(EFFECT_BLOCK_INDEX,  0x0 ),
};

/*
 * For some PID transactions block index is output report 
 */
PIDREPORT g_BlockIndex =
{
    HidP_Output,                        // Report Type
    HID_USAGE_PAGE_PID,                 // Usage Page
    0x0,                                // Any collection                            
    cbX(DWORD),                         // size of incoming data
    cA(c_rgUsgBlockIndex),              // translation table for effect block index to PID usages
    c_rgUsgBlockIndex
};

/* 
 * In the PID state report, block index is an input report
 */

PIDREPORT g_BlockIndexIN =
{
    HidP_Input,
    HID_USAGE_PAGE_PID,
    HID_USAGE_PID_STATE_REPORT,                                                            
    cbX(DWORD),
    cA(c_rgUsgBlockIndex),
    c_rgUsgBlockIndex
};



//CAssertF(MAX_ORDINALS == cA(c_rgUsgOrdinals));

PIDREPORT   g_TypeSpBlockOffset =
{
    HidP_Output,                        // For PID ordinals output reports
    HID_USAGE_PAGE_PID,                 // Usage Page
    HID_USAGE_PID_TYPE_SPECIFIC_BLOCK_OFFSET,  
    cA(c_rgUsgOrdinals)*cbX(DWORD),     // sizeof incoming data
    cA(c_rgUsgOrdinals),                // number of elements
    c_rgUsgOrdinals                     // translation table 
};

#pragma END_CONST_DATA

PIDREPORT   g_Direction =
{
    HidP_Output,                        // For PID ordinals output reports
    HID_USAGE_PAGE_PID,                 // Usage Page
    HID_USAGE_PID_DIRECTION,            
    0x0,
    0x0,
    NULL
};


/*****************************************************************************
 *
 *      hresFinddwUsageFromdwFlags
 *
 *      Given the flags for a DEVICEOBJECTINSTANCE, find the usage and usage page
 *      On init we enum the device and cache the 
 *      DeviceObjects marked as actuators and Effect Triggers. 
 *
 *****************************************************************************/
HRESULT
    hresFinddwUsageFromdwFlags
    (
    IDirectInputEffectDriver *ped,
    DWORD dwFlags,
    DWORD *pdwUsage
    )
{
    HRESULT hres = S_OK;
    CPidDrv *this = (CPidDrv *)ped;

    EnterProcI( PID_hresFinddwUsageFromdwFlags, (_"xxx", ped, dwFlags, pdwUsage ));

    // Init FF attributes 
    hres = PID_InitFFAttributes(ped);

    if( SUCCEEDED(hres) )
    {
        /* Better be a FF object ( actuator / Trigger ) */
        if(   dwFlags & DIDFT_FFACTUATOR 
              || dwFlags & DIDFT_FFEFFECTTRIGGER )
        {
            hres = S_OK;
        } else
        {
            SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("%s:FAIL dwFlags(0x%x) not FFACTUATOR | FFEFFECTTRIGGER "),
                            s_tszProc, dwFlags );
            hres = E_UNEXPECTED;
        }

        if( SUCCEEDED(hres) )
        {
            UINT cFFObj;
            hres = E_NOTIMPL;

            /* Loop through the all the objects we found during enum */
            for(cFFObj = 0x0;
               cFFObj < this->cFFObj;
               cFFObj++ )
            {
                PDIUSAGEANDINST pdiUI = this->rgFFUsageInst + cFFObj;

                if( pdiUI->dwType == dwFlags )
                {
                    *pdwUsage = pdiUI->dwUsage;
                    hres = S_OK;
                    break;
                }
            }
        }
    }
    if( FAILED(hres) )
    {
        SquirtSqflPtszV(sqfl | sqflError,
                        TEXT("%s:FAIL No mapping for dwFlags(0x%x)  "),
                        s_tszProc, dwFlags );
    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *      PID_NewEffectIndex
 *
 *      Gets a new effect index. 
 *
 *      For host managed devices, we assign an unused effect ID. 
 *      For device managed, we get the effectID from the device 
 *
 *****************************************************************************/
STDMETHODIMP 
    PID_NewEffectIndex
    (
    IDirectInputEffectDriver *ped,
    LPDIEFFECT  peff,
    DWORD       dwEffectId,
    PDWORD      pdwEffect 
    )
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres;
    USHORT dwEffect;

    EnterProcI(PID_NewEffectIndex, (_"xx", this, pdwEffect));

    AssertF(*pdwEffect == 0);

    // Default assumption is that the device is full.
    hres = DIERR_DEVICEFULL; 

    if( this->uDeviceManaged & PID_DEVICEMANAGED )
    {
        PVOID   pReport;
        UINT    cbReport;
        USHORT  LinkCollection = 0x0;
        USHORT  TLinkCollection = 0x0;

        UINT    nUsages = 0x1;
        USAGE   Usage;
        USAGE   Collection = HID_USAGE_PID_CREATE_NEW_EFFECT;
        USAGE   UsagePage; 
        HIDP_REPORT_TYPE  HidP_Type = HidP_Feature;

        cbReport = this->cbReport[HidP_Type];
        pReport = this->pReport[HidP_Type];

        ZeroBuf(pReport, cbReport);

        // Usage and Usage page determine type of new effect
        Usage       = DIGETUSAGE(dwEffectId);
        UsagePage   = DIGETUSAGEPAGE(dwEffectId);  

        hres = PID_GetLinkCollectionIndex(ped, UsagePage, Collection, 0x0, &LinkCollection );
        if( SUCCEEDED(hres) )
        {
            Collection = HID_USAGE_PID_EFFECT_TYPE;
            hres = PID_GetLinkCollectionIndex(ped, UsagePage, Collection, LinkCollection, &TLinkCollection ); 
        }

        if( SUCCEEDED(hres) )
        {

            hres = HidP_SetUsages 
                   (
                   HidP_Type,
                   UsagePage,
                   TLinkCollection,
                   &Usage,
                   &nUsages,
                   this->ppd,
                   pReport,
                   cbReport);

        }

        if( SUCCEEDED(hres) && PIDMAKEUSAGEDWORD(ET_CUSTOM) == dwEffectId )
        {
            DICUSTOMFORCE DiParam;
            LONG lValue;
            int nBytes;

            AssertF(peff->cbTypeSpecificParams <= cbX(DiParam) );
            memcpy(&DiParam, peff->lpvTypeSpecificParams, cbX(DiParam));

            //how many bytes do we need per sample?
            nBytes    =    (   this->customCaps[   0].BitSize    +    this->customCaps[   1].BitSize    +    this->customCaps[   2].BitSize)/8;

            lValue = DiParam.cSamples * nBytes;

            hres = HidP_SetScaledUsageValue 
                   (
                   HidP_Type,
                   HID_USAGE_PAGE_GENERIC,
                   LinkCollection,
                   HID_USAGE_GENERIC_BYTE_COUNT,
                   lValue,
                   this->ppd,
                   pReport,
                   cbReport
                   );

        }

        // Send the report
        if( SUCCEEDED(hres) )
        {
            hres = PID_SendReport(ped, pReport, cbReport, HidP_Type, TRUE, 0, 1); 
        }

        // Get back the effect ID
        if( SUCCEEDED(hres) )
        {
            PIDREPORT   BlockIndex = g_BlockIndex;
            USHORT      LinkCollection;

            BlockIndex.Collection = HID_USAGE_PID_BLOCK_LOAD_REPORT;
            BlockIndex.HidP_Type  = HidP_Feature; 

            hres =  PID_GetLinkCollectionIndex
                    (ped,
                     BlockIndex.UsagePage,
                     BlockIndex.Collection,
                     0x0,
                     &LinkCollection);

            if( SUCCEEDED(hres) )
            {
                PUCHAR pReport =  this->pReport[BlockIndex.HidP_Type];
                UINT   cbReport = this->cbReport[BlockIndex.HidP_Type];
                PID_GetReport(ped, &BlockIndex, LinkCollection, pReport, cbReport );

                // Get the EffectIndex
                hres = PID_ParseReport
                       (
                       ped,
                       &BlockIndex,
                       LinkCollection,
                       pdwEffect,
                       cbX(*pdwEffect),
                       pReport,
                       cbReport
                       );

				
                if( SUCCEEDED(hres ) )
                {
                    NTSTATUS ntStat;
                    USAGE   rgUsageList[MAX_BUTTONS];
                    UINT  cUsageList = MAX_BUTTONS;
                    PID_GetLinkCollectionIndex(ped, HID_USAGE_PAGE_PID, HID_USAGE_PID_BLOCK_LOAD_STATUS, LinkCollection, &LinkCollection );

                    ntStat = HidP_GetUsages
                             (
                             BlockIndex.HidP_Type,
                             HID_USAGE_PAGE_PID, 
                             LinkCollection, 
                             rgUsageList, 
                             &cUsageList,
                             this->ppd,
                             pReport,
                             cbReport);

                    if(SUCCEEDED(ntStat) )
                    {
						if (cUsageList != 0)
						{
							if( rgUsageList[0] == HID_USAGE_PID_BLOCK_LOAD_FULL )
							{
								hres = DIERR_DEVICEFULL;
							} else if(rgUsageList[0] == HID_USAGE_PID_BLOCK_LOAD_ERROR )
							{
								hres = DIERR_PID_BLOCKLOADERROR;
							} else
							{
								AssertF(rgUsageList[0] == HID_USAGE_PID_BLOCK_LOAD_SUCCESS);
							}
						}
						else
						{
							//because of issues w/ some chipsets (see Whistler bugs 231235, 304863),
							//cUsageList can be 0.
							//so warn the user.
							RPF(TEXT("Unable to get the effect load status -- may be a USB chipset issue!"));
							RPF(TEXT("The effect may not play correctly!"));
						}
                    }
                }

                if(SUCCEEDED(hres))
                {
                    NTSTATUS ntStat;
                    UsagePage = HID_USAGE_PAGE_PID;
                    Usage = HID_USAGE_PID_RAMPOOL_AVAILABLE;

                    ntStat = HidP_GetScaledUsageValue 
                             (
                             HidP_Feature,
                             UsagePage,
                             LinkCollection,
                             Usage,
                             &this->dwUsedMem,
                             this->ppd,
                             pReport,
                             cbReport
                             );

                    if(FAILED(ntStat) )
                    {
                        // Reset the amount of used memory
                        this->dwUsedMem = 0x0 ;

                        SquirtSqflPtszV(sqfl | sqflError,
                                        TEXT("%s: FAIL HidP_GetScaledUsageValue:0x%x for(%x, %x,%x:%s)"),
                                        s_tszProc, ntStat, 
                                        LinkCollection, UsagePage, Usage, 
                                        PIDUSAGETXT(UsagePage,Usage) );
                    }
                }

            }
        }

	 if( SUCCEEDED(hres) )
        {

            PEFFECTSTATE    pEffectState =  PeffectStateFromBlockIndex(this,*pdwEffect);    
            // Serialize access to for new effect
            WaitForSingleObject(g_hmtxShared, INFINITE);

            AssertF(! (pEffectState->lEfState & PID_EFFECT_BUSY ));

            pEffectState->lEfState |= PID_EFFECT_BUSY;    
            hres = S_OK;

            ReleaseMutex(g_hmtxShared);

        }
    } else
    {
        // Serialize access to common memory block
        WaitForSingleObject(g_hmtxShared, INFINITE);

        for(dwEffect = 1; 
           dwEffect <= this->cMaxEffects; 
           dwEffect++)
        {
            PEFFECTSTATE    pEffectState =  PeffectStateFromBlockIndex(this,dwEffect);    
            if( ! ( pEffectState->lEfState & PID_EFFECT_BUSY ) )
            {
                pEffectState->lEfState |= PID_EFFECT_BUSY;    
                *pdwEffect =  dwEffect;

                ZeroBuf(pEffectState->PidMem, cbX(pEffectState->PidMem[0]) * this->cMaxParameters );
                hres = S_OK;
                break;
            }
        }
        ReleaseMutex(g_hmtxShared);
    }

    if( SUCCEEDED(hres) )
    {
        ((PUNITSTATE)(g_pshmem + this->iUnitStateOffset))->cEfDownloaded++;
    } else
    {
        SquirtSqflPtszV(sqfl | sqflError,
                        TEXT("%s:FAIL Could not create new effects, already have %d "),
                        s_tszProc, ((PUNITSTATE)(g_pshmem + this->iUnitStateOffset))->cEfDownloaded );
    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *      PID_ValidateEffectIndex
 *
 *      Validates an effect index. 
 *
 *****************************************************************************/
STDMETHODIMP  PID_ValidateEffectIndex
    (
    IDirectInputEffectDriver *ped,
    DWORD   dwEffect 
    )
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres;
    PEFFECTSTATE    pEffectState =  PeffectStateFromBlockIndex(this, dwEffect);    

    EnterProc(PID_ValidateEffectIndex, (_"xx", this, dwEffect));

    if( pEffectState->lEfState & PID_EFFECT_BUSY )
    {
        hres = S_OK;
    } else
    {
        hres = E_HANDLE;
    }


    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *      PID_DestroyEffect
 *
 *          Remove an effect from the device.
 *
 *          If the effect is playing, the driver should stop it
 *          before unloading it.
 *
 *  dwId
 *
 *          The external joystick number being addressed.
 *
 *  dwEffect
 *
 *          The effect to be destroyed.
 *
 *  Returns:
 *
 *          S_OK on success.
 *
 *          Any other DIERR_* error code may be returned.
 *
 *          Private driver-specific error codes in the range
 *          DIERR_DRIVERFIRST through DIERR_DRIVERLAST
 *          may be returned.
 *
 *
 *      Makes an effect Index available for reuse. Deallocates parameter block
 *      memory. 
 *
 *****************************************************************************/
STDMETHODIMP 
    PID_DestroyEffect
    (
    IDirectInputEffectDriver *ped,
    DWORD   dwId,
    DWORD   dwEffect 
    )
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres=S_OK;

    EnterProc(PID_DestroyEffectIndex, (_"xx", this, dwEffect));

    DllEnterCrit();

    // Stop the Effect 
    hres = PID_EffectOperation
           (
           ped, 
           dwId, 
           dwEffect,
           PID_DIES_STOP, 
           0x0,
		   TRUE,
		   0,
		   1
           );


    if(SUCCEEDED(hres) && 
       ( this->uDeviceManaged & PID_DEVICEMANAGED ) )
    {
        // Device Managed memory needs to be freed explicitly. 

        USHORT  cbReport;
        PUCHAR  pReport;
        PIDREPORT   BlockIndex = g_BlockIndex;
        USHORT      LinkCollection;

        cbReport = this->cbReport[BlockIndex.HidP_Type];
        pReport = this->pReport[BlockIndex.HidP_Type];

        ZeroBuf(pReport, cbReport);

        BlockIndex.Collection = HID_USAGE_PID_BLOCK_FREE_REPORT;
        BlockIndex.HidP_Type  = HidP_Output; 

        PID_GetLinkCollectionIndex
            (ped,
             BlockIndex.UsagePage,
             BlockIndex.Collection,
             0x0,
             &LinkCollection);

        hres = PID_PackValue
               (
               ped,
               &BlockIndex,
               LinkCollection,
               &dwEffect,
               cbX(dwEffect),
               pReport,
               cbReport
               );
        if(SUCCEEDED(hres) )
        {
            hres = PID_SendReport(ped, pReport, cbReport, BlockIndex.HidP_Type, TRUE, 0, 1); 
        }
    }

    if( SUCCEEDED(hres) )
    {
        PEFFECTSTATE    pEffectState =  PeffectStateFromBlockIndex(this,dwEffect);    
        UINT    nAlloc, uParam;

        WaitForSingleObject(g_hmtxShared, INFINITE);

        pEffectState->lEfState = PID_EFFECT_RESET;

        for( uParam = 0x0; uParam < this->cMaxParameters; uParam++ )
        {
            PPIDMEM         pMem = &pEffectState->PidMem[uParam] ;

            if( PIDMEM_SIZE(pMem) )
            {
                PPIDMEM pTmp;
				PUNITSTATE pUnitState = (PUNITSTATE)(g_pshmem + this->iUnitStateOffset);

                for(nAlloc = 0x0, pTmp = &(pUnitState->Guard[0]); 
                   nAlloc < pUnitState->nAlloc; 
                   nAlloc++, pTmp = (PPIDMEM)((PUCHAR)pUnitState + pTmp->iNext))
                {
                    if( (PPIDMEM)(pTmp->iNext) == (PPIDMEM)((PUCHAR)pMem - (PUCHAR)pUnitState ))
                    {
                        pTmp->iNext = pMem->iNext;
                        pUnitState->nAlloc--;
                        pUnitState->cbAlloc -= PIDMEM_SIZE(pMem);
                        pMem->iNext = 0;
                        pMem->uOfSz = 0x0;
                        break;
                    }

                }
            }
        }
        ReleaseMutex(g_hmtxShared);
    }

    if( SUCCEEDED(hres) )
    {
        ((PUNITSTATE)(g_pshmem + this->iUnitStateOffset))->cEfDownloaded--;
    }

    DllLeaveCrit();

    ExitOleProc(); 
    return hres;
}


/*****************************************************************************
 *
 *      PID_SanitizeEffect
 *
 *      Sanitize the parameters in the DIEFFECT structure. 
 *      Clip values of magnitude, time, etc .. 
 *      Convert the axes array to usage, usage page from the DINPUT obj instances.
 *      Convert and scale angles. 
 *
 *****************************************************************************/
HRESULT PID_SanitizeEffect
    (
    IDirectInputEffectDriver *ped,
    LPDIEFFECT lpeff,
    DWORD      dwFlags
    )
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres = S_OK;
    UINT nAxis;
    EnterProc( PID_SanitizeEffect, (_"xxx", ped, lpeff, dwFlags));

    if(   ( dwFlags & DIEP_TRIGGERBUTTON )
          && lpeff->dwTriggerButton != -1 )
    {
        DWORD   dwUsage;
        hres = hresFinddwUsageFromdwFlags(ped, lpeff->dwTriggerButton, &dwUsage);
        if( SUCCEEDED(hres) )
        {
            USAGE Usage = DIGETUSAGE(dwUsage);
            USAGE UsagePage = DIGETUSAGEPAGE(dwUsage);  
            lpeff->dwTriggerButton = Usage;
        } else
        {
            lpeff->dwTriggerButton = 0x0;
        }
    } else
    {
        lpeff->dwTriggerButton = 0x0;
    }


    for(nAxis = 0x0; 
       nAxis < lpeff->cAxes; 
       nAxis++ )
    {
        DWORD   dwUsage;
        hres = hresFinddwUsageFromdwFlags(ped, lpeff->rgdwAxes[nAxis], &dwUsage);
        if(SUCCEEDED(hres) )
        {
            lpeff->rgdwAxes[nAxis] = dwUsage;
        }

		//if we have only 1 axis and direction of 0 or 360, make sure the direction matches the axis!
		//if direction is not 0, we do not know what the app wants, so let it be.
		if ((lpeff->cAxes == 1) && (lpeff->rglDirection[nAxis] % 360*DI_DEGREES == 0))
		{
#ifndef HID_USAGE_SIMULATION_STEERING
#define	HID_USAGE_SIMULATION_STEERING       ((USAGE) 0xC8)
#endif
#ifndef HID_USAGE_SIMULATION_ACCELERATOR 
#define	HID_USAGE_SIMULATION_ACCELERATOR    ((USAGE) 0xC4)
#endif
#ifndef HID_USAGE_SIMULATION_BRAKE
#define	HID_USAGE_SIMULATION_BRAKE          ((USAGE) 0xC5)
#endif
			//if it is X-axis or steering on the wheel, set direction to 90 degrees
			if ((DIGETUSAGE(lpeff->rgdwAxes[nAxis]) == HID_USAGE_GENERIC_X) || (DIGETUSAGE(lpeff->rgdwAxes[nAxis]) == HID_USAGE_SIMULATION_STEERING))
			{
				lpeff->rglDirection[nAxis] = 90*DI_DEGREES;
			}
			//if it is Y-axis or accelerator or brake, set direction to 0
			else if ((DIGETUSAGE(lpeff->rgdwAxes[nAxis]) == HID_USAGE_GENERIC_Y) || (DIGETUSAGE(lpeff->rgdwAxes[nAxis]) == HID_USAGE_SIMULATION_ACCELERATOR) ||
				(DIGETUSAGE(lpeff->rgdwAxes[nAxis]) == HID_USAGE_SIMULATION_BRAKE))
			{
				lpeff->rglDirection[nAxis] = 0x0;
			}
		}
		else
		//we have more than 1 axes or direction is non-0 for 1-axis effect; leave the direction along
		{
			lpeff->rglDirection[nAxis] %= 360*DI_DEGREES;
			if(lpeff->rglDirection[nAxis] < 0)
			{
				lpeff->rglDirection[nAxis] += 360*DI_DEGREES;
			}
		}
    }

	
    // Clip the values to min / max

    lpeff->dwGain   = Clip(lpeff->dwGain,  DI_FFNOMINALMAX);

    // Scale to units that device expects
    PID_ApplyScalingFactors(ped, &g_Effect, &this->DiSEffectScale, this->DiSEffectScale.dwSize, &this->DiSEffectOffset, this->DiSEffectOffset.dwSize, lpeff, lpeff->dwSize );

    ExitOleProc();
    return hres;
}


/*****************************************************************************
 *
 *      CPidDrv_DownloadEffect
 *
 *          Send an effect to the device.
 *
 *  dwId
 *
 *          The external joystick number being addressed.
 *
 *  dwEffectId
 *
 *          Internal identifier for the effect, taken from
 *          the DIEFFECTATTRIBUTES structure for the effect
 *          as stored in the registry.
 *
 *  pdwEffect
 *
 *          On entry, contains the handle of the effect being
 *          downloaded.  If the value is zero, then a new effect
 *          is downloaded.  If the value is nonzero, then an
 *          existing effect is modified.
 *
 *          On exit, contains the new effect handle.
 *
 *          On failure, set to zero if the effect is lost,
 *          or left alone if the effect is still valid with
 *          its old parameters.
 *
 *          Note that zero is never a valid effect handle.
 *
 *  peff
 *
 *          The new parameters for the effect.  The axis and button
 *          values have been converted to object identifiers
 *          as follows:
 *
 *          - One type specifier:
 *
 *              DIDFT_RELAXIS,
 *              DIDFT_ABSAXIS,
 *              DIDFT_PSHBUTTON,
 *              DIDFT_TGLBUTTON,
 *              DIDFT_POV.
 *
 *          - One instance specifier:
 *
 *              DIDFT_MAKEINSTANCE(n).
 *
 *          Other bits are reserved and should be ignored.
 *
 *          For example, the value 0x0200104 corresponds to
 *          the type specifier DIDFT_PSHBUTTON and
 *          the instance specifier DIDFT_MAKEINSTANCE(1),
 *          which together indicate that the effect should
 *          be associated with button 1.  Axes, buttons, and POVs
 *          are each numbered starting from zero.
 *
 *  dwFlags
 *
 *          Zero or more DIEP_* flags specifying which
 *          portions of the effect information has changed from
 *          the effect already on the device.
 *
 *          This information is passed to drivers to allow for
 *          optimization of effect modification.  If an effect
 *          is being modified, a driver may be able to update
 *          the effect in situ and transmit to the device
 *          only the information that has changed.
 *
 *          Drivers are not, however, required to implement this
 *          optimization.  All fields in the DIEFFECT structure
 *          pointed to by the peff parameter are valid, and
 *          a driver may choose simply to update all parameters of
 *          the effect at each download.
 *
 *  Returns:
 *
 *          S_OK on success.
 *
 *          DI_TRUNCATED if the parameters of the effect were
 *          successfully downloaded, but some of them were
 *          beyond the capabilities of the device and were truncated.
 *
 *          DI_EFFECTRESTARTED if the parameters of the effect
 *          were successfully downloaded, but in order to change
 *          the parameters, the effect needed to be restarted.
 *
 *          DI_TRUNCATEDANDRESTARTED if both DI_TRUNCATED and
 *          DI_EFFECTRESTARTED apply.
 *
 *          Any other DIERR_* error code may be returned.
 *
 *          Private driver-specific error codes in the range
 *          DIERR_DRIVERFIRST through DIERR_DRIVERLAST
 *          may be returned.
 *
 *****************************************************************************/

STDMETHODIMP
    PID_DownloadEffect
    (
    IDirectInputEffectDriver *ped,
    DWORD dwId, 
    DWORD dwEffectId,
    LPDWORD pdwEffect, 
    LPCDIEFFECT peff, 
    DWORD dwFlags
    )
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres = S_OK;
    DIEFFECT    eff;
    DWORD       rgdwAxes[MAX_AXES];
    LONG        rglDirection[MAX_AXES];
	UINT        uParameter = 0x0 ;
	UINT		totalBlocks = 0x0;
	BOOL		bBlocking = FALSE;

    EnterProcI( PID_DownloadEffectBlock, (_"xxxxxx", ped, dwId, dwEffectId, pdwEffect, peff, dwFlags));

    AssertF(peff->cAxes <= MAX_AXES);

    DllEnterCrit();

    // If new effect is being downloaded  
    if( *pdwEffect == 0x0 )
    {
        // Verify that dwEffectId is supported
        DWORD dwJunk;
        PIDSUPPORT  pidSupport;
        pidSupport.dwPidUsage = dwEffectId;
        pidSupport.HidP_Type = HidP_Output;
        pidSupport.Type      = HID_BUTTON;

        hres = PID_Support
               (
               ped,
               0x1,
               &pidSupport,
               &dwJunk
               );

        if(FAILED(hres))
        {
            SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("%s:FAIL dwEffectId(0x%x) not supported"),
                            s_tszProc, dwEffectId );
        }
    }


    if( SUCCEEDED(hres) )
    {
        // Make a local copy of the effect structure
        // And sanitize the effect struct
        eff = *peff;
        memcpy(rgdwAxes, peff->rgdwAxes,eff.cAxes*cbX(*(eff.rgdwAxes)));
        memcpy(rglDirection, peff->rglDirection, eff.cAxes*cbX(*(eff.rglDirection)));
        eff.rgdwAxes = rgdwAxes;
        eff.rglDirection = rglDirection;
        hres = PID_SanitizeEffect(ped, &eff, dwFlags);
    }

    // Allocate new effect index or Validate Existing index 
    if( SUCCEEDED(hres) )
    {
        if( *pdwEffect != 0x0 )
        {
             hres = PID_ValidateEffectIndex(ped, *pdwEffect);
        }
        else
        {
             if (! (dwFlags & DIEP_NODOWNLOAD))
             {
                  hres = PID_NewEffectIndex(ped, &eff, dwEffectId, pdwEffect);
				  //block the first time around
				  bBlocking = TRUE;
             }
        }
    }

    if (dwFlags & DIEP_NODOWNLOAD)
    {
        goto done;
    }

	//if the DIEP_NORESTART flag is passed, we have no block because this may fail
	//if the device can't update the parameters on the fly
	if (dwFlags & DIEP_NORESTART)
	{
		bBlocking = TRUE;
	}

    if( SUCCEEDED(hres) )
    {
		//count up how many total blocks we will have in this download
		//check wether we're sending the effect block
		if (dwFlags & ( DIEP_DURATION | DIEP_SAMPLEPERIOD | DIEP_GAIN | DIEP_TRIGGERBUTTON | DIEP_TRIGGERREPEATINTERVAL | DIEP_AXES | DIEP_DIRECTION | DIEP_STARTDELAY ) )
		{
			totalBlocks ++;
		}
		//check whether we're sending the type-specific params
		if (dwFlags & DIEP_TYPESPECIFICPARAMS)
		{
			//this is slightly different, in that conditions can have 1 type-specific block PER AXIS,
			//i.e. currently up to 2
			//so if we have a DICONDITION, we check how many type-specific blocks we've got
			if ((dwEffectId == PIDMAKEUSAGEDWORD(ET_SPRING)) ||
						(dwEffectId == PIDMAKEUSAGEDWORD(ET_DAMPER)) ||
						(dwEffectId == PIDMAKEUSAGEDWORD(ET_INERTIA)) ||
						(dwEffectId == PIDMAKEUSAGEDWORD(ET_FRICTION)))
			{
				totalBlocks +=(eff.cbTypeSpecificParams)/sizeof(DICONDITION);
				//DICONDITIONS also can't have envelopes
				dwFlags &= ~DIEP_ENVELOPE;
			}
			else
			{
				totalBlocks++;
			}
		}
		//check whether we're sending the envelope
		if ((dwFlags & DIEP_ENVELOPE) && (eff.lpEnvelope != NULL))
		{
			totalBlocks++;
		}
		//check whether we need to send the start reprot
		if (dwFlags & DIEP_START)
		{
			totalBlocks++;
		}
		//make sure that we haven't got more than the maximum
		AssertF(totalBlocks <= MAX_BLOCKS);

        // Do the parameter block
        if(     SUCCEEDED(hres) 
                &&  ( dwFlags & ( DIEP_TYPESPECIFICPARAMS | DIEP_ENVELOPE)  )
          )
        {
            hres =  PID_DoParameterBlocks
                    (
                    ped,
                    dwId, 
                    dwEffectId,
                    *pdwEffect, 
                    &eff, 
                    dwFlags,
                    &uParameter,
					bBlocking,
					totalBlocks
                    );
        }

        // Now do the effect report 
        if( SUCCEEDED(hres) 
            && ( dwFlags & ( DIEP_DURATION | DIEP_SAMPLEPERIOD | DIEP_GAIN | DIEP_TRIGGERBUTTON | DIEP_TRIGGERREPEATINTERVAL | DIEP_AXES | DIEP_DIRECTION | DIEP_STARTDELAY ) ) )
        {
            USHORT  cbReport;
            PUCHAR  pReport;

            AssertF(g_Effect.HidP_Type == HidP_Output);
            cbReport = this->cbReport[g_Effect.HidP_Type];
            pReport = this->pReport[g_Effect.HidP_Type];

            // Set the Effect Structure 
            if( SUCCEEDED(hres) )
            {
                USHORT  LinkCollection;
                PID_GetLinkCollectionIndex(ped, g_Effect.UsagePage, g_Effect.Collection, 0x0, &LinkCollection );

                ZeroBuf(pReport, cbReport);

                // Do the common elements of the effect structure
                hres = PID_PackValue
                       (
                       ped,
                       &g_Effect,
                       LinkCollection,
                       &eff,
                       eff.dwSize,
                       pReport,
                       cbReport
                       );


                // Set the Effect Block Index
                if( SUCCEEDED(hres) )
                {
                    hres = PID_PackValue
                           (
                           ped,
                           &g_BlockIndex,
                           LinkCollection,
                           pdwEffect,
                           cbX(*pdwEffect),
                           pReport,
                           cbReport
                           );
                }

                // Set Direction and axis attributes
                if( SUCCEEDED(hres) )
                {
                    USHORT  DirectionCollection;

                    PID_GetLinkCollectionIndex(ped, g_Direction.UsagePage, g_Direction.Collection, 0x0, &DirectionCollection );
                    PID_ApplyScalingFactors(ped, &g_Direction, &this->DiSEffectAngleScale, cbX(this->DiSEffectAngleScale), &this->DiSEffectAngleOffset, cbX(this->DiSEffectAngleOffset), eff.rglDirection, eff.cAxes*cbX(LONG) );

                    hres = PID_PackValue
                           (
                           ped,
                           &g_Direction,
                           DirectionCollection,
                           eff.rglDirection,
                           eff.cAxes * cbX(LONG),
                           pReport,
                           cbReport
                           );


                    if(SUCCEEDED(hres) && 
                      ! ( eff.dwFlags & DIEFF_CARTESIAN ) )
                    {
                        // Direction Enable
                        USHORT  Usage;
                        USHORT  UsagePage;
                        UINT    nUsages = 0x1;
                        NTSTATUS  ntStat;

                        // Direction Enable is in the set effect collection
                        UsagePage = g_Effect.UsagePage;
                        Usage = HID_USAGE_PID_DIRECTION_ENABLE;

                        ntStat = HidP_SetUsages 
                                 (
                                 HidP_Output,
                                 UsagePage,
                                 LinkCollection,
                                 &Usage,
                                 &nUsages,
                                 this->ppd,
                                 pReport,
                                 cbReport);


                        if( FAILED(ntStat) )
                        {
                            SquirtSqflPtszV(sqfl | sqflError,
                                            TEXT("%s: FAIL HidP_SetUsages:0x%x for(%x,%x,%x:%s)"),
                                            s_tszProc, ntStat, 
                                            LinkCollection, UsagePage, Usage,
                                            PIDUSAGETXT(UsagePage,Usage) );

                        } else
                        {
                            SquirtSqflPtszV(sqfl | sqflVerbose,
                                            TEXT("%s: HidP_SetUsages:0x%x for(%x,%x,%x:%s)"),
                                            s_tszProc, ntStat, 
                                            LinkCollection,UsagePage, Usage,
                                            PIDUSAGETXT(UsagePage,Usage) );
                        }



                    } else  //if(  dwFlags  & DIEP_AXES )
                    {
                        UINT    nAxis;
                        USHORT  LinkCollection_AE=0x0;

                        if(SUCCEEDED(PID_GetLinkCollectionIndex(ped, HID_USAGE_PAGE_PID, HID_USAGE_PID_AXES_ENABLE, 0x0, &LinkCollection_AE)))
                        {
                            // ISSUE-2001/03/29-timgill Need to support axes within pointer collections
                            // PID spec indicates a pointer collection, 
                            // Do we want to support axes enables within a pointer 
                            // collection ? 

                            // See if there is a pointer collection 

                        }

                        for(nAxis = 0x0; 
                           nAxis < eff.cAxes; 
                           nAxis++ )
                        {
                            UINT    nUsages = 0x1;
                            USHORT  Usage = DIGETUSAGE(eff.rgdwAxes[nAxis]);
                            USHORT  UsagePage = DIGETUSAGEPAGE(eff.rgdwAxes[nAxis]);
                            NTSTATUS ntStat;

                            //ISSUE-2001/03/29-timgill For now we assume any collection
                            ntStat = HidP_SetUsages 
                                     (
                                     HidP_Output,
                                     UsagePage,
                                     0x0,       
                                     &Usage,
                                     &nUsages,
                                     this->ppd,
                                     pReport,
                                     cbReport);

                            if( FAILED(ntStat) )
                            {
                                SquirtSqflPtszV(sqfl | sqflError,
                                                TEXT("%s: FAIL HidP_SetUsages:0x%x for(%x,%x,%x:%s)"),
                                                s_tszProc, ntStat, 
                                                0x0, UsagePage, Usage,
                                                PIDUSAGETXT(UsagePage,Usage) );
                                hres = ntStat;
                                break;
                            } else
                            {
                                SquirtSqflPtszV(sqfl | sqflVerbose,
                                                TEXT("%s: HidP_SetUsages:0x%x for(%x,%x,%x:%s)"),
                                                s_tszProc, ntStat, 
                                                0x0, UsagePage, Usage,
                                                PIDUSAGETXT(UsagePage, Usage) );
                            }
                        }
                    }
                }
                if(  SUCCEEDED(hres) 
                     && !( this->uDeviceManaged & PID_DEVICEMANAGED ) 
                  )
                {
                    // Need parameter block offsets
                    UINT indx;
                    USHORT LinkCollection;
                    LONG rglValue[MAX_ORDINALS];

                    PID_GetLinkCollectionIndex(ped,  g_Effect.UsagePage, g_TypeSpBlockOffset.Collection, 0x0, &LinkCollection );

                    for(indx = 0x0; indx < this->cMaxParameters; indx++ )
                    {
                        hres = PID_GetParameterOffset(ped, *pdwEffect, indx, 0x0, &rglValue[indx]); 
                        if(FAILED(hres))
                        {
                            break;
                        }
                    }
                    if(SUCCEEDED(hres))
                    {
                        hres = PID_PackValue
                               (
                               ped,
                               &g_TypeSpBlockOffset,
                               LinkCollection,
                               rglValue,
                               this->cMaxParameters*cbX(LONG),
                               pReport,
                               cbReport
                               );
                    }
                }

                // Set the Effect Type
                if( SUCCEEDED(hres) )
                {
                    USAGE   UsagePage = DIGETUSAGEPAGE(dwEffectId);
                    USAGE   Usage     = DIGETUSAGE(dwEffectId);

                    UINT    nUsages = 0x1;
                    USHORT  LinkCollection_ET;
                    NTSTATUS  ntStat;

                    PID_GetLinkCollectionIndex(ped, g_Effect.UsagePage, HID_USAGE_PID_EFFECT_TYPE, 0x0, &LinkCollection_ET);

                    ntStat = HidP_SetUsages 
                             (
                             HidP_Output,
                             UsagePage,
                             LinkCollection_ET,
                             &Usage,
                             &nUsages,
                             this->ppd,
                             pReport,
                             cbReport);
                    if( FAILED(ntStat) )
                    {
                        SquirtSqflPtszV(sqfl | sqflError,
                                        TEXT("%s: FAIL HidP_SetUsages:0x%x for(%x,%x,%x:%s)"),
                                        s_tszProc, ntStat, 
                                        LinkCollection_ET, UsagePage, Usage,
                                        PIDUSAGETXT(UsagePage,Usage) );
                        hres = ntStat;

                    } else
                    {
                        SquirtSqflPtszV(sqfl | sqflVerbose,
                                        TEXT("%s: HidP_SetUsages:0x%x for(%x,%x,%x:%s)"),
                                        s_tszProc, ntStat, 
                                        LinkCollection_ET, UsagePage, Usage,
                                        PIDUSAGETXT(UsagePage,Usage) );
                    }
                }


                if( SUCCEEDED(hres) )
                {
                    hres = PID_SendReport(ped, pReport, cbReport, g_Effect.HidP_Type, bBlocking, uParameter, totalBlocks);
					uParameter ++;
                }
            }
        }
    }

    if( FAILED(hres) )
    {
        PID_DestroyEffect(ped, dwId, *pdwEffect);
    }

    if(   SUCCEEDED(hres)
          && (dwFlags & DIEP_START) )
    {
        hres = PID_EffectOperation
               (
               ped, 
               dwId, 
               *pdwEffect,
               PID_DIES_START, 
               0x1,
			   bBlocking,
			   uParameter,
			   totalBlocks
               );

		if (SUCCEEDED(hres))
		{

			//set the status to DIEGES_PLAYING.
			//we do this because of the following: if an app calls Start(), and then immediately
			//calls GetEffectStatus(), it might happen that our second thread (pidrd.c) 
			//would not have time to update the status of the effect to DIEGES_PLAYING
			//(see Whistler bug 287035).
			//GetEffectStatus() returns (pEffectState->lEfState & DIEGES_PLAYING).
			//in the blocking case, we know that the call to WriteFile() has succeeded, and that
			//all the data has been written (see PID_SendReportBl() in pidhid.c) --
			//so we might as well set the status.
			//in the non-blocking case, the data can be buffered anyway -- so we might as well set the status.
			PEFFECTSTATE    pEffectState =  PeffectStateFromBlockIndex(this, *pdwEffect); 
			pEffectState->lEfState |= DIEGES_PLAYING;
		}
			   
    }

done:;

    DllLeaveCrit();

    ExitOleProc();
    return hres;
}