/*******************************************************************************
*
*  (C) COPYRIGHT MICROSOFT CORP., 1998
*
*  TITLE:       Device.Cpp
*
*  VERSION:     2.0
*
*  AUTHOR:      ReedB
*
*  DATE:        5 Jan, 1999
*
*  DESCRIPTION:
*   Implementation of the WIA test scanner device methods. This sample WIA USD
*   supports push events by detecting when %windir%\temp\TESTUSD.BMP file has
*   been modified. This file becomes the new source of scanning data. An
*   event is generated the first time the device is loaded.
*
*******************************************************************************/

#include <windows.h>
#include <tchar.h>

#include "testusd.h"
#include "resource.h"
#include "tcamprop.h"

extern HINSTANCE g_hInst;

//
// Function prototypes, implemented in this file:
//

VOID FileChangeThread(LPVOID  lpParameter);

/**************************************************************************\
* TestUsdDevice::TestUsdDevice
*
*   Device class constructor
*
* Arguments:
*
*    None
*
* Return Value:
*
*    None
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

TestUsdDevice::TestUsdDevice(LPUNKNOWN punkOuter):
    m_cRef(1),
    m_punkOuter(NULL),
    m_fValid(FALSE),
    m_pIStiDevControl(NULL),
    m_hShutdownEvent(INVALID_HANDLE_VALUE),
    m_hSignalEvent(INVALID_HANDLE_VALUE),
    m_hEventNotifyThread(NULL),
    m_guidLastEvent(GUID_NULL),
    m_pIWiaEventCallback(NULL),
    m_pStiDevice(NULL),
    m_bstrDeviceID(NULL),
    m_bstrRootFullItemName(NULL),
    m_pIDrvItemRoot(NULL)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::TestUsdDevice"));

    *m_szSrcDataName = L'\0';

    //
    // See if we are aggregated. If we are (almost always the case) save
    // pointer to the controlling Unknown , so subsequent calls will be
    // delegated. If not, set the same pointer to "this".
    //

    if (punkOuter) {
        m_punkOuter = punkOuter;
    }
    else {

        //
        // Cast below is needed in order to point to right virtual table
        //

        m_punkOuter = reinterpret_cast<IUnknown*>
                      (static_cast<INonDelegatingUnknown*>
                      (this));
    }

    //
    // init camera search path
    //

    LPTSTR lpwszEnvString = TEXT("%CAMERA_ROOT%");

    DWORD dwRet = ExpandEnvironmentStrings(lpwszEnvString,
                                           gpszPath, MAX_PATH);

    if ((dwRet == 0) || (dwRet == (ULONG)_tcslen(lpwszEnvString)+1)) {

        _tcscpy(gpszPath, TEXT("C:\\Image"));
    }

}

/**************************************************************************\
* TestUsdDevice::PrivateInitialize
*
*   Device class private initialization
*
* Arguments:
*
*    None
*
* Return Value:
*
*    None
*
\**************************************************************************/
HRESULT TestUsdDevice::PrivateInitialize()
{
    HRESULT hr = S_OK;

    __try {
        if(!InitializeCriticalSectionAndSpinCount(&m_csShutdown, MINLONG)) {
            hr = HRESULT_FROM_WIN32(::GetLastError());
            WIAS_ERROR((g_hInst,"TestUsdDevice::PrivateInitialize init CritSect failed"));
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        hr = E_OUTOFMEMORY;
    }

    if(hr == S_OK) {
        //
        // Create event for syncronization of notifications shutdown.
        //

        m_hShutdownEvent =  CreateEvent(NULL,
                                        FALSE,
                                        FALSE,
                                        NULL);

        if (m_hShutdownEvent && (INVALID_HANDLE_VALUE != m_hShutdownEvent)) {
            m_fValid = TRUE;
        }
        else {
            hr = HRESULT_FROM_WIN32(::GetLastError());
            WIAS_ERROR((g_hInst,"TestUsdDevice::PrivateInitialize, create shutdown event failed"));
        }
    }

    return hr;
}

/**************************************************************************\
* TestUsdDevice::~TestUsdDevice
*
*   Device class destructor
*
* Arguments:
*
*    None
*
* Return Value:
*
*    None
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

TestUsdDevice::~TestUsdDevice(void)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::~TestUsdDevice"));


    //
    // Kill notification thread if it exists.
    //

    SetNotificationHandle(NULL);

    //
    // Close event for syncronization of notifications shutdown.
    //

    if (m_hShutdownEvent && (m_hShutdownEvent != INVALID_HANDLE_VALUE)) {
        CloseHandle(m_hShutdownEvent);
    }

    //
    // Release the device control interface.
    //

    if (m_pIStiDevControl) {
        m_pIStiDevControl->Release();
        m_pIStiDevControl = NULL;
    }

    //
    // WIA member destruction
    //
    // Cleanup the WIA event sink.
    //

    if (m_pIWiaEventCallback) {
        m_pIWiaEventCallback->Release();
    }

    //
    // Free the storage for the device ID.
    //

    if (m_bstrDeviceID) {
        SysFreeString(m_bstrDeviceID);
    }

    //
    // Release the objects supporting device property storage.
    //

    if (m_bstrRootFullItemName) {
        SysFreeString(m_bstrRootFullItemName);
    }

    //
    // Free the critical section.
    //

    DeleteCriticalSection(&m_csShutdown);
}

/**************************************************************************\
* TestUsdDevice::GetCapabilities
*
*   Get the device STI capabilities.
*
* Arguments:
*
*   pUsdCaps    - Pointer to USD capabilities data.
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::GetCapabilities(PSTI_USD_CAPS pUsdCaps)
{
    ZeroMemory(pUsdCaps, sizeof(*pUsdCaps));

    pUsdCaps->dwVersion = STI_VERSION;

    //
    // We do support device notifications, but do not requiring polling.
    //

    pUsdCaps->dwGenericCaps = STI_USD_GENCAP_NATIVE_PUSHSUPPORT;

    return STI_OK;
}

/**************************************************************************\
* TestUsdDevice::GetStatus
*
*   Query device online and/or event status.
*
* Arguments:
*
*   pDevStatus  - Pointer to device status data.
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::GetStatus(PSTI_DEVICE_STATUS pDevStatus)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::GetStatus"));

    //
    // Validate parameters.
    //

    if (!pDevStatus) {
        WIAS_ERROR((g_hInst,"TestUsdDevice::GetStatus, NULL parameter"));
        return E_INVALIDARG;
    }

    //
    // If we are asked, verify whether device is online.
    //

    pDevStatus->dwOnlineState = 0L;
    if (pDevStatus->StatusMask & STI_DEVSTATUS_ONLINE_STATE)  {

        //
        // The test device is always on-line.
        //

        pDevStatus->dwOnlineState |= STI_ONLINESTATE_OPERATIONAL;
    }

    //
    // If we are asked, verify state of event.
    //

    pDevStatus->dwEventHandlingState &= ~STI_EVENTHANDLING_PENDING;

    if (pDevStatus->StatusMask & STI_DEVSTATUS_EVENTS_STATE) {

        //
        // Generate an event the first time we load.
        //

        if (m_bUsdLoadEvent) {
            pDevStatus->dwEventHandlingState = STI_EVENTHANDLING_PENDING;

            m_guidLastEvent = guidEventFirstLoaded;

            m_bUsdLoadEvent = FALSE;
        }

        //
        // event pending ???
        //

    }

    return STI_OK;
}

/**************************************************************************\
* TestUsdDevice::DeviceReset
*
*   Reset data file pointer to start of file.
*
* Arguments:
*
*    None
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::DeviceReset(void)
{
    WIAS_TRACE((g_hInst,"DeviceReset"));

    return STI_OK;
}

/**************************************************************************\
* TestUsdDevice::Diagnostic
*
*   The test device always passes the diagnostic.
*
* Arguments:
*
*    pBuffer    - Pointer o diagnostic result data.
*
* Return Value:
*
*    None
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::Diagnostic(LPSTI_DIAG pBuffer)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::Diagnostic"));

    //
    // Initialize response buffer
    //

    pBuffer->dwStatusMask = 0;

    ZeroMemory(&pBuffer->sErrorInfo,sizeof(pBuffer->sErrorInfo));

    pBuffer->sErrorInfo.dwGenericError = NOERROR;
    pBuffer->sErrorInfo.dwVendorError = 0;

    return STI_OK;
}

/**************************************************************************\
* TestUsdDevice::SetNotificationHandle
*
*   Starts and stops the event notification thread.
*
* Arguments:
*
*    hEvent -   If not valid start the notification thread otherwise kill
*               the notification thread.
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::SetNotificationHandle(HANDLE hEvent)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::SetNotificationHandle"));

    HRESULT hr = S_OK;

    EnterCriticalSection(&m_csShutdown);

    //
    // Are we starting or stopping the notification thread?
    //

    if (hEvent && (hEvent != INVALID_HANDLE_VALUE)) {

        m_hSignalEvent = hEvent;

        //
        // Initialize to no event.
        //

        m_guidLastEvent = GUID_NULL;

        //
        // Create the notification thread.
        //

        if (!m_hEventNotifyThread) {

            DWORD   dwThread;

            m_hEventNotifyThread = CreateThread(NULL,
                                                0,
                                                (LPTHREAD_START_ROUTINE)FileChangeThread,
                                                (LPVOID)this,
                                                0,
                                                &dwThread);

            if (m_hEventNotifyThread) {
                WIAS_TRACE((g_hInst,"TestUsdDevice::SetNotificationHandle, Enabling event notification"));
            }
            else {
                WIAS_ERROR((g_hInst,"TestUsdDevice::SetNotificationHandle, unable to create notification thread"));
                hr = HRESULT_FROM_WIN32(::GetLastError());
            }
        }
        else {
            WIAS_ERROR((g_hInst,"TestUsdDevice::SetNotificationHandle, spurious notification thread"));
            hr = STIERR_UNSUPPORTED;
        }
    }
    else {

        //
        // Disable event notifications.
        //

        SetEvent(m_hShutdownEvent);
        if (m_hEventNotifyThread) {
            WIAS_TRACE((g_hInst,"Disabling event notification"));
            WaitForSingleObject(m_hEventNotifyThread, 400);
            CloseHandle(m_hEventNotifyThread);
            m_hEventNotifyThread = NULL;
            m_guidLastEvent = GUID_NULL;

            //
            // close dlg
            //

            if (m_hDlg != NULL) {
                SendMessage(m_hDlg,WM_COMMAND,IDOK,0);
                m_hDlg = NULL;
            }

        }
    }
    LeaveCriticalSection(&m_csShutdown);
    return hr;
}

/**************************************************************************\
* TestUsdDevice::GetNotificationData
*
*   Provides data on n event.
*
* Arguments:
*
*    pBuffer    - Pointer to event data.
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::GetNotificationData( LPSTINOTIFY pBuffer )
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::GetNotificationData"));

    //
    // If we have notification ready - return it's guid
    //

    if (!IsEqualIID(m_guidLastEvent, GUID_NULL)) {

        pBuffer->guidNotificationCode  = m_guidLastEvent;

        m_guidLastEvent = GUID_NULL;

        pBuffer->dwSize = sizeof(STINOTIFY);

        ZeroMemory(&pBuffer->abNotificationData, sizeof(pBuffer->abNotificationData));

        //
        // private event
        //


        if (IsEqualIID(m_guidLastEvent, WIA_EVENT_NAME_CHANGE)) {

        }
    }
    else {
        return STIERR_NOEVENTS;
    }

    return STI_OK;
}

/**************************************************************************\
* TestUsdDevice::Escape
*
*   Issue a command to the device.
*
* Arguments:
*
*    EscapeFunction - Command to be issued.
*    pInData        - Input data to be passed with command.
*    cbInDataSize   - Size of input data.
*    pOutData       - Output data to be passed back from command.
*    cbOutDataSize  - Size of output data buffer.
*    pcbActualData  - Size of output data actually written.
*
* Return Value:
*
*    None
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::Escape(
    STI_RAW_CONTROL_CODE    EscapeFunction,
    LPVOID                  pInData,
    DWORD                   cbInDataSize,
    LPVOID                  pOutData,
    DWORD                   cbOutDataSize,
    LPDWORD                 pcbActualData)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::Escape, unsupported"));

    //
    // Write command to device if needed.
    //

    return STIERR_UNSUPPORTED;
}

/**************************************************************************\
* TestUsdDevice::GetLastError
*
*   Get the last error from the device.
*
* Arguments:
*
*    pdwLastDeviceError - Pointer to last error data.
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::GetLastError(LPDWORD pdwLastDeviceError)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::GetLastError"));

    if (IsBadWritePtr(pdwLastDeviceError, sizeof(DWORD))) {
        return STIERR_INVALID_PARAM;
    }

    *pdwLastDeviceError = m_dwLastOperationError;

    return STI_OK;
}

/**************************************************************************\
* TestUsdDevice::GetLastErrorInfo
*
*   Get extended error information from the device.
*
* Arguments:
*
*    pLastErrorInfo - Pointer to extended device error data.
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::GetLastErrorInfo(STI_ERROR_INFO *pLastErrorInfo)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::GetLastErrorInfo"));

    if (IsBadWritePtr(pLastErrorInfo, sizeof(STI_ERROR_INFO))) {
        return STIERR_INVALID_PARAM;
    }

    pLastErrorInfo->dwGenericError = m_dwLastOperationError;
    pLastErrorInfo->szExtendedErrorText[0] = '\0';

    return STI_OK;
}

/**************************************************************************\
* TestUsdDevice::LockDevice
*
*   Lock access to the device.
*
* Arguments:
*
*    None
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::LockDevice(void)
{
    return STI_OK;
}

/**************************************************************************\
* TestUsdDevice::UnLockDevice
*
*   Unlock access to the device.
*
* Arguments:
*
*    None
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::UnLockDevice(void)
{
    return STI_OK;
}

/**************************************************************************\
* TestUsdDevice::RawReadData
*
*   Read raw data from the device.
*
* Arguments:
*
*    lpBuffer           -
*    lpdwNumberOfBytes  -
*    lpOverlapped       -
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::RawReadData(
    LPVOID          lpBuffer,
    LPDWORD         lpdwNumberOfBytes,
    LPOVERLAPPED    lpOverlapped)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::RawReadData"));

    return STI_OK;
}

/**************************************************************************\
* TestUsdDevice::RawWriteData
*
*   Write raw data to the device.
*
* Arguments:
*
*    lpBuffer           -
*    dwNumberOfBytes    -
*    lpOverlapped       -
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::RawWriteData(
    LPVOID          lpBuffer,
    DWORD           dwNumberOfBytes,
    LPOVERLAPPED    lpOverlapped)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::RawWriteData"));

    return STI_OK;
}

/**************************************************************************\
* TestUsdDevice::RawReadCommand
*
*
*
* Arguments:
*
*    lpBuffer           -
*    lpdwNumberOfBytes  -
*    lpOverlapped       -
*
* Return Value:
*
*    Status
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::RawReadCommand(
    LPVOID          lpBuffer,
    LPDWORD         lpdwNumberOfBytes,
    LPOVERLAPPED    lpOverlapped)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::RawReadCommand, unsupported"));

    return STIERR_UNSUPPORTED;
}

/**************************************************************************\
* TestUsdDevice::RawWriteCommand
*
*
*
* Arguments:
*
*    lpBuffer       -
*    nNumberOfBytes -
*    lpOverlapped   -
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::RawWriteCommand(
    LPVOID          lpBuffer,
    DWORD           nNumberOfBytes,
    LPOVERLAPPED    lpOverlapped)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::RawWriteCommand, unsupported"));

    return STIERR_UNSUPPORTED;
}

/**************************************************************************\
* TestUsdDevice::Initialize
*
*   Initialize the device object.
*
* Arguments:
*
*    pIStiDevControlNone    -
*    dwStiVersion           -
*    hParametersKey         -
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

STDMETHODIMP TestUsdDevice::Initialize(
    PSTIDEVICECONTROL   pIStiDevControl,
    DWORD               dwStiVersion,
    HKEY                hParametersKey)
{
    HRESULT         hr = STI_OK;
    UINT            uiNameLen = 0;
    CAMERA_STATUS   camStatus;

    WIAS_TRACE((g_hInst,"TestUsdDevice::Initialize"));

    if (!pIStiDevControl) {
        WIAS_ERROR((g_hInst,"TestUsdDevice::Initialize, invalid device control interface"));
        return STIERR_INVALID_PARAM;
    }

    //
    // Cache the device control interface.
    //

    m_pIStiDevControl = pIStiDevControl;
    m_pIStiDevControl->AddRef();

    //
    // Try to open the camera only once here during Initialize
    //

    hr = CamOpenCamera(&camStatus);

    return (hr);
}

/**************************************************************************\
* TestUsdDevice::RunNotifications
*
*   Monitor changes to the source data file parent directory.
*
* Arguments:
*
*    None
*
* Return Value:
*
*    Status.
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

VOID TestUsdDevice::RunNotifications(void)
{
    //
    // start up camera event dlg
    //

    WIAS_TRACE((g_hInst,"TestUsdDevice::RunNotifications: start up event dlg"));

    HWND hWnd = GetDesktopWindow();

    int iret = (int)DialogBoxParam(
        g_hInst,
        MAKEINTRESOURCE(IDD_EVENT_DLG),
        hWnd,
        (DLGPROC)CameraEventDlgProc,
        (LPARAM)this
        );

    WIAS_TRACE((g_hInst,"TestUsdDevice::RunNotifications, iret = 0x%lx",iret));

    if (iret == -1) {
        int err = ::GetLastError();
        WIAS_TRACE((g_hInst,"TestUsdDevice::RunNotifications, dlg error = 0x%lx",err));
    }
}

/**************************************************************************\
* FileChangeThread
*
*   Calls RunNotifications to detect changing source data file.
*
* Arguments:
*
*    lpParameter    - Pointer to device object.
*
* Return Value:
*
*    None
*
* History:
*
*    9/11/1998 Original Version
*
\**************************************************************************/

VOID FileChangeThread(LPVOID  lpParameter)
{
    WIAS_TRACE((g_hInst,"TestUsdDevice::"));

    TestUsdDevice   *pThisDevice = (TestUsdDevice *)lpParameter;

    pThisDevice->RunNotifications();
}