/*****************************************************************************
 *  PidHid.c
 *
 *  Copyright (c) 1999 Microsoft Corporation.  All Rights Reserved.
 *
 *  Abstract:
 *
 *      HID utility routines for PID .
 *
 *****************************************************************************/
#include "PidPr.h"

#define sqfl        (   sqflHid   )

/*****************************************************************************
 *
 *      PID_GetReportId
 *
 *          Obtain the HID report ID given the usage, usagePage and LinkCollection
 *
 *  IDirectInputEffectDriver | ped |
 *
 *          The effect driver interface
 *
 *  PPIDREPORT | pPidReport |
 *
 *          Address of PIDREPORT structure
 *
 *  USHORT | uLinkCollection |
 *  
 *          LinkCollection ID
 *
 *  OUT UCHAR * | pReportId |
 *
 *          Report ID. Undefined if unsuccessful 
 *
 *  Returns:
 *      
 *  HRESULT 
 *          Error code
 *
 *****************************************************************************/
STDMETHODIMP
    PID_GetReportId
    (
    IDirectInputEffectDriver *ped,
    PPIDREPORT  pPidReport, 
    USHORT  uLinkCollection,
    UCHAR* pReportId
    )
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres = S_OK;

    EnterProcI(PID_GetReportId, (_"xxxx", ped, pPidReport, pReportId));

    if( SUCCEEDED(hres) )
    {
        HIDP_VALUE_CAPS ValCaps;
        USHORT cAValCaps = 0x1;
        USAGE  Usage = DIGETUSAGE(pPidReport->rgPidUsage->dwUsage);
        USAGE  UsagePage = DIGETUSAGEPAGE(pPidReport->rgPidUsage->dwUsage);

        hres = HidP_GetSpecificValueCaps
               (
               pPidReport->HidP_Type,
               UsagePage,
               uLinkCollection,
               Usage,
               &ValCaps,
               &cAValCaps,
               this->ppd 
               );

        // If the report has no values, only buttons
        if(hres == HIDP_STATUS_USAGE_NOT_FOUND )
        {
            // Guarded Laziness 
            CAssertF(cbX(HIDP_VALUE_CAPS) == cbX(HIDP_BUTTON_CAPS) ); 
            CAssertF(FIELD_OFFSET(HIDP_VALUE_CAPS, ReportID) == FIELD_OFFSET(HIDP_BUTTON_CAPS, ReportID) );

            hres = HidP_GetSpecificButtonCaps
                   (
                   pPidReport->HidP_Type,
                   UsagePage,
                   uLinkCollection,
                   0x0,
                   (PHIDP_BUTTON_CAPS)&ValCaps,
                   &cAValCaps,
                   this->ppd 
                   );
        }

        if( SUCCEEDED(hres ) || ( hres == HIDP_STATUS_BUFFER_TOO_SMALL) )
        {
            (*pReportId) = ValCaps.ReportID;
            hres = S_OK;
        } else
        {
            SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("%s: FAIL HidP_GetValCaps for CollectionId%d (%x %x:%s) "),
                            s_tszProc, uLinkCollection, UsagePage, Usage, PIDUSAGETXT(UsagePage,Usage) );
        }
    }

    ExitOleProc();

    return hres;
}
/*****************************************************************************
 *
 *      PID_GetCollectionIndex
 *
 *          Obtain the collection index for collection usage page & usage.
 *
 *
 *          The external joystick number being addressed.
 *
 *  dwEffect
 *
 *          The effect to be queried.
 *
 *  pdwStatus
 *
 *          Receives the effect status in the form of zero
 *          or more DIEGES_* flags.
 *
 *  Returns:
 *          Collection Index ( 0 .. NumberLinkCollectionNodes -1 ) on success
 *          0x0 on failure
 *
 *****************************************************************************/
STDMETHODIMP
    PID_GetLinkCollectionIndex
    (
    IDirectInputEffectDriver *ped,
    USAGE UsagePage, 
    USAGE Collection,
    USHORT Parent,
    PUSHORT puLinkCollection )
{
    CPidDrv *this = (CPidDrv *)ped;
    USHORT indx;
    HRESULT hres;
    PHIDP_LINK_COLLECTION_NODE  pLinkCollection;

    EnterProcI(PID_GetLinkCollectionIndex, (_"xxxxx", this, UsagePage, Collection, Parent, puLinkCollection));
    hres = DIERR_PID_USAGENOTFOUND;

    *puLinkCollection = 0x0;
    for(indx = 0x0, pLinkCollection = this->pLinkCollection; 
       indx < this->caps.NumberLinkCollectionNodes; 
       indx++, pLinkCollection++ )
    {

        if( pLinkCollection->LinkUsagePage == UsagePage && 
            pLinkCollection->LinkUsage     == Collection )
        {
            if( Parent && Parent != pLinkCollection->Parent )
            {
                continue;
            }
            *puLinkCollection = indx;
            hres = S_OK;
            break;
        }
    }

    if( FAILED(hres) )
    {
        SquirtSqflPtszV(sqfl | sqflVerbose,
                        TEXT("%s: FAIL No LinkCollection for (%x %x:%s) "),
                        s_tszProc, UsagePage, Collection, PIDUSAGETXT(UsagePage,Collection) );
    }else
    {
        SquirtSqflPtszV(sqfl | sqflVerbose,
                        TEXT("%s: LinkCollection for (%x %x:%s)=%x "),
                        s_tszProc, UsagePage, Collection, PIDUSAGETXT(UsagePage,Collection), *puLinkCollection );
    }

    ExitBenignOleProc();
    return (hres) ;
}


//Helper fn to tell us when we're dealing w/ an "absolute" usage, since they require special handling
BOOL PID_IsUsageAbsoluteLike
    (
	IDirectInputEffectDriver *ped,
    USHORT			Usage
    )
{
	//"Absolute-like" usages need special handling, 
	//since we can't simply translate data into logical units by scaling
	//but need to calculate exponent, etc.
	//and then use special procedure to set the values
	//"Absolute" usages are all time usages as well as trigger button usages
	if ((Usage == HID_USAGE_PID_DURATION) || (Usage ==HID_USAGE_PID_SAMPLE_PERIOD ) ||
		(Usage == HID_USAGE_PID_TRIGGER_REPEAT_INTERVAL) || (Usage == HID_USAGE_PID_START_DELAY) ||
		(Usage == HID_USAGE_PID_ATTACK_TIME ) ||(Usage == HID_USAGE_PID_FADE_TIME) || 
		(Usage == HID_USAGE_PID_PERIOD) || (Usage == HID_USAGE_PID_TRIGGER_BUTTON))
	{
		return TRUE;
	}

	return FALSE;
}


//Helper fn to tell us when we're dealing w/ a magnitude that can be both positive and negative, 
//since we have to scale those differently
BOOL PID_IsUsagePositiveNegative
    (
	IDirectInputEffectDriver *ped,
    USHORT			Usage,
	USHORT			LinkCollection
    )
{
	BOOL isPosNeg = FALSE;
	//All the usages corresponding to the structures given by LONGs can be positive or negative.
	//Exception is the direction / angle, which should already be scaled into the range 0 - 360 * DI_DEGREES, 
	//so should be treated as only positive.
	//Another exception is DICONDITION.lDeadband, which is defined as a LONG, but our headers
	//say it can only be in the range 0 to DI_FFNOMINALMAX.
	if ((Usage == HID_USAGE_PID_CP_OFFSET) ||
		(Usage == HID_USAGE_PID_POSITIVE_COEFFICIENT) || (Usage == HID_USAGE_PID_NEGATIVE_COEFFICIENT) ||
		(Usage == HID_USAGE_PID_RAMP_START) ||(Usage == HID_USAGE_PID_RAMP_END) || 
		(Usage == HID_USAGE_PID_OFFSET))
	{
		isPosNeg = TRUE;
	}

	//Magnitude of the constant force and the magnitude of the periodic force are defined to be the same thing,
	//but only the constant force magnitude can be both positive and negative.
	//To distinguish them, need to look at the collection. 
	//Get constant force's collection and compare.
	if (Usage == HID_USAGE_PID_MAGNITUDE)
	{
		USHORT ConstCollection = 0x0;
		PID_GetLinkCollectionIndex(ped, g_Constant.UsagePage, g_Constant.Collection, 0x0, &ConstCollection);
		if (LinkCollection == ConstCollection)
		{
			isPosNeg = TRUE;
		}
	}

	return isPosNeg;
}


STDMETHODIMP
    PID_PackValue
    (
    IDirectInputEffectDriver *ped,
    PPIDREPORT  pPidReport,
    USHORT      LinkCollection,
    PVOID       pvData,
    UINT        cbData,
    PCHAR       pReport,
    ULONG       cbReport
    )
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT     hres;
    PPIDUSAGE   pPidUsage;
    UINT        indx;

    EnterProcI( PID_PackValue, (_"xxxxxxx", ped, pPidReport, LinkCollection, pvData, cbData, pReport, cbReport));

    hres = S_OK;
    // Loop over all data values in the PID Report
    for(indx = 0x0, pPidUsage = pPidReport->rgPidUsage; 
       indx < pPidReport->cAPidUsage;
       indx++, pPidUsage++ )
    {
        // Make sure the offsets are valid
        if( pPidUsage->DataOffset < cbData )
        {
            LONG        lValue;
            NTSTATUS    ntStat;
            USHORT      Usage     = DIGETUSAGE(pPidUsage->dwUsage);
            USHORT      UsagePage = DIGETUSAGEPAGE(pPidUsage->dwUsage);

		    lValue = *((LONG*)((UCHAR*)pvData+pPidUsage->DataOffset));

			ntStat = HidP_SetScaledUsageValue 
					 (
					 pPidReport->HidP_Type,
					 UsagePage,
					 LinkCollection,
					 Usage,
					 lValue,
					 this->ppd,
					 pReport,
					 cbReport
					 );

			if( FAILED(ntStat) )
			{
				// HidP_SetScaledUsageValue FAILED

				SquirtSqflPtszV(sqfl | sqflBenign,
							TEXT("%s: FAIL HidP_SetScaledUsageValue:0x%x for(%x,%x,%x:%s)=0x%x "),
							s_tszProc, ntStat, 
							LinkCollection, UsagePage, Usage,
							PIDUSAGETXT(UsagePage,Usage), 
							lValue );

				// Try to set the unscaled value to get something that might make sense
				if( ntStat != HIDP_STATUS_USAGE_NOT_FOUND )
				{
					lValue = -1;
					// The range could be messed up. 
					ntStat = HidP_SetUsageValue 
							 (
							 pPidReport->HidP_Type,
							 UsagePage,
							 LinkCollection,
							 Usage,
							 lValue,
							 this->ppd,
							 pReport,
							 cbReport
							 );
					if(FAILED(ntStat) )
					{
					SquirtSqflPtszV(sqfl | sqflBenign,
								TEXT("%s: FAIL HidP_SetUsageValue:0x%x for(%x,%x,%x:%s)=0x%x "),
								s_tszProc, ntStat, 
								LinkCollection, UsagePage, Usage,
								PIDUSAGETXT(UsagePage,Usage), 
								lValue );
					}
				}
			
			} else
			{
				SquirtSqflPtszV(sqfl | sqflVerbose,
							TEXT("%s: HidP_SetScaledUsageValue:0x%x for(%x,%x,%x:%s)=0x%x "),
							s_tszProc, ntStat, 
							LinkCollection, UsagePage, Usage,
							PIDUSAGETXT(UsagePage,Usage), 
							lValue );
			}
		} else
		{
            //SquirtSqflPtszV(sqfl | sqflBenign,
            //                TEXT("%s: FAIL Invalid Offset(%d), max(%d) "),
            //                s_tszProc, pPidUsage->DataOffset, cbData );
		}
    }
    ExitOleProc();
    return hres;
}


//blocking version -- used for creating a new effect or destroying an effect, and for custom forces
STDMETHODIMP 
    PID_SendReportBl
    (
    IDirectInputEffectDriver *ped,
    PUCHAR  pReport,
    UINT    cbReport,
    HIDP_REPORT_TYPE    HidP_Type
    )
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres;

    EnterProcI( PID_SendReportBl, (_"xxxx", ped, pReport, cbReport, HidP_Type));

    hres = S_OK;
    if( HidP_Type == HidP_Output )
    {
        BOOL frc;
        UINT cbWritten;

        frc = WriteFile (this->hdev,
                         pReport,
                         cbReport,
                         &cbWritten,
                         NULL);

        if( frc != TRUE || cbWritten != cbReport )
        {
            LONG lrc = GetLastError();
            hres = hresLe(lrc);
            SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("%s: FAIL WriteFile():%d (cbWritten(0x%x) cbReport(0x%x) Le(0x%x)"),
                            s_tszProc, frc, cbWritten, cbReport, lrc );

        }
    } else if( HidP_Type == HidP_Feature )
    {
        hres = HidD_SetFeature
               (this->hdev,
                pReport,
                cbReport
               );
        if(FAILED(hres) )
        {
            SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("%s: FAIL SendD_Feature() hres:0x%x"),
                            s_tszProc, hres );

        }

    } else
    {
        hres = DIERR_PID_USAGENOTFOUND;
    }
    ExitOleProc();
    return hres;
}


STDMETHODIMP 
    PID_SendReport
    (
    IDirectInputEffectDriver *ped,
    PUCHAR  pReport,
    UINT    cbReport,
    HIDP_REPORT_TYPE    HidP_Type,
	BOOL	bBlocking,
	UINT	blockNr,
	UINT	totalBlocks
    )
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres = S_OK;

    EnterProcI( PID_SendReport, (_"xxxx", ped, pReport, cbReport, HidP_Type));

	if (bBlocking == TRUE)
	{
		hres = PID_SendReportBl(ped, pReport, cbReport, HidP_Type);
	}
	else
	{
		AssertF(this->hThread != 0x0);
		AssertF(this->hWrite != 0x0);
		AssertF(this->hWriteComplete != 0x0);

		//blockNr is 0-based.
		AssertF(totalBlocks > 0);
		AssertF(blockNr < totalBlocks);

		if( HidP_Type == HidP_Output )
		{
			//WaitForMultipleObjects() till the completion event becomes set.
			//we save each report into the appropriate place in the array.
			//when we get all the reports, we set the event to signal to the other thread to write.
			// Windows bug 627797 -- do not use INFINITE wait, so that we don't hang the app
			//if smth goes wrong w/ the previous write, but instead use the blocking version.
			DWORD dwWait = WaitForMultipleObjects(1, &this->hWriteComplete, FALSE, 1000);
			if (dwWait == WAIT_OBJECT_0)
			{
				AssertF(this->dwWriteAttempt == 0);
				//save the report data
				ZeroMemory(this->pWriteReport[blockNr], this->cbWriteReport[blockNr]);
				memcpy(this->pWriteReport[blockNr], pReport, cbReport);
				this->cbWriteReport[blockNr] = (USHORT)cbReport;
				if (blockNr == totalBlocks-1)
				{
					this->totalBlocks = totalBlocks;
					this->blockNr = 0;
					ResetEvent(this->hWriteComplete);
					SetEvent(this->hWrite);
				}
			}
			else
			{
				//The wait interval has expired, or an error has occured
				RPF( TEXT("Waiting for the write completion event ended without the event being signaled, dwWait = %u"), dwWait);
				//call the blocking version
				hres = PID_SendReportBl(ped, pReport, cbReport, HidP_Type);
			}

		} else if( HidP_Type == HidP_Feature )
		{
			hres = HidD_SetFeature
					 (this->hdev,
					pReport,
					cbReport
					  );
			if(FAILED(hres) )
			{
				SquirtSqflPtszV(sqfl | sqflError,
								TEXT("%s: FAIL SendD_Feature() hres:0x%x"),
								s_tszProc, hres );

			}

		} else
		{
			hres = DIERR_PID_USAGENOTFOUND;
		}
	}

    ExitOleProc();
    return hres;
}

STDMETHODIMP
    PID_ParseReport
    (
    IDirectInputEffectDriver *ped,
    PPIDREPORT  pPidReport,
    USHORT      LinkCollection,
    PVOID       pvData,
    UINT        cbData,
    PCHAR       pReport,
    ULONG       cbReport
    )
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres;
    PPIDUSAGE   pPidUsage;
    UINT        indx;
    EnterProcI( PID_ParseReport, (_"xxxxxxx", ped, pPidReport, pvData, cbData, pReport, cbReport));

    hres = S_OK;
    // Loop over all data values in the PID Report
    for(indx = 0x0, pPidUsage = pPidReport->rgPidUsage; 
       indx < pPidReport->cAPidUsage;
       indx++, pPidUsage++ )
    {
        // Make sure the offsets are valid
        if( pPidUsage->DataOffset < cbData )
        {
            LONG        lValue;
            NTSTATUS    ntStat;
            USHORT      Usage     = DIGETUSAGE(pPidUsage->dwUsage);
            USHORT      UsagePage = DIGETUSAGEPAGE(pPidUsage->dwUsage);

            ntStat = HidP_GetScaledUsageValue 
                     (
                     pPidReport->HidP_Type,
                     UsagePage,
                     LinkCollection,
                     Usage,
                     &lValue,
                     this->ppd,
                     pReport,
                     cbReport
                     );

            if(SUCCEEDED(ntStat))
            {
                *((LONG*)((UCHAR*)pvData+pPidUsage->DataOffset)) = lValue;
            } else
            {
                hres |= E_NOTIMPL;
                                SquirtSqflPtszV(sqfl | sqflBenign,
                                                TEXT("%s: FAIL HidP_GetScaledUsageValue:0x%x for(%x,%x,%x:%s)"),
                                                s_tszProc, ntStat, 
                                                LinkCollection, UsagePage, Usage,
                                                PIDUSAGETXT(UsagePage,Usage) );
            }
        } else
        {

            SquirtSqflPtszV(sqfl | sqflBenign,
                            TEXT("%s: FAIL Invalid Offset(%d), max(%d) "),
                            s_tszProc, pPidUsage->DataOffset, cbData );
        }
    }
    ExitBenignOleProc();
    return hres;
}



STDMETHODIMP 
    PID_GetReport
    (
    IDirectInputEffectDriver *ped,
    PPIDREPORT  pPidReport,
    USHORT      LinkCollection,
    PVOID       pReport,
    UINT        cbReport
    )
{
    HRESULT hres = S_OK;
    CPidDrv *this = (CPidDrv *)ped;
    UCHAR       ReportId;
    EnterProcI( PID_GetReport, (_"xxxxx", ped, pPidReport, LinkCollection, pReport, cbReport));

    if( SUCCEEDED(hres) )
    {
        hres = PID_GetReportId(ped, pPidReport, LinkCollection, &ReportId);

        if(SUCCEEDED(hres) )
        {
            AssertF(pPidReport->HidP_Type == HidP_Feature);

            if(SUCCEEDED(hres) )
            {
                ZeroBuf(pReport, cbReport);

                /*
                 *  The Win9x headers do not yet have use HidP_InitializeReportForID 
                 *  use MAXULONG_PTR to tell the header sets apart so that we can still build
                 */

#ifdef WINNT    
                /*hres*=*/HidP_InitializeReportForID 
                    (
                    pPidReport->HidP_Type,  //ReportType,
                    ReportId,               //ReportID,
                    this->ppd,              //PreparsedData
                    pReport,                //Report
                    cbReport                //ReportLength
                    );
#else
                (*(PUCHAR)pReport) = ReportId;
                hres = S_OK;
#endif
                if( FAILED(hres) )
                {
                    SquirtSqflPtszV(sqfl | sqflError,
                                    TEXT("%s: FAIL HidP_InitializeReportForId:0x%x for Type(%d) CollectionId%d ReportID%d "),
                                    s_tszProc, hres, pPidReport->HidP_Type, LinkCollection, ReportId );
                }

                if(    SUCCEEDED(hres) 
                       && pPidReport->HidP_Type == HidP_Feature )
                {
                    BOOL frc;
                    frc = HidD_GetFeature 
                          (
                          this->hdev,     // HidDeviceObject,
                          pReport,        // ReportBuffer,
                          cbReport       //ReportBufferLength
                          );

                    if( frc != TRUE )
                    {
                        hres = DIERR_PID_USAGENOTFOUND;
                    }
                }
            }
        }
    }
    ExitOleProc();
    return(hres);
}


/*****************************************************************************
 *
 *      PID_ComputeScalingFactors
 *
 *      Dinput units for various parameters are well defined. The device may choose
 *      to implement the units that it is most comfortable with. This routine
 *      computes scaling factors that are to be used when scaling DINPUT parameters
 *      before they are send to the device. 
 *
 *  IDirectInputEffectDriver | ped |
 *
 *          The effect driver interface
 *
 *  PPIDREPORT | pPidReport |
 *
 *          Address of PIDREPORT structure
 *
 *  USHORT | uLinkCollection |
 *  
 *          LinkCollection ID
 *
 *  IN OUT PVOID | pvData | 
 *
 *          Parameter data. On entry value is the nominal scale used by Dinput.
 *          For example: Angles: DI_DEGREES, DI_FFNOMINALMAX, DI_SECONDS 
 *
 *  IN UINT | cbData | 
 *
 *          Number of valid DWORDS in pvData
 *
 *  Returns:
 *      
 *  HRESULT 
 *          Error code
 *          E_NOTIMPL:						Did not find any usage / usage Page 
 *          DIERR_PID_INVALIDSCALING:		Unsupported device scaling parameters.
 *          S_OK:							Scaling value for at least one parameter was found
 *
 *****************************************************************************/

STDMETHODIMP
    PID_ComputeScalingFactors
    (
    IDirectInputEffectDriver *ped,
    PPIDREPORT  pPidReport,
    USHORT      LinkCollection,
    PVOID       pvData,
    UINT        cbData,
	PVOID		pvOffset,
	UINT		cbOffset
    )
{
    HRESULT hres = E_NOTIMPL;
    CPidDrv *this = (CPidDrv *)ped;
    UINT indx;
    PPIDUSAGE   pPidUsage;

    EnterProcI( PID_ComputeScalingFactors, (_"xxxxxxx", ped, pPidReport, LinkCollection, pvData, cbData, pvOffset, cbOffset));

    // Loop over all data values in the PID Report
    for(indx = 0x0, pPidUsage = pPidReport->rgPidUsage; 
       indx < pPidReport->cAPidUsage;
       indx++, pPidUsage++ )
    {
        // Make sure the offsets are valid
        if (( pPidUsage->DataOffset < cbData ) && (pPidUsage->DataOffset < cbOffset))
        {
            NTSTATUS    ntStat;
            HIDP_VALUE_CAPS ValCaps;
            USHORT      cAValCaps = 0x1;

            USHORT      Usage     = DIGETUSAGE(pPidUsage->dwUsage);
            USHORT      UsagePage = DIGETUSAGEPAGE(pPidUsage->dwUsage);
            PDWORD      pdwValue;
			PDWORD      pdwOffset;    
            DWORD       dwScale = 0x1;
			DWORD		dwOffset = 0x0;

            pdwValue = ((DWORD*)((UCHAR*)pvData+pPidUsage->DataOffset));
			pdwOffset = ((DWORD*)((UCHAR*)pvOffset+pPidUsage->DataOffset));

            ntStat = HidP_GetSpecificValueCaps 
                     (
                     pPidReport->HidP_Type,
                     UsagePage,
                     LinkCollection,
                     Usage,
                     &ValCaps,
                     &cAValCaps,
                     this->ppd
                     );

            if(SUCCEEDED(ntStat))
            {
		//some units are "absolute" and thus don't need to be scaled to the limits.
				//for them, we just find out the correct units
		if (PID_IsUsageAbsoluteLike(ped, Usage))
		{
			if( ! ValCaps.Units )
			{
				SquirtSqflPtszV(sqfl | sqflVerbose,
						TEXT("%s:No Units(%x,%x %x:%s) Max:%d Scale:%d "),
						s_tszProc, LinkCollection, UsagePage, Usage, PIDUSAGETXT(UsagePage,Usage),
						ValCaps.PhysicalMax, dwScale );
				// No units, scaling exponent is default = 1
				hres = S_FALSE;
			} else
			{
				LONG UnitExp;
				UnitExp = (LONG)ValCaps.UnitsExp ;

				if( UnitExp > 0x0 )
				{
					RPF(TEXT("Driver does not support Units (%x,%x %x:%s) Exp:%d Max:%d"),
						LinkCollection, UsagePage, Usage, PIDUSAGETXT(UsagePage,Usage), ValCaps.UnitsExp, ValCaps.PhysicalMax ) ;
					hres = DIERR_PID_INVALIDSCALING;
				}else
				{
					hres = S_OK;
				}

				if(SUCCEEDED(hres) )
				{
					dwScale = (*pdwValue);
					for(; UnitExp; UnitExp++ )
				{
					dwScale /= 10;
				}

				if( dwScale == 0 )
				{
					RPF(TEXT("Driver does not support Units (%x,%x %x:%s) Exp:%d Max:%d"),
						LinkCollection, UsagePage, Usage, PIDUSAGETXT(UsagePage,Usage), ValCaps.UnitsExp, ValCaps.PhysicalMax ) ;
					dwScale = 0x1;
					hres = DIERR_PID_INVALIDSCALING;
				}else
				{ 
					hres = S_OK;
				}
			}
			SquirtSqflPtszV(sqfl | sqflVerbose,
                                    TEXT("%s: (%x,%x %x:%s) Exp%d Max:%d Scale:%d "),
                                    s_tszProc, LinkCollection, UsagePage, Usage, PIDUSAGETXT(UsagePage,Usage),
                                    ValCaps.UnitsExp, ValCaps.PhysicalMax, (*pdwValue) );
			}
		}
		else
		{
			//for everything else, get Physical and /or Logical  Min/ Max
			//From PID spec, doesn't have to have a Physical / Logical Min, but does have to have either Physical or Logical Max
			if ((!ValCaps.PhysicalMax) && (!ValCaps.LogicalMax))
			{
				RPF(TEXT("Driver does not have either Physical Max or Logical Max for (%x,%x %x:%s)"),
					LinkCollection, UsagePage, Usage, PIDUSAGETXT(UsagePage,Usage)) ;
				hres = DIERR_PID_INVALIDSCALING;
			}
			else
			{
				//Compute the scaling value from either Physical or Logical Min/ Max and store it
				int Scale = 0;
				int Min = 0;
				int Max = 0;
				if (ValCaps.PhysicalMax)
				{
					Max = ValCaps.PhysicalMax;
					if (ValCaps.PhysicalMin)
					{
						Min = ValCaps.PhysicalMin;
					}
				}
				else 
				{
					Max = ValCaps.LogicalMax;
					if (ValCaps.LogicalMin)
					{
						Min = ValCaps.LogicalMin;
					}
				}
#ifdef DEBUG
				//if Min/max are not in correct order, print a message so that we know if there are any problems w/ the forces
				if (Min >= Max)
				{
					RPF(TEXT("Maximum of the device's range is %d, not bigger than minimum %d"), Max, Min);
				}
#endif
				//certain magnitudes can be both positive and negative -- for those, we need to know the device's offset
				if (PID_IsUsagePositiveNegative(ped, Usage, LinkCollection))
				{
					
					Scale = (Max - Min)/2; 
					dwOffset = (Max + Min)/2; 

				}
				//other magnitudes can only be positive
				else
				{
					Scale = Max - Min;
					dwOffset = Min;
				}
				//for angular usages, multiply by DI_FFNOMINALMAX and divide by 360 * DI_DEGREES
				//we are doing this since later we will have no way of knowing that the values represent angles,
				//and will thus divide all the values by DI_FFNOMINALMAX
				if (*pdwValue == 360 * DI_DEGREES)
				{
					dwScale = MulDiv(Scale, DI_FFNOMINALMAX, (360 * DI_DEGREES));
				}
				else
				{
					dwScale = Scale;
				}
				hres = S_OK;
			}
		}

            } else
            {
                // HidP_SetScaledUsageValue FAILED
                SquirtSqflPtszV(sqfl | sqflBenign,
                                TEXT("%s: FAIL HidP_GetSpecificValueCaps:0x%x for(%x,%x,%x:%s)=0x%x "),
                                s_tszProc, ntStat, 
                                LinkCollection, UsagePage, Usage,
                                PIDUSAGETXT(UsagePage,Usage), 
                                dwScale );
            }

            (*pdwValue) = dwScale;
			(*pdwOffset) = dwOffset;
        } else
        {
            //SquirtSqflPtszV(sqfl | sqflVerbose,
            //                TEXT("%s: FAIL Invalid Offset(%d), max(%d) "),
            //                s_tszProc, pPidUsage->DataOffset, cbData );
        }
    }

    ExitOleProc();
    return hres;
}


/*****************************************************************************
 *
 *      PID_ApplyScalingFactors
 *
 *      Dinput units for various parameters are well defined. The device may choose
 *      to implement the units that it is most comfortable with. This routine
 *      apply scaling factors that are to be used when scaling DINPUT parameters
 *      before they are send to the device. 
 *
 *  IDirectInputEffectDriver | ped |
 *
 *      The effect driver interface
 *
 *  PPIDREPORT | pPidReport |
 *
 *      Address of PIDREPORT structure
 *
 *  IN PVOID | pvScale |
 *  
 *      Scaling values
 *
 *  IN UINT | cbScale | 
 *
 *      Number of scaling values.
 *
 *  IN OUT PVOID | pvData | 
 *
 *      Array of data values.
 *
 *  IN UINT | cbData | 
 *
 *      Number of data values. 
 *
 *  Returns:
 *      
 *  HRESULT 
 *          Error code
 *          E_NOTIMPL:						Did not find any usage / usage Page 
 *          DIERR_PID_INVALIDSCALING:				Unsupported device scaling parameters.
 *          S_OK:						Scaling value for at least one parameter was found
 *
 *****************************************************************************/


STDMETHODIMP
    PID_ApplyScalingFactors
    (
    IDirectInputEffectDriver *ped,
    PPIDREPORT  pPidReport,
    PVOID       pvScale,
    UINT        cbScale,
	PVOID		pvOffset,
	UINT		cbOffset,
    PVOID       pvData,
    UINT        cbData
    )
{
    HRESULT hres = S_OK;
    CPidDrv *this = (CPidDrv *)ped;
    UINT indx;
    PPIDUSAGE   pPidUsage;
    EnterProcI( PID_ApplyScalingFactors, (_"xxxxxxxx", ped, pPidReport, pvScale, cbScale, pvOffset, cbOffset, pvData, cbData));
    // Loop over all data values in the PID Report
    for(indx = 0x0, pPidUsage = pPidReport->rgPidUsage; 
       indx < pPidReport->cAPidUsage;
       indx++, pPidUsage++ )
    {
        // Make sure we the offsets are valid
        if( (pPidUsage->DataOffset < cbData) &&
            (pPidUsage->DataOffset < cbScale) && ((pPidUsage->DataOffset < cbOffset) ))
        {
			PUINT      pValue;        
			PUINT      pScale;
			PUINT	   pOffset;

			pValue = ((PUINT)((UCHAR*)pvData    +pPidUsage->DataOffset));
			pScale = ((PUINT)((UCHAR*)pvScale   +pPidUsage->DataOffset));
			pOffset = ((PUINT)((UCHAR*)pvOffset   +pPidUsage->DataOffset));

			//"absolute"-like usages need special handling, because they don't need to be scaled to the max device values
			if (PID_IsUsageAbsoluteLike(ped, DIGETUSAGE(pPidUsage->dwUsage)))
			{
				if( (*pScale) > 0x1 )
				{
					(*pValue) /= (*pScale) ;    
				}
			}
			//for everything else, do a calculation based on Logical or Physical Min/ Max
			else
			{
				(int)(*pValue) = MulDiv((*pScale), (*pValue), DI_FFNOMINALMAX) + (*pOffset);
			}
        } else
        {
            //SquirtSqflPtszV(sqfl | sqflBenign,
            //                TEXT("%s: FAIL Invalid Offset(%d), max(%d) "),
            //                s_tszProc, pPidUsage->DataOffset, cbData );
        }
    }

    ExitOleProc();
    return hres;    
}