/*****************************************************************************
 *
 *  PidRd.c
 *
 *  Copyright (c) 1999 Microsoft Corporation.  All Rights Reserved.
 *
 *
 *
 *  Abstract:
 *
 *      Read input data from PID device .
 *
 *****************************************************************************/

#include "pidpr.h"

#define sqfl    (sqflRead)

BOOL INTERNAL
    PID_IssueRead(PCPidDrv this);

BOOL INTERNAL
    PID_IssueWrite(PCPidDrv this);

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   PCHID | pchidFromPo |
 *
 *          Given an interior pointer to an <t OVERLAPPED>, retrieve
 *          a pointer to the parent <t CHid>.
 *
 *  @parm   LPOVERLAPPED | po |
 *
 *          The pointer to convert.
 *
 *****************************************************************************/

PCPidDrv INLINE
    pCPidDrvFromPo(LPOVERLAPPED po)
{
    return (CPidDrv*) pvSubPvCb(po, FIELD_OFFSET(CPidDrv, o));
}

void CALLBACK
    PID_ReadComplete(DWORD dwError, DWORD cbRead, LPOVERLAPPED po)
{
    PCPidDrv this = pCPidDrvFromPo(po);

    //EnterProc( PID_ReadComplete, (_"xxx", dwError, cbRead, po ));

    if( !IsBadReadPtr(this, cbX(this))
        &&this->cThreadRef
        && dwError == 0
        //&&( this->o.InternalHigh == this->cbReport[HidP_Input] )
        &&( this->hdevOvrlp != INVALID_HANDLE_VALUE ) )
    {
		HRESULT hres;
		PUCHAR  pReport;
		UINT    cbReport;

		USHORT  LinkCollection = 0x0;
		USAGE   rgUsages[MAX_BUTTONS] ;
		USAGE   UsagePage   =   HID_USAGE_PAGE_PID;
		ULONG   cAUsages    =  MAX_BUTTONS;
		BOOL    fEffectPlaying = FALSE;
		LONG    lEffectIndex;

		pReport  = this->pReport[HidP_Input];
		cbReport = this->cbReport[HidP_Input];
	
		// If the report does not belong to the PID usage page
		// we should be out of here really quick.
		hres = HidP_GetUsages
				   (HidP_Input,
					UsagePage,
					LinkCollection,
					rgUsages,
					&cAUsages,
					this->ppd,
					pReport,
					cbReport
				   );
		if( SUCCEEDED(hres ) )
		{
			UINT indx;
			DWORD dwState =  DIGFFS_ACTUATORSOFF | DIGFFS_USERFFSWITCHOFF | DIGFFS_POWEROFF | DIGFFS_SAFETYSWITCHOFF;
			for(indx = 0x0; indx < cAUsages; indx++ )
			{
				USAGE Usage = rgUsages[indx];
				switch(Usage)
				{
				case HID_USAGE_PID_EFFECT_PLAYING:
					fEffectPlaying = TRUE;
					break;
				case  HID_USAGE_PID_DEVICE_PAUSED:
					dwState |= DIGFFS_PAUSED;
					break;
				case  HID_USAGE_PID_ACTUATORS_ENABLED:
					dwState |= DIGFFS_ACTUATORSON;
					dwState &= ~(DIGFFS_ACTUATORSOFF);
					break;
				case  HID_USAGE_PID_ACTUATOR_OVERRIDE_SWITCH:
					dwState |= DIGFFS_USERFFSWITCHON;
					dwState &= ~(DIGFFS_USERFFSWITCHOFF);
					break;
				case  HID_USAGE_PID_SAFETY_SWITCH:
					dwState |= DIGFFS_SAFETYSWITCHON;
					dwState &= ~(DIGFFS_SAFETYSWITCHOFF);
					break;
				case  HID_USAGE_PID_ACTUATOR_POWER:
					dwState |= DIGFFS_POWERON;
					dwState &= ~(DIGFFS_POWEROFF);
					break;
				default:
					SquirtSqflPtszV(sqfl | sqflVerbose,
										TEXT("%s: Unsupported input status usage (%x,%x:%s) "),
										TEXT("PID_ReadComplete"),
										UsagePage, Usage,
										PIDUSAGETXT(UsagePage,Usage) );

					break;
				}

				this->dwState = dwState;
			}

			hres = PID_ParseReport(
									  &this->ed,
									  &g_BlockIndexIN,
									  LinkCollection,
									  &lEffectIndex,
									  cbX(lEffectIndex),
									  pReport,
									  cbReport
									  );

			if( SUCCEEDED(hres) )
			{
				PEFFECTSTATE    pEffectState =  PeffectStateFromBlockIndex(this,lEffectIndex);

				if(fEffectPlaying)
				{
					pEffectState->lEfState |= DIEGES_PLAYING;
				} else
				{
					pEffectState->lEfState &= ~(DIEGES_PLAYING);
				}
			}
		}

        // Issue another read
        PID_IssueRead(this);
    } else
    {
        // Boo!
    }
    //ExitProc();
}


BOOL INTERNAL
    PID_IssueRead(PCPidDrv this)
{
    BOOL fRc = FALSE;

    if(  !IsBadReadPtr(this, cbX(this))
       && this->cThreadRef )
    {
        fRc = ReadFileEx(this->hdevOvrlp, this->pReport[HidP_Input],
                         this->cbReport[HidP_Input], &this->o,
                         PID_ReadComplete);
        if (fRc == FALSE)
		{
			SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("FAIL ReadFileEx"));
        }
    }
	else
	{
		RPF(TEXT("Bad this pointer or thread ref count!"));
	}
    return fRc;
}


void CALLBACK
    PID_WriteComplete(DWORD dwError, DWORD cbWritten, LPOVERLAPPED po)
{
    PCPidDrv this = pCPidDrvFromPo(po);

    //EnterProc( PID_ReadComplete, (_"xxx", dwError, cbRead, po ));

    if( !IsBadReadPtr(this, cbX(this))
        &&(this->cThreadRef)
        //&&( this->o.InternalHigh == this->cbWriteReport[this->blockNr] )
		&&(this->hWriteComplete)
		&&(this->hWrite)
        &&( this->hdevOvrlp != INVALID_HANDLE_VALUE ) )
    {
		//if we didn't get an error & wrote everything -- or if we already tried
		//twice -- we move on
		if ( ((dwError == 0) && (cbWritten == this->cbWriteReport [this->blockNr]))
			|| (this->dwWriteAttempt != 0)
			)
		{
			//print a message if couldn't write a particular block
			if ((dwError != 0) || (cbWritten != this->cbWriteReport [this->blockNr]))
			{
                SquirtSqflPtszV(sqfl | sqflError,
                    TEXT("Couldn't write block %u after two tries, giving up on this block."), 
                    this->blockNr);
			}
			//move on
			this->dwWriteAttempt = 0;
			this->blockNr++;
			if (this->blockNr < this->totalBlocks)
			{
				//write the next block
				if (PID_IssueWrite(this) == FALSE)
				{
					//in case of failure, the callback will never be called and the completion event never set,
					//so need to set it here.
					SetEvent(this->hWriteComplete);
				}
			}
			else
			{
				//we are done w/ this update
				AssertF(this->blockNr == this->totalBlocks);
				SetEvent(this->hWriteComplete);
			}
		}
		else
		{
			//this is the first time we tried to write a particular block, and we failed;
			//we will try once more
            SquirtSqflPtszV(sqfl | sqflBenign,
                TEXT("Couldn't write block %u on first attempt, retrying."), 
                this->blockNr);
			this->dwWriteAttempt = 1;
			if (PID_IssueWrite(this) == FALSE)
			{
				//in case of failure, the callback will never be called and the completion event never set,
				//so need to set it here.
				this->dwWriteAttempt = 0;
				SetEvent(this->hWriteComplete);
			}
		}
    } else
    {
        //need to set the completion event, otherwise we will keep waiting...
		RPF(TEXT("Bad this pointer or thread ref count or handle!"));
		this->dwWriteAttempt = 0;
		SetEvent(this->hWriteComplete);
    }
    //ExitProc();
}


BOOL INTERNAL
    PID_IssueWrite(PCPidDrv this)
{
    BOOL fRc = FALSE;
	
    if(  !IsBadReadPtr(this, cbX(this))
       && this->cThreadRef )
    {
        fRc = WriteFileEx(this->hdevOvrlp, this->pWriteReport[this->blockNr],
                         this->cbWriteReport[this->blockNr], &this->o,
                         PID_WriteComplete);
		if (fRc == FALSE)
		{
			SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("FAIL WriteFileEx"));
        }
    }
	else
	{
		RPF(TEXT("Bad this pointer or thread ref count!"));
	}
    return fRc;
}

VOID INTERNAL
    PID_ThreadProc(CPidDrv* this)
{
    MSG msg;

    EnterProc( PID_ThreadProc, (_"x", this ));
    AssertF(this->hdevOvrlp == INVALID_HANDLE_VALUE );

    this->hdevOvrlp = CreateFile(this->tszDeviceInterface,
                                 GENERIC_READ | GENERIC_WRITE,
                                 FILE_SHARE_READ | FILE_SHARE_WRITE,
                                 0,                      /* no SECURITY_ATTRIBUTES */
                                 OPEN_EXISTING,
                                 FILE_FLAG_OVERLAPPED,   /* attributes */
                                 0);                     /* template */


    if( this->hdevOvrlp == INVALID_HANDLE_VALUE )
    {
        SquirtSqflPtszV(sqfl | sqflError,
                        TEXT("%s:FAIL CreateFile(OverLapped)"),
                        s_tszProc );
    }
	else
	//fix for mb 35282 -- no use calling PID_IssueRead() w/ an INVALID_HANDLE_VALUE for a file handle
	{

		if( PID_IssueRead(this) )
		{
			do
			{
				DWORD dwRc;
				do
				{
					AssertF(this->hWrite != 0x0);
					AssertF(this->hWriteComplete != 0x0);

					dwRc = MsgWaitForMultipleObjectsEx(1, &this->hWrite, INFINITE, QS_ALLINPUT,
													   MWMO_ALERTABLE);

					if (dwRc == WAIT_OBJECT_0)
					{
						if (PID_IssueWrite(this) == FALSE)
						{
							//in case of failure, the callback will never be called and the completion event never set,
							//so need to set it here.
							SetEvent(this->hWriteComplete);
						}
					}

				} while ((dwRc == WAIT_IO_COMPLETION) || (dwRc == WAIT_OBJECT_0));

				while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
				{
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}

			} while(this->cThreadRef);

			if( this->hdevOvrlp != INVALID_HANDLE_VALUE )
			{
				HANDLE hdev;
				hdev = this->hdevOvrlp;
                                this->hdevOvrlp = INVALID_HANDLE_VALUE;
				CancelIo_(hdev);
				Sleep(0);
				CloseHandle(hdev);
			}
		}
	}

	//close the event handles as well
	if (this->hWrite != 0x0)
	{
		CloseHandle(this->hWrite);
		this->hWrite = 0x0;
	}
	if (this->hWriteComplete != 0x0)
	{
		CloseHandle(this->hWriteComplete);
		this->hWriteComplete = 0x0;
	}

    if(this->hThread)
    {
        HANDLE hdev = this->hThread;
        this->hThread = NULL;
        CloseHandle(hdev);
    }


    FreeLibraryAndExitThread(g_hinst, 0);
    ExitProc();
}