/*
 * Still Image class installer
 *
 * Copyright (C) 2005 Damjan Jovanovic
 * Copyright (C) 2006 Igor Farinic
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#define COBJMACROS
#include "sti_internal.h"
#include "winuser.h"
#include "setupapi.h"
#include "devguid.h"

WINE_DEFAULT_DEBUG_CHANNEL(sti);

/* Method declarations */

static HRESULT WINAPI UNKNOWN_QueryInterface(IUnknown* This, REFIID riid,
                                             void** ppvObject);
static ULONG   WINAPI UNKNOWN_AddRef(IUnknown* This);
static ULONG   WINAPI UNKNOWN_Release(IUnknown* This);
static HRESULT WINAPI STIDEVICE_QueryInterface(IStiDevice* This, REFIID riid,
                                               void** ppvObject);
static ULONG   WINAPI STIDEVICE_AddRef(IStiDevice* This);
static ULONG   WINAPI STIDEVICE_Release(IStiDevice* This);
static HRESULT WINAPI STIDEVICE_Initialize(IStiDevice* This, HINSTANCE hinst,
                                           LPCWSTR pwszDeviceName,
                                           DWORD dwVersion, DWORD dwMode);
static HRESULT WINAPI STIDEVICE_GetCapabilities(IStiDevice* This,
                                                PSTI_DEV_CAPS pDevCaps);
static HRESULT WINAPI STIDEVICE_GetStatus(IStiDevice* This,
                                          PSTI_DEVICE_STATUS pDevStatus);
static HRESULT WINAPI STIDEVICE_DeviceReset(IStiDevice* This);
static HRESULT WINAPI STIDEVICE_Diagnostic(IStiDevice* This,
                                           LPSTI_DIAG pBuffer);
static HRESULT WINAPI STIDEVICE_Escape(IStiDevice* This,
                                       STI_RAW_CONTROL_CODE EscapeFunction,
                                       LPVOID lpInData, DWORD cbInDataSize,
                                       LPVOID pOutData, DWORD dwOutDataSize,
                                       DWORD* pdwActualData);
static HRESULT WINAPI STIDEVICE_GetLastError(IStiDevice* This,
                                             DWORD* pdwLastDeviceError);
static HRESULT WINAPI STIDEVICE_LockDevice(IStiDevice* This, DWORD dwTimeOut);
static HRESULT WINAPI STIDEVICE_UnLockDevice(IStiDevice* This);
static HRESULT WINAPI STIDEVICE_RawReadData(IStiDevice* This, LPVOID lpBuffer,
                                            DWORD* lpdwNumberOfBytes,
                                            OVERLAPPED* lpOverlapped);
static HRESULT WINAPI STIDEVICE_RawWriteData(IStiDevice* This, LPVOID lpBuffer,
                                             DWORD nNumberOfBytes,
                                             OVERLAPPED* lpOverlapped);
static HRESULT WINAPI STIDEVICE_RawReadCommand(IStiDevice* This,
                                               LPVOID lpBuffer,
                                               DWORD* lpdwNumberOfBytes,
                                               OVERLAPPED* lpOverlapped);
static HRESULT WINAPI STIDEVICE_RawWriteCommand(IStiDevice* This,
                                                LPVOID lpBuffer,
                                                DWORD nNumberOfBytes,
                                                OVERLAPPED* lpOverlapped);
static HRESULT WINAPI STIDEVICE_Subscribe(IStiDevice* This,
                                          LPSTISUBSCRIBE lpSubscribe);
static HRESULT WINAPI STIDEVICE_GetLastNotificationData(IStiDevice* This,
                                                        LPSTINOTIFY lpNotify);
static HRESULT WINAPI STIDEVICE_UnSubscribe(IStiDevice* This);
static HRESULT WINAPI STIDEVICE_GetLastErrorInfo(IStiDevice* This,
                                                 STI_ERROR_INFO* pLastErrorInfo);


static HRESULT open_driver_key(LPCWSTR internalName, HKEY *driverKey)
{
    HKEY classKey;
    LONG ret;

    /* Open "HKLM\System\CurrentControlSet\Control\Class" */
    classKey = SetupDiOpenClassRegKey(NULL, KEY_EXECUTE);
    if (classKey == INVALID_HANDLE_VALUE)
        return HRESULT_FROM_WIN32(GetLastError());

    /* Now open internal name (eg. "{6BDD...}\0000") inside that */
    ret = RegOpenKeyW(classKey, internalName, driverKey);
    RegCloseKey(classKey);
    return HRESULT_FROM_WIN32(ret);
}


/* Virtual method tables */

static IUnknownVtbl UnknownVtbl = {
    UNKNOWN_QueryInterface,
    UNKNOWN_AddRef,
    UNKNOWN_Release
};

static IStiDeviceVtbl StiDeviceVtbl = {
    STIDEVICE_QueryInterface,
    STIDEVICE_AddRef,
    STIDEVICE_Release,
    STIDEVICE_Initialize,
    STIDEVICE_GetCapabilities,
    STIDEVICE_GetStatus,
    STIDEVICE_DeviceReset,
    STIDEVICE_Diagnostic,
    STIDEVICE_Escape,
    STIDEVICE_GetLastError,
    STIDEVICE_LockDevice,
    STIDEVICE_UnLockDevice,
    STIDEVICE_RawReadData,
    STIDEVICE_RawWriteData,
    STIDEVICE_RawReadCommand,
    STIDEVICE_RawWriteCommand,
    STIDEVICE_Subscribe,
    STIDEVICE_GetLastNotificationData,
    STIDEVICE_UnSubscribe,
    STIDEVICE_GetLastErrorInfo
};

/* Methods */

static HRESULT WINAPI UNKNOWN_QueryInterface(IUnknown* pUnk, REFIID riid,
                                             void** ppvObject)
{
    StiDevice *This = IMPL(StiDevice, iUnknown, pUnk);
    TRACE("(%p, %p, %p)\n", pUnk, riid, ppvObject);
    *ppvObject = 0;
    if (IsEqualIID(riid, &IID_IUnknown))
        *ppvObject = &This->iUnknown;
    else if (IsEqualIID(riid, &IID_IStiDevice))
        *ppvObject = &This->iStiDevice;
    else
        return E_NOINTERFACE;
    UNKNOWN_AddRef(pUnk);
    return NOERROR;
}

static ULONG WINAPI UNKNOWN_AddRef(IUnknown* pUnk)
{
    StiDevice *This = IMPL(StiDevice, iUnknown, pUnk);
    TRACE("(%p)\n", pUnk);
    return ++This->m_refCount;
}

static ULONG WINAPI UNKNOWN_Release(IUnknown* pUnk)
{
    StiDevice *This = IMPL(StiDevice, iUnknown, pUnk);
    TRACE("(%p)\n", pUnk);
    --This->m_refCount;
    if (This->m_refCount == 0)
    {
        InterlockedDecrement(&This->m_internalDev->m_refCount);
        free(This);
        return 0;
    }
    else
        return This->m_refCount;
}

static HRESULT WINAPI STIDEVICE_QueryInterface(IStiDevice* pDev, REFIID riid,
                                               void **ppvObject)
{
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %p, %p)\n", pDev, riid, ppvObject);
    if (This->m_punkOuter)
        return This->m_punkOuter->lpVtbl->QueryInterface(This->m_punkOuter, riid, ppvObject);
    else
        return UNKNOWN_QueryInterface(&This->iUnknown, riid, ppvObject);
}

static ULONG WINAPI STIDEVICE_AddRef(IStiDevice* pDev)
{
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p)\n", pDev);
    if (This->m_punkOuter)
        return This->m_punkOuter->lpVtbl->AddRef(This->m_punkOuter);
    else
        return UNKNOWN_AddRef(&This->iUnknown);
}

static ULONG WINAPI STIDEVICE_Release(IStiDevice* pDev)
{
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p)\n", pDev);
    if (This->m_punkOuter)
        return This->m_punkOuter->lpVtbl->Release(This->m_punkOuter);
    else
        return UNKNOWN_Release(&This->iUnknown);
}

static HRESULT WINAPI STIDEVICE_Initialize(IStiDevice* pDev, HINSTANCE hinst,
                                           LPCWSTR pwszDeviceName,
                                           DWORD dwVersion, DWORD dwMode)
{
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    InternalDevice *internalDevice;
    IStiDeviceControl *iStiDeviceControl;
    LONG ret;
	RPC_STATUS rpcStatus;
    WCHAR minidriverGUIDString[1000]; /* Exactly how long are GUID strings, anyway? */
    GUID minidriverGUID;

    TRACE("(%p, %p, %s, %lu, %lu)\n", pDev, hinst, debugstr_w(pwszDeviceName),
          dwVersion, dwMode);

    internalDevice = find_device(pwszDeviceName);
    if (!internalDevice)
        return STI_NOTCONNECTED;
    This->m_internalDev = internalDevice;
    
    This->m_hinst = hinst;
    strcpyW(This->m_portName, pwszDeviceName);
    This->m_openMode = dwMode;

    ret = open_driver_key(pwszDeviceName, &This->m_driverKey);
    if (ret != NO_ERROR)
        return ret;
    
    ret = RegQueryValueExA(This->m_driverKey, "USDClass", 0, NULL, minidriverGUIDString,
                           NULL);
    if (ret != NO_ERROR)
        return ret;
    
    rpcStatus = UuidFromStringA(minidriverGUIDString, &minidriverGUID);
    if (rpcStatus != RPC_S_OK)
        return STIERR_GENERIC;

    ret = CoCreateInstance(&minidriverGUID, NULL, CLSCTX_ALL, &IID_IStiUSD, &This->m_iStiUSD);
    if (ret != NO_ERROR)
        return ret;

    /* Create the IStiDeviceControl we need to pass */
    ret = CreateStiDeviceControl(NULL, &iStiDeviceControl);
    if (ret != NOERROR)
        return ret;
    ret = IStiDeviceControl_Initialize(iStiDeviceControl, 0, dwMode, pwszDeviceName, 0);
    if (ret != STI_OK)
    {
        IStiDeviceControl_Release(iStiDeviceControl);
        return ret;
    }
    
    ret = IStiUSD_Initialize(This->m_iStiUSD, iStiDeviceControl, dwVersion,
                             This->m_driverKey);
    IStiDeviceControl_Release(iStiDeviceControl);
    if (ret != STI_OK)
        return ret;
}

static HRESULT WINAPI STIDEVICE_GetCapabilities(IStiDevice* pDev,
                                                PSTI_DEV_CAPS pDevCaps)
{
    HRESULT ret;
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %p)\n", pDev, pDevCaps);
    ret = IStiUSD_GetCapabilities(This->m_iStiUSD, pDevCaps);
    return ret;
}

static HRESULT WINAPI STIDEVICE_GetStatus(IStiDevice* pDev,
                                          PSTI_DEVICE_STATUS pDevStatus)
{
    HRESULT ret;
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %p)\n", pDev, pDevStatus);
    ret = IStiUSD_GetStatus(This->m_iStiUSD, pDevStatus);
    return ret;
}

static HRESULT WINAPI STIDEVICE_DeviceReset(IStiDevice* pDev)
{
    HRESULT ret;
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p)\n", pDev);
    ret = IStiUSD_DeviceReset(This->m_iStiUSD);
    return ret;
}

static HRESULT WINAPI STIDEVICE_Diagnostic(IStiDevice* pDev, LPSTI_DIAG pBuffer)
{
    HRESULT ret;
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %p)\n", pDev, pBuffer);
    ret = IStiUSD_Diagnostic(This->m_iStiUSD, pBuffer);
    return ret;
}

static HRESULT WINAPI STIDEVICE_Escape(IStiDevice* pDev,
                                       STI_RAW_CONTROL_CODE EscapeFunction,
                                       LPVOID lpInData, DWORD cbInDataSize,
                                       LPVOID pOutData, DWORD dwOutDataSize,
                                       DWORD* pdwActualData)
{
    HRESULT ret;
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %lu, %p, %lu, %p, %lu, %p)\n", pDev, EscapeFunction,
          lpInData, cbInDataSize, pOutData, dwOutDataSize, pdwActualData);
    ret = IStiUSD_Escape(This->m_iStiUSD, EscapeFunction,
                         lpInData, cbInDataSize,
                         pOutData, dwOutDataSize,
                         pdwActualData);
    return ret;
}

static HRESULT WINAPI STIDEVICE_GetLastError(IStiDevice* pDev,
                                             DWORD* pdwLastDeviceError)
{
    HRESULT ret;
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %p)\n", pDev, pdwLastDeviceError);
    ret = IStiUSD_GetLastError(This->m_iStiUSD, pdwLastDeviceError);
    return ret;
}

static HRESULT WINAPI STIDEVICE_LockDevice(IStiDevice* pDev, DWORD dwTimeOut)
{
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %lu)\n", pDev, dwTimeOut);
    if (WaitForSingleObject(This->m_internalDev->m_mutex, dwTimeOut) == WAIT_OBJECT_0)
    {
        IStiUSD_LockDevice(This->m_iStiUSD);
        return STI_OK;
    }
    else
        return STIERR_GENERIC;
}

static HRESULT WINAPI STIDEVICE_UnLockDevice(IStiDevice* pDev)
{
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p)\n", pDev);
    ReleaseMutex(This->m_internalDev->m_mutex);
    IStiUSD_UnLockDevice(This->m_iStiUSD);
    return STI_OK;
}

static HRESULT WINAPI STIDEVICE_RawReadData(IStiDevice* pDev, LPVOID lpBuffer,
                                            DWORD* lpdwNumberOfBytes,
                                            OVERLAPPED* lpOverlapped)
{
    HRESULT ret;
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %p, %p, %p)\n", pDev, lpBuffer, lpdwNumberOfBytes, lpOverlapped);
    ret = IStiUSD_RawReadData(This->m_iStiUSD, lpBuffer, lpdwNumberOfBytes,
                              lpOverlapped);
    return ret;
}

static HRESULT WINAPI STIDEVICE_RawWriteData(IStiDevice* pDev, LPVOID lpBuffer,
                                             DWORD nNumberOfBytes,
                                             OVERLAPPED* lpOverlapped)
{
    HRESULT ret;
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %p, %lu, %p)\n", pDev, lpBuffer, nNumberOfBytes, lpOverlapped);
    ret = IStiUSD_RawWriteData(This->m_iStiUSD, lpBuffer, nNumberOfBytes,
                               lpOverlapped);
    return ret;
}

static HRESULT WINAPI STIDEVICE_RawReadCommand(IStiDevice* pDev,
                                               LPVOID lpBuffer,
                                               DWORD* lpdwNumberOfBytes,
                                               OVERLAPPED* lpOverlapped)
{
    HRESULT ret;
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %p, %p, %p)\n", pDev, lpBuffer, lpdwNumberOfBytes, lpOverlapped);
    ret = IStiUSD_RawReadCommand(This->m_iStiUSD, lpBuffer, lpdwNumberOfBytes,
                                 lpOverlapped);
    return ret;
}

static HRESULT WINAPI STIDEVICE_RawWriteCommand(IStiDevice* pDev,
                                                LPVOID lpBuffer,
                                                DWORD nNumberOfBytes,
                                                OVERLAPPED* lpOverlapped)
{
    HRESULT ret;
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %p, %lu, %p)\n", pDev, lpBuffer, nNumberOfBytes, lpOverlapped);
    ret = IStiUSD_RawWriteCommand(This->m_iStiUSD, lpBuffer, nNumberOfBytes,
                                  lpOverlapped);
    return ret;
}

static HRESULT WINAPI STIDEVICE_Subscribe(IStiDevice* pDev,
                                          LPSTISUBSCRIBE lpSubscribe)
{
    return STIERR_UNSUPPORTED;
}

static HRESULT WINAPI STIDEVICE_GetLastNotificationData(IStiDevice* pDev,
                                                        LPSTINOTIFY lpNotify)
{
    return STIERR_UNSUPPORTED;
}

static HRESULT WINAPI STIDEVICE_UnSubscribe(IStiDevice* pDev)
{
    return STIERR_UNSUPPORTED;
}

static HRESULT WINAPI STIDEVICE_GetLastErrorInfo(IStiDevice* pDev,
                                                 STI_ERROR_INFO* pLastErrorInfo)
{
    HRESULT ret;
    StiDevice *This = IMPL(StiDevice, iStiDevice, pDev);
    TRACE("(%p, %p)\n", pDev, pLastErrorInfo);
    ret = IStiUSD_GetLastErrorInfo(This->m_iStiUSD, pLastErrorInfo);
    return ret;
}

HRESULT CreateStiDevice(IUnknown *punkOuter, void **ppv)
{
    StiDevice *stiDev = (StiDevice*) malloc(sizeof(StiDevice));
    *ppv = 0;
    if (!stiDev)
        return E_OUTOFMEMORY;
    memset(stiDev, 0, sizeof(StiDevice));
    stiDev->iUnknown.lpVtbl = &UnknownVtbl;
    stiDev->iStiDevice.lpVtbl = &StiDeviceVtbl;
    stiDev->m_refCount = 1;
    stiDev->m_punkOuter = punkOuter;
    *ppv = &stiDev->iUnknown;
    return NOERROR;
}
