/*****************************************************************************
 *
 *  EffDrv.c
 *
 *  Copyright (c) 1999 Microsoft Corporation.  All Rights Reserved.
 *
 *  Abstract:
 *
 *      Effect driver.
 *
 *      WARNING!  Since the effect driver is marked ThreadingModel="Both",
 *      all methods must be thread-safe.
 *
 *****************************************************************************/
#include "PIDpr.h"

#define sqfl    (sqflEffDrv)
/*****************************************************************************
 *
 *      CPidDrv - Effect driver
 *
 *****************************************************************************/

/*****************************************************************************
 *
 *      PID_AddRef
 *
 *      Increment our object reference count (thread-safely) and return
 *      the new reference count.
 *
 *****************************************************************************/

STDMETHODIMP_(ULONG)
PID_AddRef(IDirectInputEffectDriver *ped)
{
    CPidDrv *this = (CPidDrv *)ped;

    InterlockedIncrement((LPLONG)&this->cRef);
    

    return this->cRef;
}


/*****************************************************************************
 *
 *      PID_Release
 *
 *      Decrement our object reference count (thread-safely) and
 *      destroy ourselves if there are no more references.
 *
 *****************************************************************************/

STDMETHODIMP_(ULONG)
PID_Release(IDirectInputEffectDriver *ped)
{
    ULONG ulRc;
    CPidDrv *this = (CPidDrv *)ped;

    if(InterlockedDecrement((LPLONG)&this->cRef) == 0)
    {
		DllRelease(); 
        PID_Finalize(ped);
        LocalFree(this);
        ulRc = 0;
    } else
    {
        ulRc = this->cRef;
    }

    return ulRc;
}

/*****************************************************************************
 *
 *      PID_QueryInterface
 *
 *      Our QI is very simple because we support no interfaces beyond
 *      ourselves.
 *
 *      riid - Interface being requested
 *      ppvOut - receives new interface (if successful)
 *
 *****************************************************************************/

STDMETHODIMP
    PID_QueryInterface(IDirectInputEffectDriver *ped, REFIID riid, LPVOID *ppvOut)
{
    HRESULT hres;

    if(IsEqualIID(riid, &IID_IUnknown) ||
       IsEqualIID(riid, &IID_IDirectInputEffectDriver))
    {
        PID_AddRef(ped);
        *ppvOut = ped;
        hres = S_OK;
    } else
    {
        *ppvOut = 0;
        hres = E_NOINTERFACE;
    }
    return hres;
}

/*****************************************************************************
 *
 *      PID_DeviceID
 *
 *          DirectInput uses this method to inform us of
 *          the identity of the device.
 *
 *          For example, if a device driver is passed
 *          dwExternalID = 2 and dwInternalID = 1,
 *          then this means the interface will be used to
 *          communicate with joystick ID number 2, which
 *          corresonds to physical unit 1 in VJOYD.
 *
 *  dwDirectInputVersion
 *
 *          The version of DirectInput that loaded the
 *          effect driver.
 *
 *  dwExternalID
 *
 *          The joystick ID number being used.
 *          The Windows joystick subsystem allocates external IDs.
 *
 *  fBegin
 *
 *          Nonzero if access to the device is beginning.
 *          Zero if the access to the device is ending.
 *
 *  dwInternalID
 *
 *          Internal joystick id.  The device driver manages
 *          internal IDs.
 *
 *  lpReserved
 *
 *          Reserved for future use (HID).
 *
 *  Returns:
 *
 *          S_OK if the operation completed successfully.
 *
 *          Any DIERR_* error code may be returned.
 *
 *          Private driver-specific error codes in the range
 *          DIERR_DRIVERFIRST through DIERR_DRIVERLAST
 *          may be returned.
 *
 *****************************************************************************/

STDMETHODIMP
    PID_DeviceID(IDirectInputEffectDriver *ped,
                     DWORD dwDirectInputVersion,
                     DWORD dwExternalID, DWORD fBegin,
                     DWORD dwInternalID, LPVOID pvReserved)
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres = S_OK;
    LPDIHIDFFINITINFO   init = (DIHIDFFINITINFO*)pvReserved;

    EnterProcI(PID_DeviceID, (_"xxxxxx", ped, dwDirectInputVersion, dwExternalID, fBegin, dwInternalID, pvReserved));
    
    DllEnterCrit();

    if(  init == NULL )
    {
        SquirtSqflPtszV(sqfl | sqflError,
                        TEXT("%s: FAIL init == NULL "),
                        s_tszProc );
        hres = DIERR_PID_NOTINITIALIZED;
    }
    
    if(    SUCCEEDED(hres) 
        && (init->dwSize < cbX(*init) ) )
    {
        
        SquirtSqflPtszV(sqfl | sqflError,
                        TEXT("%s: FAIL init->dwSize(%d) expecting(%d) "),
                        s_tszProc, init->dwSize, cbX(*init) );
        hres = DIERR_PID_NOTINITIALIZED;
    
    }

    if( SUCCEEDED(hres) )
    {

#ifdef UNICODE
        lstrcpy(this->tszDeviceInterface, init->pwszDeviceInterface );
#else // !UNICODE
        {
            TCHAR   tszDeviceInterface[MAX_DEVICEINTERFACE];
            UToA(tszDeviceInterface, MAX_DEVICEINTERFACE, init->pwszDeviceInterface);

            lstrcpy(this->tszDeviceInterface, tszDeviceInterface);
        }
#endif

        if( FAILED(hres) )
        {
            SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("%s: FAIL:0x%x Invalid string(%s) "),
                            s_tszProc, hres, init->pwszDeviceInterface );
        }
        
        if( SUCCEEDED(hres) && IsEqualGUID(&init->GuidInstance, &GUID_NULL ) )
        {
            hres = DIERR_PID_NOTINITIALIZED;

            SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("%s: FAIL:init->GuidInstance is NULL "),
                            s_tszProc );
        }
        
        this->GuidInstance = init->GuidInstance;

        /* Record the DI version number */
        this->dwDirectInputVersion = dwDirectInputVersion;

        /* Keep the external ID as a cookie for access to the driver functionality */
        this->dwID = dwExternalID;
    }

    if( SUCCEEDED(hres) )
    {
        /* Ping the device to make sure it is fine */
        hres = PID_Init(ped);
    }

    /*
     *  Remember the unit number because that tells us which of
     *  our devices we are talking to.  The DirectInput external
     *  joystick number is useless to us.  (We don't care if we
     *  are joystick 1 or joystick 2.)
     *
     *  Note that although our other methods are given an external
     *  joystick Id, we don't use it.  Instead, we use the unit
     *  number that we were given here.
     *
     *  Our hardware supports only MAX_UNITS units.
     */

     DllLeaveCrit();

     ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *      PID_GetVersions
 *
 *          Obtain version information about the force feedback
 *          hardware and driver.
 *
 *  pvers
 *
 *          A structure which should be filled in with version information
 *          describing the hardware, firmware, and driver.
 *
 *          DirectInput will set the dwSize field
 *          to sizeof(DIDRIVERVERSIONS) before calling this method.
 *
 *  Returns:
 *
 *          S_OK if the operation completed successfully.
 *
 *          E_NOTIMPL to indicate that DirectInput should retrieve
 *          version information from the VxD driver instead.
 *
 *          Any DIERR_* error code may be returned.
 *
 *          Private driver-specific error codes in the range
 *          DIERR_DRIVERFIRST through DIERR_DRIVERLAST
 *          may be returned.
 *
 *****************************************************************************/

STDMETHODIMP
    PID_GetVersions(IDirectInputEffectDriver *ped, LPDIDRIVERVERSIONS pvers)
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres;

    EnterProc(PID_GetVersions, (_"xx", ped, pvers));

    DllEnterCrit();

    if(pvers->dwSize >= sizeof(DIDRIVERVERSIONS))
    {
        /*
         *  Tell DirectInput how much of the structure we filled in.
         */
        pvers->dwSize = sizeof(DIDRIVERVERSIONS);

        /*
         *  In real life, we would detect the version of the hardware
         *  that is connected to unit number this->dwUnit.
         */
        pvers->dwFirmwareRevision = 0x0;
        pvers->dwHardwareRevision = this->attr.ProductID;
        pvers->dwFFDriverVersion =  PID_DRIVER_VERSION;
        hres = S_OK;
    } else
    {
        hres = E_INVALIDARG;
    }

    DllLeaveCrit();

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *      PID_Escape
 *
 *          DirectInput uses this method to communicate
 *          IDirectInputDevice2::Escape and
 *          IDirectInputEFfect::Escape methods to the driver.
 *
 *  dwId
 *
 *          The joystick ID number being used.
 *
 *  dwEffect
 *
 *          If the application invoked the
 *          IDirectInputEffect::Escape method, then
 *          dwEffect contains the handle (returned by
 *          mf IDirectInputEffectDriver::DownloadEffect)
 *          of the effect at which the command is directed.
 *
 *          If the application invoked the
 *          mf IDirectInputDevice2::Escape method, then
 *          dwEffect is zero.
 *
 *  pesc
 *
 *          Pointer to a DIEFFESCAPE structure which describes
 *          the command to be sent.  On success, the
 *          cbOutBuffer field contains the number
 *          of bytes of the output buffer actually used.
 *
 *          DirectInput has already validated that the
 *          lpvOutBuffer and lpvInBuffer and fields
 *          point to valid memory.
 *
 *  Returns:
 *
 *          S_OK if the operation completed successfully.
 *
 *          Any DIERR_* error code may be returned.
 *
 *          Private driver-specific error codes in the range
 *          DIERR_DRIVERFIRST through DIERR_DRIVERLAST
 *          may be returned.
 *
 *****************************************************************************/

STDMETHODIMP
    PID_Escape(IDirectInputEffectDriver *ped,
                   DWORD dwId, DWORD dwEffect, LPDIEFFESCAPE pesc)
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres;

    EnterProc(PID_Escape, (_"xxxx", ped, dwId, dwEffect, pesc));

    hres = E_NOTIMPL;
    
    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *      PID_GetForceFeedbackState
 *
 *          Retrieve the force feedback state for the device.
 *
 *  dwId
 *
 *          The external joystick number being addressed.
 *
 *  pds
 *
 *          Receives device state.
 *
 *          DirectInput will set the dwSize field
 *          to sizeof(DIDEVICESTATE) before calling this method.
 *
 *  Returns:
 *
 *          S_OK on success.
 *
 *          Any DIERR_* error code may be returned.
 *
 *          Private driver-specific error codes in the range
 *          DIERR_DRIVERFIRST through DIERR_DRIVERLAST
 *          may be returned.
 *
 *****************************************************************************/

STDMETHODIMP
    PID_GetForceFeedbackState(IDirectInputEffectDriver *ped,
                                  DWORD dwId, LPDIDEVICESTATE pds)
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres;
    USHORT  LinkCollection;

    EnterProcI(PID_GetFFState, (_"xxx", ped, dwId, pds));

    DllEnterCrit();

    hres = PID_GetLinkCollectionIndex(ped,g_PoolReport.UsagePage,g_PoolReport.Collection,0x0,&LinkCollection);
	if (SUCCEEDED(hres))
    {
        hres = PID_GetReport
				(ped, 
				 &g_PoolReport,
				 LinkCollection,
				 this->pReport[g_PoolReport.HidP_Type], 
				 this->cbReport[g_PoolReport.HidP_Type] 
				);

		if (SUCCEEDED(hres))
		{
			 if (FAILED(PID_ParseReport
					(
					ped,
					&g_PoolReport,
					LinkCollection,
					&this->ReportPool,
					cbX(this->ReportPool),
					this->pReport[g_PoolReport.HidP_Type],
					this->cbReport[g_PoolReport.HidP_Type]
					)))
			{
				SquirtSqflPtszV(sqfl | sqflError,
                        TEXT("%s: FAIL to parse report."),
                        s_tszProc);
			}
		}
		else
		{
			SquirtSqflPtszV(sqfl | sqflError,
                        TEXT("%s: FAIL to get report."),
                        s_tszProc);

		}
    }
	else
	{
		SquirtSqflPtszV(sqfl | sqflError,
                        TEXT("%s: FAIL to get Link Collection Index."),
                        s_tszProc);

	}
    
    if( ((PUNITSTATE)(g_pshmem + this->iUnitStateOffset))->cEfDownloaded !=  this->ReportPool.uRomETCount )
    {
        SquirtSqflPtszV(sqfl | sqflError,
                        TEXT("%s: PID driver downloaded %d effects, device claims it has %d"),
                        s_tszProc, ((PUNITSTATE)(g_pshmem + this->iUnitStateOffset))->cEfDownloaded, this->ReportPool.uRomETCount );

    }

    if(SUCCEEDED(hres))
    {
        /*
         *  Start out empty and then work our way up.
         */
        pds->dwState = this->dwState;

        /*
         *  If there are no effects, then DIGFFS_EMPTY.
         */
        // ISSUE-2001/03/29-timgill Should use this->ReportPool.uRomETCount == 0x0  
        if(((PUNITSTATE)(g_pshmem + this->iUnitStateOffset))->cEfDownloaded == 0x0 )
        {
            pds->dwState |= DIGFFS_EMPTY;
            
            // No effects playing and device is not paused
            if(!( pds->dwState & DIGFFS_PAUSED ) )
            {
                pds->dwState |= DIGFFS_STOPPED;
            }
        }
    
		//if everything has succeeded, this->ReportPool.uRamPoolSz shouldn't be 0. 
		if (this->ReportPool.uRamPoolSz != 0)
		{
			if( this->uDeviceManaged & PID_DEVICEMANAGED )
			{
				pds->dwLoad = 100 * ( this->dwUsedMem /  this->ReportPool.uRamPoolSz ); 
			}else
			{
				pds->dwLoad = 100 * ( ((PUNITSTATE)(g_pshmem + this->iUnitStateOffset))->cbAlloc / this->ReportPool.uRamPoolSz  ); 
			}
		}
		else
		{
			SquirtSqflPtszV(sqfl | sqflError,
                        TEXT("%s: this->ReportPool.uRamPoolSz = 0."),
                        s_tszProc);
			hres = E_FAIL;
		}
    }

    DllLeaveCrit();
    ExitOleProc();
    return hres;
}


/*****************************************************************************
 *
 *      PID_StartEffect
 *
 *          Begin playback of an effect.
 *
 *          If the effect is already playing, then it is restarted
 *          from the beginning.
 *
 *  @cwrap  LPDIRECTINPUTEFFECTDRIVER | lpEffectDriver
 *
 *  @parm   DWORD | dwId |
 *
 *          The external joystick number being addressed.
 *
 *  @parm   DWORD | dwEffect |
 *
 *          The effect to be played.
 *
 *  @parm   DWORD | dwMode |
 *
 *          How the effect is to affect other effects.
 *
 *          This parameter consists of zero or more
 *          DIES_* flags.  Note, however, that the driver
 *          will never receive the DIES_NODOWNLOAD flag;
 *          the DIES_NODOWNLOAD flag is managed by
 *          DirectInput and not the driver.
 *
 *  @parm   DWORD | dwCount |
 *
 *          Number of times the effect is to be played.
 *
 *  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.
 *
 *
 *****************************************************************************/

STDMETHODIMP
    PID_StartEffect(IDirectInputEffectDriver *ped, DWORD dwId, DWORD dwEffect,
                        DWORD dwMode, DWORD dwCount)
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres;

    EnterProc(PID_StartEffect, (_"xxxxx", ped, dwId, dwEffect, dwMode, dwCount));

    DllEnterCrit();

    hres = PID_EffectOperation
           (
           ped, 
           dwId, 
           dwEffect,
           dwMode | PID_DIES_START, 
           dwCount,
		   TRUE,
		   0,
		   1
           );

	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).
		//at this point, 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.
		PEFFECTSTATE pEffectState =  PeffectStateFromBlockIndex(this, dwEffect); 
		pEffectState->lEfState |= DIEGES_PLAYING;
	}

    
    DllLeaveCrit();
    return hres;

    ExitOleProc();
}

/*****************************************************************************
 *
 *      PID_StopEffect
 *
 *          Halt playback of an effect.
 *
 *  dwId
 *
 *          The external joystick number being addressed.
 *
 *  dwEffect
 *
 *          The effect to be stopped.
 *
 *  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.
 *
 *
 *****************************************************************************/

STDMETHODIMP
    PID_StopEffect(IDirectInputEffectDriver *ped, DWORD dwId, DWORD dwEffect)
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres;

    EnterProc(PID_StopEffect, (_"xxxx", ped, dwId, dwEffect));

    DllEnterCrit();

    hres = PID_EffectOperation
           (
           ped, 
           dwId, 
           dwEffect,
           PID_DIES_STOP, 
           0x0,
		   TRUE,
		   0,
		   1
           );

	if (SUCCEEDED(hres))
		{
			//set the status to ~(DIEGES_PLAYING).
			//we do this because of the following: if an app calls Stop(), 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).
			//at this point, 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.
			PEFFECTSTATE pEffectState =  PeffectStateFromBlockIndex(this, dwEffect); 
			pEffectState->lEfState &= ~(DIEGES_PLAYING);
	}

    ExitOleProc();

    DllLeaveCrit();

    return hres;
}

/*****************************************************************************
 *
 *      PID_GetEffectStatus
 *
 *          Obtain information about an effect.
 *
 *  dwId
 *
 *          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:
 *
 *          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.
 *
 *
 *****************************************************************************/

STDMETHODIMP
    PID_GetEffectStatus(IDirectInputEffectDriver *ped, DWORD dwId, DWORD dwEffect,
                            LPDWORD pdwStatus)
{
    CPidDrv *this = (CPidDrv *)ped;
    HRESULT hres;

    EnterProc(PID_GetEffectStatus, (_"xxxx", ped, dwId, dwEffect, pdwStatus));

    DllEnterCrit();

    *pdwStatus = 0x0;
    hres = PID_ValidateEffectIndex(ped, dwEffect);

    if(SUCCEEDED(hres) )
    {     
        PEFFECTSTATE    pEffectState =  PeffectStateFromBlockIndex(this,dwEffect); 
            *pdwStatus = (pEffectState->lEfState & DIEGES_PLAYING);
    }
    
    DllLeaveCrit();

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *      The VTBL for our effect driver
 *
 *****************************************************************************/

IDirectInputEffectDriverVtbl PID_Vtbl = {
    PID_QueryInterface,
    PID_AddRef,
    PID_Release,
    PID_DeviceID,
    PID_GetVersions,
    PID_Escape,
    PID_SetGain,
    PID_SendForceFeedbackCommand,
    PID_GetForceFeedbackState,
    PID_DownloadEffect,
    PID_DestroyEffect,
    PID_StartEffect,
    PID_StopEffect,
    PID_GetEffectStatus,
};

/*****************************************************************************
 *
 *      PID_New
 *
 *****************************************************************************/

STDMETHODIMP
    PID_New(REFIID riid, LPVOID *ppvOut)
{
    HRESULT hres;
    CPidDrv *this;

    this = LocalAlloc(LPTR, sizeof(CPidDrv));
    if(this)
    {

        /*
         *  Initialize the basic object management goo.
         */
        this->ed.lpVtbl = &PID_Vtbl;
        this->cRef = 1;
        DllAddRef();

        /*
         *  !!IHV!! Do instance initialization here.
         *
         *  (e.g., open the driver you are going to IOCTL to)
         *
         *  DO NOT RESET THE DEVICE IN YOUR CONSTRUCTOR!
         *
         *  Wait for the SendForceFeedbackCommand(SFFC_RESET)
         *  to reset the device.  Otherwise, you may reset
         *  a device that another application is still using.
         */

        this->hdevOvrlp = this->hdev = INVALID_HANDLE_VALUE;
        
        /*
         *  Attempt to obtain the desired interface.  QueryInterface
         *  will do an AddRef if it succeeds.
         */
        hres = PID_QueryInterface(&this->ed, riid, ppvOut);
        PID_Release(&this->ed);

    } else
    {
        hres = E_OUTOFMEMORY;
    }

    return hres;
}