/*
 * 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"
//#include "/opt/xmingw/i386-mingw32msvc/include/setupapi.h"

//#include "msvcrt/stdio.h"

WINE_DEFAULT_DEBUG_CHANNEL(sti);

static const WCHAR regAppLaunchPath[] = {
    'S','O','F','T','W','A','R','E','\\',
    'M','i','c','r','o','s','o','f','t','\\',
    'W','i','n','d','o','w','s','\\',
    'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
    'S','t','i','l','l','I','m','a','g','e','\\',
    'R','e','g','i','s','t','e','r','e','d',' ',
        'A','p','p','l','i','c','a','t','i','o','n','s',0
};

        
/* Method forward declarations */
static HRESULT WINAPI UNKNOWN_QueryInterface(IUnknown *pUnk, REFIID riid,
                                             void **ppvObject);
static ULONG WINAPI UNKNOWN_AddRef(IUnknown *pUnk);
static ULONG WINAPI UNKNOWN_Release(IUnknown *pUnk);
static HRESULT WINAPI STI_QueryInterface(PSTI pSti, REFIID riid,
                                         void **ppvObject);
static ULONG WINAPI STI_AddRef(PSTI pSti);
static ULONG WINAPI STI_Release(PSTI pSti);
static HRESULT WINAPI STI_Initialize(PSTI pSti, HINSTANCE hinst,
                                     DWORD dwVersion);
static HRESULT WINAPI STIA_GetDeviceList(PSTIA pSti, DWORD dwType,
                                         DWORD dwFlags, DWORD *pdwItemsReturned,
                                         LPVOID *ppBuffer);
static HRESULT WINAPI STIW_GetDeviceList(PSTIW pSti, DWORD dwType,
                                         DWORD dwFlags, DWORD *pdwItemsReturned,
                                         LPVOID *ppBuffer);
static HRESULT WINAPI STIA_GetDeviceInfo(PSTIA pSti, LPCSTR pszDeviceName,
                                         LPVOID *ppBuffer);
static HRESULT WINAPI STIW_GetDeviceInfo(PSTIW pSti, LPWSTR pwszDeviceName,
                                         LPVOID *ppBuffer);
static HRESULT WINAPI STIA_CreateDevice(PSTIA pSti, LPCSTR pszDeviceName,
                                        DWORD dwMode, PSTIDEVICE *pDevice,
                                        LPUNKNOWN punkOuter);
static HRESULT WINAPI STIW_CreateDevice(PSTIW pSti, LPWSTR pwszDeviceName,
                                        DWORD dwMode, PSTIDEVICE *pDevice,
                                        LPUNKNOWN punkOuter);
static HRESULT WINAPI STIA_GetDeviceValue(PSTIA pSti, LPSTR pszDeviceName,
                                          LPSTR pValueName, LPDWORD pType,
                                          LPBYTE pData, LPDWORD cbData);
static HRESULT WINAPI STIW_GetDeviceValue(PSTIW pSti, LPWSTR pwszDeviceName,
                                          LPWSTR pValueName, LPDWORD pType,
                                          LPBYTE pData, LPDWORD cbData);
static HRESULT WINAPI STIA_SetDeviceValue(PSTIA pSti, LPSTR pszDeviceName,
                                          LPSTR pValueName, DWORD Type,
                                          LPBYTE pData, DWORD cbData);
static HRESULT WINAPI STIW_SetDeviceValue(PSTIW pSti, LPWSTR pwszDeviceName,
                                          LPWSTR pValueName, DWORD Type,
                                          LPBYTE pData, DWORD cbData);
static HRESULT WINAPI STIA_GetSTILaunchInformation(PSTIA pSti,
                                                   LPSTR pszDeviceName,
                                                   DWORD *pdwEventCode,
                                                   LPSTR pszEventName);
static HRESULT WINAPI STIW_GetSTILaunchInformation(PSTIW pSti,
                                                   LPWSTR pwszDeviceName,
                                                   DWORD *pdwEventCode,
                                                   LPWSTR pwszEventName);
static HRESULT WINAPI STIA_RegisterLaunchApplication(PSTIA pSti,
                                                     LPSTR pszAppName,
                                                     LPSTR pszCommandLine);
static HRESULT WINAPI STIW_RegisterLaunchApplication(PSTIW pSti,
                                                     LPWSTR pwszAppName,
                                                     LPWSTR pwszCommandLine);
static HRESULT WINAPI STIA_UnregisterLaunchApplication(PSTIA pSti,
                                                       LPSTR pszAppName);
static HRESULT WINAPI STIW_UnregisterLaunchApplication(PSTIW pSti,
                                                       LPWSTR pwszAppName);
static HRESULT WINAPI STIA_EnableHWNotifications(PSTIA pSti,
                                                 LPCSTR pszDeviceName,
                                                 BOOL bNewState);
static HRESULT WINAPI STIW_EnableHWNotifications(PSTIW pSti,
                                                 LPCWSTR pwszDeviceName,
                                                 BOOL bNewState);
static HRESULT WINAPI STIA_GetHWNotificationState(PSTIA pSti,
                                                  LPCSTR pszDeviceName,
                                                  BOOL *pbCurrentState);
static HRESULT WINAPI STIW_GetHWNotificationState(PSTIW pSti,
                                                  LPCWSTR pwszDeviceName,
                                                  BOOL *pbCurrentState);
static HRESULT WINAPI STIA_RefreshDeviceBus(PSTIA pSti, LPCSTR pszDeviceName);
static HRESULT WINAPI STIW_RefreshDeviceBus(PSTIW pSti, LPCWSTR pwszDeviceName);
static HRESULT WINAPI STIA_LaunchApplicationForDevice(PSTIA pSti,
                                                      LPSTR pszDeviceName,
                                                      LPSTR pszAppName,
                                                      LPSTINOTIFY pStiNotify);
static HRESULT WINAPI STIW_LaunchApplicationForDevice(PSTIW pSti,
                                                      LPWSTR pwszDeviceName,
                                                      LPWSTR pwszAppName,
                                                      LPSTINOTIFY pStiNotify);
static HRESULT WINAPI STIA_SetupDeviceParameters(PSTIA pSti,
                                                 PSTI_DEVICE_INFORMATIONA pDevInfo);
static HRESULT WINAPI STIW_SetupDeviceParameters(PSTIW pSti,
                                                 PSTI_DEVICE_INFORMATIONW pDevInfo);
static HRESULT WINAPI STIA_WriteToErrorLog(PSTIA pSti, DWORD dwMessageType,
                                           LPCSTR pszMessage);
static HRESULT WINAPI STIW_WriteToErrorLog(PSTIW pSti, DWORD dwMessageType,
                                           LPCWSTR pwszMessage);



/* Virtual method table declarations */

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

static IStillImageAVtbl StillImageAVtbl = {
    (HRESULT (WINAPI *)(PSTIA, REFIID, void**))
        STI_QueryInterface,
    (ULONG (WINAPI *)(PSTIA))	
        STI_AddRef,
    (ULONG (WINAPI *)(PSTIA))
        STI_Release,
    (HRESULT (WINAPI *)(PSTIA, HINSTANCE, DWORD))	
        STI_Initialize,
    STIA_GetDeviceList,
    STIA_GetDeviceInfo,
    STIA_CreateDevice,
    STIA_GetDeviceValue,
    STIA_SetDeviceValue,
    STIA_GetSTILaunchInformation,
    STIA_RegisterLaunchApplication,
    STIA_UnregisterLaunchApplication,
    STIA_EnableHWNotifications,
    STIA_GetHWNotificationState,
    STIA_RefreshDeviceBus,
    STIA_LaunchApplicationForDevice,
    STIA_SetupDeviceParameters,
    STIA_WriteToErrorLog
};

static IStillImageWVtbl StillImageWVtbl = {
    STI_QueryInterface,
    STI_AddRef,
    STI_Release,
    STI_Initialize,
    STIW_GetDeviceList,
    STIW_GetDeviceInfo,
    STIW_CreateDevice,
    STIW_GetDeviceValue,
    STIW_SetDeviceValue,
    STIW_GetSTILaunchInformation,
    STIW_RegisterLaunchApplication,
    STIW_UnregisterLaunchApplication,
    STIW_EnableHWNotifications,
    STIW_GetHWNotificationState,
    STIW_RefreshDeviceBus,
    STIW_LaunchApplicationForDevice,
    STIW_SetupDeviceParameters,
    STIW_WriteToErrorLog
};



/* Misc functions */

static HRESULT open_driver_key(LPCWSTR internalName, HKEY *driverKey)
{
    HKEY classKey;
    LONG ret;
   
    TRACE("internalName = %s, driverKey = %p", debugstr_w(internalName), driverKey);
 
    /* 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);
}

/* Fills in an STI_DEVICE_INFORMATIONW for a given internalName.
   The STI_DEVICE_INFORMATIONW struct must have all string pointers
   pointing to buffers at least STI_MAX_INTERNAL_NAME WCHARs long.
 */
static HRESULT get_device_details(LPCWSTR internalName,
                                  STI_DEVICE_INFORMATIONW *stiDevInfo)
{
    HKEY hkey;
    DWORD value, size;
    HRESULT hres;
    DWORD ret;
    const int lastPosition = STI_MAX_INTERNAL_NAME_LENGTH - 1;

    static const WCHAR str_Capabilities[] = {
        'C','a','p','a','b','i','l','i','t','i','e','s',0
    };
    static const WCHAR str_CreateFileName[] = {
        'C','r','e','a','t','e','F','i','l','e','N','a','m','e',0
    };
    static const WCHAR str_DeviceType[] = {
        'D','e','v','i','c','e','T','y','p','e',0
    };
    static const WCHAR str_DeviceSubType[] = {
        'D','e','v','i','c','e','S','u','b','T','y','p','e',0
    };
    static const WCHAR str_DriverDesc[] = {
        'D','r','i','v','e','r','D','e','s','c',0
    };
    static const WCHAR str_FriendlyName[] = {
        'F','r','i','e','n','d','l','y','N','a','m','e',0
    };
    static const WCHAR str_HardwareConfig[] = {
        'H','a','r','d','w','a','r','e','C','o','n','f','i','g',0
    };
    static const WCHAR str_PropertyPages[] = {
        'P','r','o','p','e','r','t','y','P','a','g','e','s',0
    };
    static const WCHAR str_Vendor[] = {
        'V','e','n','d','o','r',0
    };


    hres = open_driver_key(internalName, &hkey);
    if (FAILED(hres))
        return hres;

    /* Note: some registry entries are allowed to be missing (even on Windows) */

    /* Set size */
    stiDevInfo->dwSize = sizeof(STI_DEVICE_INFORMATIONW);
    
    /* Device type */
    size = sizeof(value);
    ret = RegQueryValueExW(hkey, str_DeviceType, 0, NULL, (BYTE*) &value, &size);
    if (ret != NO_ERROR)
        goto done;
    stiDevInfo->DeviceType = (value & 0xFFFF) << 16;
    size = sizeof(value);
    ret = RegQueryValueExW(hkey, str_DeviceSubType, 0, NULL, (BYTE*) &value, &size);
    if (ret != NO_ERROR)
        goto done;
    stiDevInfo->DeviceType |= (value & 0xFFFF);

    /* Internal name */
    strcpyW(stiDevInfo->szDeviceInternalName, internalName);

    /* Capabilities */
    size = sizeof(value);
    ret = RegQueryValueExW(hkey, str_Capabilities, 0, NULL, (BYTE*) &value, &size);
    if (ret == NO_ERROR)
        stiDevInfo->DeviceCapabilities.dwGeneric = value;

    /* Hardware config (USB, SCSI, ...) */
    size = sizeof(value);
    ret = RegQueryValueExW(hkey, str_HardwareConfig, 0, NULL, (BYTE*) &value, &size);
    if (ret != NO_ERROR)
        goto done;
    stiDevInfo->dwHardwareConfiguration = value;

    /* Vendor */
    size = STI_MAX_INTERNAL_NAME_LENGTH;
    RegQueryValueExW(hkey, str_Vendor, 0, NULL,
        (BYTE*) stiDevInfo->pszVendorDescription, &size);
    stiDevInfo->pszVendorDescription[lastPosition] = ((WCHAR) '\0');

    /* DriverDesc */
    size = STI_MAX_INTERNAL_NAME_LENGTH;
    RegQueryValueExW(hkey, str_DriverDesc, 0, NULL,
        (BYTE*) stiDevInfo->pszDeviceDescription, &size);
    stiDevInfo->pszDeviceDescription[lastPosition] = ((WCHAR) '\0');

    /* CreateFileName is crucial: without it can't CreateFile() the scanner! */
    size = STI_MAX_INTERNAL_NAME_LENGTH;
    ret = RegQueryValueExW(hkey, str_CreateFileName, 0, NULL,
        (BYTE*) stiDevInfo->pszPortName, &size);
    stiDevInfo->pszPortName[lastPosition] = ((WCHAR) '\0');
    if (ret != NO_ERROR)
        goto done;

    /* Property pages */
    size = STI_MAX_INTERNAL_NAME_LENGTH;
    ret = RegQueryValueExW(hkey, str_PropertyPages, 0, NULL,
        (BYTE*) stiDevInfo->pszPropProvider, &size);
    stiDevInfo->pszPropProvider[lastPosition] = ((WCHAR) '\0');

    /* FriendlyName */
    size = STI_MAX_INTERNAL_NAME_LENGTH;
    ret = RegQueryValueExW(hkey, str_FriendlyName, 0, NULL,
        (BYTE*) stiDevInfo->pszLocalName, &size);
    stiDevInfo->pszLocalName[lastPosition] = ((WCHAR) '\0');

done:
    RegCloseKey(hkey);
    hres = HRESULT_FROM_WIN32(ret);
    return hres;
}

static void sti_device_information_W_to_A(STI_DEVICE_INFORMATIONW *stiDevInfoW,
                                          STI_DEVICE_INFORMATIONA *stiDevInfoA)
{
    stiDevInfoA->dwSize = sizeof(STI_DEVICE_INFORMATIONA);
    stiDevInfoA->DeviceType = stiDevInfoW[0].DeviceType;
    WideCharToMultiByte(CP_ACP, 0,
        stiDevInfoW->szDeviceInternalName, -1,
        stiDevInfoA->szDeviceInternalName, STI_MAX_INTERNAL_NAME_LENGTH,
        0, 0);
    stiDevInfoA->DeviceCapabilities = stiDevInfoW->DeviceCapabilities;
    stiDevInfoA->dwHardwareConfiguration = stiDevInfoW->dwHardwareConfiguration;
    WideCharToMultiByte(CP_ACP, 0,
        stiDevInfoW->pszVendorDescription, -1,
        stiDevInfoA->pszVendorDescription, STI_MAX_INTERNAL_NAME_LENGTH,
        0, 0);
    WideCharToMultiByte(CP_ACP, 0,
        stiDevInfoW->pszDeviceDescription, -1,
        stiDevInfoA->pszDeviceDescription, STI_MAX_INTERNAL_NAME_LENGTH,
        0, 0);
    WideCharToMultiByte(CP_ACP, 0,
        stiDevInfoW->pszPortName, -1,
        stiDevInfoA->pszPortName, STI_MAX_INTERNAL_NAME_LENGTH,
        0, 0);
    WideCharToMultiByte(CP_ACP, 0,
        stiDevInfoW->pszPropProvider, -1,
        stiDevInfoA->pszPropProvider, STI_MAX_INTERNAL_NAME_LENGTH,
        0, 0);
    WideCharToMultiByte(CP_ACP, 0,
        stiDevInfoW->pszLocalName, -1,
        stiDevInfoA->pszLocalName, STI_MAX_INTERNAL_NAME_LENGTH,
        0, 0);
}

static STI_DEVICE_INFORMATIONA* allocate_device_informationsA(int count)
{
    STI_DEVICE_INFORMATIONA *devinfos;

    devinfos = (STI_DEVICE_INFORMATIONA*)
        LocalAlloc(0, 6 * count * sizeof(STI_DEVICE_INFORMATIONA));
    if (devinfos)
    {
        int i;
        ZeroMemory(devinfos, 6 * count * sizeof(STI_DEVICE_INFORMATIONA));
        for (i = 0; i < count; ++i)
        {
            devinfos[i].pszVendorDescription =
                devinfos[count + i*5 + 0].szDeviceInternalName;
            devinfos[i].pszDeviceDescription =
                devinfos[count + i*5 + 1].szDeviceInternalName;
            devinfos[i].pszPortName =
                devinfos[count + i*5 + 2].szDeviceInternalName;
            devinfos[i].pszPropProvider =
                devinfos[count + i*5 + 3].szDeviceInternalName;
        		devinfos[i].pszLocalName =
                devinfos[count + i*5 + 4].szDeviceInternalName;
        }
    }
    return devinfos;
}

static STI_DEVICE_INFORMATIONW* allocate_device_informationsW(int count)
{
    STI_DEVICE_INFORMATIONW *devinfos;

    devinfos = (STI_DEVICE_INFORMATIONW*)
        LocalAlloc(0, 6 * count * sizeof(STI_DEVICE_INFORMATIONW));
    if (devinfos)
    {
        int i;
        ZeroMemory(devinfos, 6 * count * sizeof(STI_DEVICE_INFORMATIONW));
        for (i = 0; i < count; ++i)
        {
            devinfos[i].pszVendorDescription =
                devinfos[count + i*5 + 0].szDeviceInternalName;
            devinfos[i].pszDeviceDescription =
                devinfos[count + i*5 + 1].szDeviceInternalName;
            devinfos[i].pszPortName =
                devinfos[count + i*5 + 2].szDeviceInternalName;
            devinfos[i].pszPropProvider =
                devinfos[count + i*5 + 3].szDeviceInternalName;
        		devinfos[i].pszLocalName =
                devinfos[count + i*5 + 4].szDeviceInternalName;
        }
    }
    return devinfos;
}

/* Method implementations */

static HRESULT WINAPI UNKNOWN_QueryInterface(IUnknown *pUnk, REFIID riid,
                                             void **ppvObject)
{
    StillImage *This = IMPL(StillImage, 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_IStillImageW) || IsEqualIID(riid, &IID_IStillImageA))
        *ppvObject = &This->iStillImage;
    else
        return E_NOINTERFACE;
    UNKNOWN_AddRef(pUnk);
    return NOERROR;
}

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

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

static HRESULT WINAPI STI_QueryInterface(PSTI pSti, REFIID riid,
                                         void **ppvObject)
{
    StillImage *This = IMPL(StillImage, iStillImage, pSti);
    TRACE("(%p, %p, %p)\n", pSti, 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 STI_AddRef(PSTI pSti)
{
    StillImage *This = IMPL(StillImage, iStillImage, pSti);
    TRACE("(%p)\n", pSti);
    if (This->m_punkOuter)
        return This->m_punkOuter->lpVtbl->AddRef(This->m_punkOuter);
    else
        return UNKNOWN_AddRef(&This->iUnknown);
}

static ULONG WINAPI STI_Release(PSTI pSti)
{
    StillImage *This = IMPL(StillImage, iStillImage, pSti);
    TRACE("(%p)\n", pSti);
    if (This->m_punkOuter)
        return This->m_punkOuter->lpVtbl->Release(This->m_punkOuter);
    else
        return UNKNOWN_Release(&This->iUnknown);
}

static HRESULT WINAPI STI_Initialize(PSTI pSti, HINSTANCE hinst,
                                     DWORD dwVersion)
{
    StillImage *This = IMPL(StillImage, iStillImage, pSti);
    TRACE("(%p, %p, %lu)\n", pSti, hinst, dwVersion);
    This->m_hinst = hinst;
    This->m_refCount = 1;
    return STI_OK;
}

static HRESULT WINAPI STIA_GetDeviceList(PSTIA pSti, DWORD dwType,
                                         DWORD dwFlags, DWORD *pdwItemsReturned,
                                         LPVOID *ppBuffer)
{
    STI_DEVICE_INFORMATIONW *stiDevInfoW = 0;
    STI_DEVICE_INFORMATIONA *stiDevInfoA;
    HRESULT ret;
    int i, count;

    TRACE("(%p, %lu, %lu, %p %p)\n", pSti, dwType, dwFlags, pdwItemsReturned,
        ppBuffer);

    ret = STIW_GetDeviceList(0, dwType, dwFlags, pdwItemsReturned,
        (void**) &stiDevInfoW);
    if (ret != STI_OK)
        return ret;

    count = *pdwItemsReturned;
    stiDevInfoA = allocate_device_informationsA(count);
    if (!stiDevInfoA)
    {
        ret = STIERR_OUTOFMEMORY;
        goto done;
    }
    for (i = 0; i < count; ++i)
        sti_device_information_W_to_A(&stiDevInfoW[i], &stiDevInfoA[i]);
    *ppBuffer = stiDevInfoA;
    ret = STI_OK;

done:
    LocalFree(stiDevInfoW);
    return ret;
}

static HRESULT WINAPI STIW_GetDeviceList(PSTIW pSti, DWORD dwType,
                                         DWORD dwFlags, DWORD *pdwItemsReturned,
                                         LPVOID *ppBuffer)
{
    HDEVINFO devinfo;
    SP_DEVINFO_DATA dev;
    int i, count, ret;
    STI_DEVICE_INFORMATION *stiDevInfo;

    TRACE("(%p, %lu, %lu, %p %p)\n", pSti, dwType, dwFlags, pdwItemsReturned,
        ppBuffer);

    /* Get a list of all still image devices from SETUPAPI */
    devinfo = SetupDiGetClassDevsW(&GUID_DEVCLASS_IMAGE, NULL, NULL, DIGCF_DEVICEINTERFACE);
    if (devinfo == INVALID_HANDLE_VALUE)
        return STIERR_GENERIC;

    /* The brightsparks that made STI decided to put pointers in STI_DEVICE_INFORMATION.
     * Who manages the strings those pointers point to?
     * STI probably drags around some internal state...
     * What we'll do, is:
     *
     *
     *       real               fake
     * <---------------------><--------------------------->
     *                       +---------------+
     *                       |               |
     * 0          1          |2           3  v       4
     * +----------+----------|+-----------+----------+ ... 
     * |        | |         |*|  |&&&&    |  |&&&&   |
     * |        |*|         | |  |&&&&    |  |&&&&   |
     * +---------|+-----------+-----------+----------+ ...
     *           |               ^
     *           |               |
     *           +---------------+
     * So there is N real STI_DEVICE_INFORMATION structs,
     * followed by 5*N fake ones whose szDeviceInternalName field (&&&& above)
     * we point to from the real structures
     * and use to store those strings :-)
     * So, the caller manages the entire block of memory for us.
     * Not much space is wasted: 90% of a STI_DEVICE_INFORMATION is szDeviceInternalName
     */

    DWORD PROP_TYPE;

    /* Calculate memory needed. */
    for (count = 0; SetupDiEnumDeviceInfo(devinfo, count, &dev); ++count)
        ;

    /* Allocate memory */
    stiDevInfo = allocate_device_informationsW(count);
    if (!stiDevInfo)
    {
        ret = STIERR_OUTOFMEMORY;
        goto done;
    }

    /* Fill in device info */
    for (i = 0; i < count; ++i)
    {
        dev.cbSize = sizeof(dev);
        if (!SetupDiEnumDeviceInfo(devinfo, i, &dev))
            break;		/* Devices could have unplugged by now, it's not an error */


        if (!SetupDiGetDeviceRegistryPropertyW(devinfo, &dev, SPDRP_DRIVER, &PROP_TYPE,
                                               (BYTE*) stiDevInfo[i].szDeviceInternalName,
                                               STI_MAX_INTERNAL_NAME_LENGTH, NULL))
        {
	    TRACE("Error getting device properties\n");
            ret = GetLastError();
            goto done;
        }

	//TRACE("stiDevInfo[%d].szDeviceInternalName = %s\n", i, stiDevInfo[i].szDeviceInternalName);

        ret = get_device_details(stiDevInfo[i].szDeviceInternalName, &stiDevInfo[i]);
        if (ret != NO_ERROR)
            goto done;

    }
    *ppBuffer = stiDevInfo;
    *pdwItemsReturned = i;
    ret = NO_ERROR;

done:
    SetupDiDestroyDeviceInfoList(devinfo);

    if (TRACE_ON(sti) && ret == NO_ERROR)
    {
        int i;
        TRACE("returning %lu device(s):\n", *pdwItemsReturned);
        for (i=0; i < *pdwItemsReturned; ++i)
        {
            TRACE("\t%s\n", debugstr_w(
                ((STI_DEVICE_INFORMATIONW*)*ppBuffer)[i].szDeviceInternalName
            ));
        }
    }

    return ret;
}

static HRESULT WINAPI STIA_GetDeviceInfo(PSTIA pSti, LPCSTR pszDeviceName,
                                         LPVOID *ppBuffer)
{
    WCHAR deviceName[STI_MAX_INTERNAL_NAME_LENGTH];
    STI_DEVICE_INFORMATIONA *stiDevInfoA;
    STI_DEVICE_INFORMATIONW *stiDevInfoW;
    HRESULT ret;

    TRACE("(%p, %s, %p)\n", pSti, debugstr_a(pszDeviceName), ppBuffer);

    MultiByteToWideChar(CP_ACP, 0,
        pszDeviceName, -1,
        deviceName, STI_MAX_INTERNAL_NAME_LENGTH);
    ret = STIW_GetDeviceInfo(0, deviceName, (void**) &stiDevInfoW);
    if (ret != STI_OK)
        return ret;

    stiDevInfoA = allocate_device_informationsA(1);
    if (!stiDevInfoA)
    {
        ret = STIERR_OUTOFMEMORY;
        goto done;
    }
    sti_device_information_W_to_A(stiDevInfoW, stiDevInfoA);

    *ppBuffer = stiDevInfoA;
    ret = STI_OK;

done:
    LocalFree(stiDevInfoW);
    return ret;
}

static HRESULT WINAPI STIW_GetDeviceInfo(PSTIW pSti, LPWSTR pwszDeviceName,
                                         LPVOID *ppBuffer)
{
    HRESULT ret;
    STI_DEVICE_INFORMATIONW *stiDevInfo;
    
    TRACE("(%p, %s, %p)\n", pSti, debugstr_w(pwszDeviceName), ppBuffer);

    stiDevInfo = allocate_device_informationsW(1);
    if (!stiDevInfo)
        return STIERR_OUTOFMEMORY;
    ret = get_device_details(pwszDeviceName, stiDevInfo);
    if (SUCCEEDED(ret))
        *ppBuffer = stiDevInfo;
    else
        LocalFree(stiDevInfo);
    return ret;
}

static HRESULT WINAPI STIA_CreateDevice(PSTIA pSti, LPCSTR pszDeviceName,
                                        DWORD dwMode, PSTIDEVICE *pDevice,
                                        LPUNKNOWN punkOuter)
{
    WCHAR deviceName[STI_MAX_INTERNAL_NAME_LENGTH];
    TRACE("(%p, %s, %lu, %p, %p)\n", pSti, debugstr_a(pszDeviceName),
        dwMode, pDevice, punkOuter);
    MultiByteToWideChar(CP_ACP, 0,
        pszDeviceName, -1,
        deviceName, STI_MAX_INTERNAL_NAME_LENGTH);
    return STIW_CreateDevice((PSTIW) pSti, deviceName, dwMode, pDevice, punkOuter);
}

static HRESULT WINAPI STIW_CreateDevice(PSTIW pSti, LPWSTR pwszDeviceName,
                                        DWORD dwMode, PSTIDEVICE *pDevice,
                                        LPUNKNOWN punkOuter)
{	/* There are 2 ways this could be done:
        - Make a wine-specific StiDevice implementation using libusb etc.
          Has the advantage of being universally supported, but only works
          assuming that the manufacturer's StiDevice just plainly calls
          CreateFile(), DeviceIoControl(), ReadFile() and WriteFile(). If
          the manufacturer's StiDevice does other important stuff, or the
          TWAIN components don't even use STI, it won't work (for my scanner,
          they just call IStillImage::GetDeviceList() followed by
          CreateFile(IStillImage::GetDeviceValue("PortName")); I am guessing
          some other scanner driver must do the same).
    
        - Load the manufacturer's StiDevice implementation. If wine supported
          device drivers, this would work. It's a good option for ReactOS,
          though.
    
        I'll do both (seeing as most wine developers have something against
        device drivers in wine), with emphasis on the second way.
     */
    HRESULT ret;
    StillImage *This = IMPL(StillImage, iStillImage, pSti);

    TRACE("(%p, %s, %lu, %p, %p)\n", pSti, debugstr_w(pwszDeviceName), dwMode,
        pDevice, punkOuter);
    ret = CreateStiDevice(punkOuter, (void**) pDevice);
    if (ret != STI_OK)
        return ret;
    ret = IStiDevice_Initialize(*pDevice, This->m_hinst, pwszDeviceName,
                                STI_VERSION, dwMode);
    return ret;
}

static HRESULT WINAPI STIA_GetDeviceValue(PSTIA pSti, LPSTR pszDeviceName,
                                          LPSTR pValueName, LPDWORD pType,
                                          LPBYTE pData, LPDWORD cbData)
{
    WCHAR deviceName[STI_MAX_INTERNAL_NAME_LENGTH];
    WCHAR valueName[STI_MAX_INTERNAL_NAME_LENGTH];
    TRACE("(%p, %s, %s, %p, %p, %p)\n", pSti, debugstr_a(pszDeviceName),
        debugstr_a(pValueName), pType, pData, cbData);
    MultiByteToWideChar(CP_ACP, 0,
        pszDeviceName, -1,
        deviceName, STI_MAX_INTERNAL_NAME_LENGTH);
    MultiByteToWideChar(CP_ACP, 0,
        pValueName, -1,
        valueName, STI_MAX_INTERNAL_NAME_LENGTH);
    return STIW_GetDeviceValue(0, deviceName, valueName, pType, pData, cbData);
}

static HRESULT WINAPI STIW_GetDeviceValue(PSTIW pSti, LPWSTR pwszDeviceName,
                                          LPWSTR pValueName, LPDWORD pType,
                                          LPBYTE pData, LPDWORD cbData)
{
    DWORD ret;
    HRESULT hres;
    HKEY driverKey = 0, deviceData = 0;

    TRACE("(%p, %s, %s, %p, %p, %p)\n", pSti, debugstr_w(pwszDeviceName),
        debugstr_w(pValueName), pType, pData, cbData);

    hres = open_driver_key(pwszDeviceName, &driverKey);
    if (FAILED(hres))
        return hres;

    ret = RegOpenKeyA(driverKey, "DeviceData", &deviceData);
    if (ret == NO_ERROR)
    {
        ret = RegQueryValueExW(deviceData, pValueName, 0, pType, pData, cbData);
        RegCloseKey(deviceData);
    }
    RegCloseKey(driverKey);
    return HRESULT_FROM_WIN32(ret);
}

static HRESULT WINAPI STIA_SetDeviceValue(PSTIA pSti, LPSTR pszDeviceName,
                                          LPSTR pValueName, DWORD Type,
                                          LPBYTE pData, DWORD cbData)
{
    WCHAR deviceName[STI_MAX_INTERNAL_NAME_LENGTH];
    WCHAR valueName[STI_MAX_INTERNAL_NAME_LENGTH];
    TRACE("(%p, %s, %s, %lu, %p, %lu)\n", pSti, debugstr_a(pszDeviceName),
        debugstr_a(pValueName), Type, pData, cbData);
    MultiByteToWideChar(CP_ACP, 0,
        pszDeviceName, -1,
        deviceName, STI_MAX_INTERNAL_NAME_LENGTH);
    MultiByteToWideChar(CP_ACP, 0,
        pValueName, -1,
        valueName, STI_MAX_INTERNAL_NAME_LENGTH);
    return STIW_SetDeviceValue(0, deviceName, valueName, Type, pData, cbData);
}

static HRESULT WINAPI STIW_SetDeviceValue(PSTIW pSti, LPWSTR pwszDeviceName,
                                          LPWSTR pValueName, DWORD Type,
                                          LPBYTE pData, DWORD cbData)
{
    HRESULT hres;
    DWORD ret;
    HKEY driverKey, deviceData;

    TRACE("(%p, %s, %s, %lu, %p, %lu)\n", pSti, debugstr_w(pwszDeviceName),
        debugstr_w(pValueName), Type, pData, cbData);

    hres = open_driver_key(pwszDeviceName, &driverKey);
    if (FAILED(hres))
        return hres;

    ret = RegOpenKeyA(driverKey, "DeviceData", &deviceData);
    if (ret == NO_ERROR)
    {
        ret = RegSetValueExW(deviceData, pValueName, 0, Type, pData, cbData);
        RegCloseKey(deviceData);
    }
    RegCloseKey(driverKey);
    return HRESULT_FROM_WIN32(ret);
}

static HRESULT WINAPI STIA_GetSTILaunchInformation(PSTIA pSti,
                                                   LPSTR pszDeviceName,
                                                   DWORD *pdwEventCode,
                                                   LPSTR pszEventName)
{
    HRESULT ret;
    WCHAR deviceName[STI_MAX_INTERNAL_NAME_LENGTH];
    WCHAR eventName[STI_MAX_INTERNAL_NAME_LENGTH];

    TRACE("(%p, %p, %p, %p)\n", pSti, pszDeviceName,
          pdwEventCode, pszEventName);

    ret = STIW_GetSTILaunchInformation(0, deviceName, pdwEventCode, eventName);
    if (ret == STI_OK)
    {
        WideCharToMultiByte(CP_ACP, 0, deviceName, -1, pszDeviceName,
                            STI_MAX_INTERNAL_NAME_LENGTH, 0, 0);
        WideCharToMultiByte(CP_ACP, 0, eventName, -1, pszEventName,
                            STI_MAX_INTERNAL_NAME_LENGTH, 0, 0);
    }
    return ret;
}

static HRESULT WINAPI STIW_GetSTILaunchInformation(PSTIW pSti,
                                                   LPWSTR pwszDeviceName,
                                                   DWORD *pdwEventCode,
                                                   LPWSTR pwszEventName)
{
    WCHAR *commandLine;
    int commandLineLength;
    WCHAR stiDevice[] = {'/','S','t','i','D','e','v','i','c','e',':',0}, *stiDevicePos;
    WCHAR stiEvent[] = {'/','S','t','i','E','v','e','n','t',':',0}, *stiEventPos;
    int start, length;

    TRACE("(%p, %p, %p, %p)\n", pSti, pwszDeviceName,
        pdwEventCode, pwszEventName);

    commandLine = GetCommandLineW();
    commandLineLength = strlenW(commandLine);
    
    /* Extract the device name */
    stiDevicePos = strstrW(commandLine, stiDevice);
    if (!stiDevicePos)
        return STIERR_GENERIC;
    start = stiDevicePos - commandLine + strlenW(stiDevice);
    length = 0;
    while (length < (STI_MAX_INTERNAL_NAME_LENGTH - 1)
        && start + length < commandLineLength
        && commandLine[start + length] != ((WCHAR) ' '))
    {
        pwszDeviceName[length] = commandLine[start + length];
        ++length;
    }
    pwszDeviceName[length] = (WCHAR) '\0';

    /* Extract the event */
    stiEventPos = strstrW(commandLine, stiEvent);
    if (!stiEventPos)
        return STIERR_GENERIC;
    start = stiEventPos - commandLine + strlenW(stiEvent);
    if (start != ((WCHAR)'{'))
        return STIERR_GENERIC;
    length = 0;
    /* {5EFC7975-14BC-11CF-9B2B-00AA00573819}
     *           1         2         3
     * 01234567890123456789012345678901234567
     * So a string GUID is 38 bytes long
     */
    while (length < 38
        && start + length < commandLineLength
        && commandLine[start + length] != ((WCHAR) '}'))
    {
        pwszEventName[length] = commandLine[start + length];
        ++length;
    }
    pwszEventName[length] = (WCHAR) '\0';
    if (pwszEventName[38] != ((WCHAR)'}'))
        return STIERR_GENERIC;

    return STI_OK;
}

static HRESULT WINAPI STIA_RegisterLaunchApplication(PSTIA pSti,
                                                     LPSTR pszAppName,
                                                     LPSTR pszCommandLine)
{
    WCHAR appName[STI_MAX_INTERNAL_NAME_LENGTH];
    WCHAR commandLine[STI_MAX_INTERNAL_NAME_LENGTH];
    TRACE("(%p, %s, %s)\n", pSti, debugstr_a(pszAppName),
        debugstr_a(pszCommandLine));
    MultiByteToWideChar(CP_ACP, 0,
        pszAppName, -1,
        appName, STI_MAX_INTERNAL_NAME_LENGTH);
    MultiByteToWideChar(CP_ACP, 0,
        pszCommandLine, -1,
        commandLine, STI_MAX_INTERNAL_NAME_LENGTH);
    return STIW_RegisterLaunchApplication(0, appName, commandLine);
}

static HRESULT WINAPI STIW_RegisterLaunchApplication(PSTIW pSti,
                                                     LPWSTR pwszAppName,
                                                     LPWSTR pwszCommandLine)
{
    HKEY apps;
    LONG result;

    TRACE("(%p, %s, %s)\n", pSti, debugstr_w(pwszAppName),
        debugstr_w(pwszCommandLine));

    result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, regAppLaunchPath, 0, 0, &apps);
    if (result != ERROR_SUCCESS)
        return STIERR_GENERIC;
    result = RegSetValueExW(apps, pwszAppName, 0, REG_EXPAND_SZ,
        (BYTE*) pwszCommandLine, (1 + strlenW(pwszCommandLine)) * sizeof(WCHAR));
    RegCloseKey(apps);
    if (result != ERROR_SUCCESS)
        return STIERR_GENERIC;
    else
        return STI_OK;
}

static HRESULT WINAPI STIA_UnregisterLaunchApplication(PSTIA pSti,
                                                       LPSTR pszAppName)
{
    WCHAR appName[STI_MAX_INTERNAL_NAME_LENGTH];
    TRACE("(%p, %s)\n", pSti, debugstr_a(pszAppName));
    MultiByteToWideChar(CP_ACP, 0,
        pszAppName, -1,
        appName, STI_MAX_INTERNAL_NAME_LENGTH);
    return STIW_UnregisterLaunchApplication(0, appName);
}

static HRESULT WINAPI STIW_UnregisterLaunchApplication(PSTIW pSti,
                                                       LPWSTR pwszAppName)
{
    HKEY apps;
    LONG result;

    TRACE("(%p, %s)\n", pSti, debugstr_w(pwszAppName));

    result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, regAppLaunchPath, 0, 0, &apps);
    if (result != ERROR_SUCCESS)
        return STIERR_GENERIC;
    result = RegDeleteValueW(apps, pwszAppName);
    RegCloseKey(apps);
    if (result != ERROR_SUCCESS)
        return STIERR_GENERIC;
    else
        return STI_OK;	
}

static HRESULT WINAPI STIA_EnableHWNotifications(PSTIA pSti,
                                                 LPCSTR pszDeviceName,
                                                 BOOL bNewState)
{
    FIXME("(%p, %s, %lu): stub\n", pSti, debugstr_a(pszDeviceName), 
        (unsigned long) bNewState);
    return STIERR_UNSUPPORTED;
}

static HRESULT WINAPI STIW_EnableHWNotifications(PSTIW pSti,
                                                 LPCWSTR pwszDeviceName,
                                                 BOOL bNewState)
{
    FIXME("(%p, %s, %lu): stub\n", pSti, debugstr_w(pwszDeviceName), (DWORD)bNewState);
    return STIERR_UNSUPPORTED;
}

static HRESULT WINAPI STIA_GetHWNotificationState(PSTIA pSti,
                                                  LPCSTR pszDeviceName,
                                                  BOOL *pbCurrentState)
{
    FIXME("(%p, %s, %p): stub\n", pSti, debugstr_a(pszDeviceName), pbCurrentState);
    return STIERR_UNSUPPORTED;
}

static HRESULT WINAPI STIW_GetHWNotificationState(PSTIW pSti,
                                                  LPCWSTR pwszDeviceName,
                                                  BOOL *pbCurrentState)
{
    FIXME("(%p, %s, %p): stub\n", pSti, debugstr_w(pwszDeviceName), pbCurrentState);
    return STIERR_UNSUPPORTED;
}

static HRESULT WINAPI STIA_RefreshDeviceBus(PSTIA pSti, LPCSTR pszDeviceName)
{
    FIXME("(%p, %s): stub\n", pSti, debugstr_a(pszDeviceName));
    return STIERR_UNSUPPORTED;
}

static HRESULT WINAPI STIW_RefreshDeviceBus(PSTIW pSti, LPCWSTR pwszDeviceName)
{
    FIXME("(%p, %s): stub\n", pSti, debugstr_w(pwszDeviceName));
    return STIERR_UNSUPPORTED;
}

static HRESULT WINAPI STIA_LaunchApplicationForDevice(PSTIA pSti,
                                                      LPSTR pszDeviceName,
                                                      LPSTR pszAppName,
                                                      LPSTINOTIFY pStiNotify)
{
    WCHAR deviceName[STI_MAX_INTERNAL_NAME_LENGTH];
    WCHAR appName[STI_MAX_INTERNAL_NAME_LENGTH];
    TRACE("(%p, %s, %s, %p)\n", pSti, debugstr_a(pszDeviceName),
        debugstr_a(pszAppName), pStiNotify);
    MultiByteToWideChar(CP_ACP, 0,
        pszDeviceName, -1,
        deviceName, STI_MAX_INTERNAL_NAME_LENGTH);
    MultiByteToWideChar(CP_ACP, 0,
        pszAppName, -1,
        appName, STI_MAX_INTERNAL_NAME_LENGTH);
    return STIW_LaunchApplicationForDevice(0, deviceName, appName, pStiNotify);
}

static HRESULT WINAPI STIW_LaunchApplicationForDevice(PSTIW pSti,
                                                      LPWSTR pwszDeviceName,
                                                      LPWSTR pwszAppName,
                                                      LPSTINOTIFY pStiNotify)
{
    HKEY apps;
    DWORD type;
    DWORD size = STI_MAX_INTERNAL_NAME_LENGTH;
    WCHAR app[STI_MAX_INTERNAL_NAME_LENGTH];
    WCHAR buffer[STI_MAX_INTERNAL_NAME_LENGTH];
    LONG result;
    WCHAR *pos1, *pos2;
    STARTUPINFOW sinfo;
    PROCESS_INFORMATION pinfo;

    const WCHAR device[] = {'/','S','t','i','D','e','v','i','c','e',':','%','1',0};
    const WCHAR event[] = {'/','S','t','i','E','v','e','n','t',':','%','2',0};
    const WCHAR one[] = {'%','1',0};
    const WCHAR two[] = {'%','2',0};
    const WCHAR pattern[] = {'%','l','s','%','l','s','%','l','s','%','l','s','%','l','s',0};

    TRACE("(%p, %s, %s)\n", pSti, debugstr_w(pwszDeviceName),
        debugstr_w(pwszAppName));

    /* Read in the application path from the registry */
    result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, regAppLaunchPath, 0, 0, &apps);
    if (result != ERROR_SUCCESS)
        return STIERR_GENERIC;
    result = RegQueryValueExW(apps, pwszAppName, 0, &type, (BYTE*)app, &size);
    RegCloseKey(apps);
    if (result != ERROR_SUCCESS)
        return STIERR_GENERIC;
    
    /* %1 and %2 better not occur anywhere else in the command line arguments */
    pos1 = strstrW(app, one);
    *pos1 = 0;
    pos1 += 2;
    pos2 = strstrW(app, two);
    *pos2 = 0;
    pos2 += 2;
    snprintfW(buffer, STI_MAX_INTERNAL_NAME_LENGTH,
        pattern, app, pwszDeviceName, pos1, pos2, event);

    /* Start the application */
    TRACE(": Executing [%s]\n", debugstr_w(buffer));
    ZeroMemory(&sinfo, sizeof(sinfo));
    sinfo.cb = sizeof(STARTUPINFOW);
    if (!CreateProcessW(0, buffer, 0, 0, FALSE, 0, 0, 0, &sinfo, &pinfo))
        return STIERR_GENERIC;

    return STI_OK;
}

static HRESULT WINAPI STIA_SetupDeviceParameters(PSTIA pSti,
                                                 PSTI_DEVICE_INFORMATIONA pDevInfo)
{
    FIXME("(%p, %p): stub\n", pSti, pDevInfo);
    return STIERR_UNSUPPORTED;
}

static HRESULT WINAPI STIW_SetupDeviceParameters(PSTIW pSti,
                                                 PSTI_DEVICE_INFORMATIONW pDevInfo)
{
    FIXME("(%p, %p): stub\n", pSti, pDevInfo);
    return STIERR_UNSUPPORTED;
}

static HRESULT WINAPI STIA_WriteToErrorLog(PSTIA pSti, DWORD dwMessageType,
                                           LPCSTR pszMessage)
{
    char type;
    SYSTEMTIME time;
    StillImage *This = IMPL(StillImage, iStillImage, pSti);
    TRACE("(%p, %lu, %s)\n", pSti, dwMessageType, debugstr_a(pszMessage));
    switch (dwMessageType)
    {
        case STI_TRACE_INFORMATION: type = 'i'; break;
        case STI_TRACE_WARNING: type = 'w'; break;
        case STI_TRACE_ERROR: type = 'e'; break;
        default: type = '?';
    }
    GetLocalTime(&time);
    fprintf(logFile, "%c %s [%s::%p] %02u:%02u:%02u %s\n", type, "<TracerName>",
        "<Process>", This->m_hinst, time.wHour, time.wMinute, time.wSecond,
        pszMessage);
    return STI_OK;
}

static HRESULT WINAPI STIW_WriteToErrorLog(PSTIW pSti, DWORD dwMessageType,
                                           LPCWSTR pwszMessage)
{
    CHAR *pszMessage;
    INT size;
    size = WideCharToMultiByte(CP_ACP, 0, pwszMessage, -1, 0, 0, 0, 0);
    TRACE("(%p, %lu, %s)\n", pSti, dwMessageType, debugstr_w(pwszMessage));
    pszMessage = (CHAR*) malloc(size);
    WideCharToMultiByte(CP_ACP, 0, pwszMessage, size, pszMessage, size, 0, 0);
    STIA_WriteToErrorLog((PSTIA) pSti, dwMessageType, pszMessage);
    free(pszMessage);
    return STI_OK;
}



/* API functions */

/******************************************************************************
 *           StiCreateInstance   (STI.@)
 */
HRESULT WINAPI StiCreateInstance(HINSTANCE hinst, DWORD dwVer,
                                 PSTI *ppSti, LPUNKNOWN punkOuter)
{
    StillImage *This;

    TRACE("(%p, %lu, %p, %p)\n", hinst, dwVer, ppSti, punkOuter);
    
    /* COM wants this */
    *ppSti = 0;
    
    /* Check version */
    if (GET_STIVER_MINOR(dwVer) < GET_STIVER_MINOR(STI_VERSION_MIN_ALLOWED))
        return STIERR_BETA_VERSION;

    /* Allocate memory for IStillImage implementation */
    This = (StillImage*) malloc(sizeof(StillImage));
    if (!This)
        return STIERR_OUTOFMEMORY;

    /* Fill virtual method table */
    if ((dwVer & STI_VERSION_FLAG_MASK) == STI_VERSION_FLAG_UNICODE)
        This->iStillImage.lpVtbl = &StillImageWVtbl;
    else
        This->iStillImage.lpVtbl = (IStillImageWVtbl*) &StillImageAVtbl;

    /* Initialize object */
    This->iUnknown.lpVtbl = &UnknownVtbl;
    This->m_punkOuter = punkOuter;
    STI_Initialize(&This->iStillImage, hinst, dwVer);

    /* Done */
    *ppSti = &This->iStillImage;
    return STI_OK;
}

/******************************************************************************
 *           StiCreateInstanceA   (STI.@)
 */
STDMETHODIMP StiCreateInstanceA(HINSTANCE hinst, DWORD dwVer,
                                PSTIA *ppSti, LPUNKNOWN punkOuter)
{
    TRACE("(%p, %lu, %p, %p)\n", hinst, dwVer, ppSti, punkOuter);
    return StiCreateInstance(hinst,
                             dwVer | (dwVer & ~STI_VERSION_FLAG_UNICODE),
                             (PSTIW*) ppSti,
                             punkOuter);
}

/******************************************************************************
 *           StiCreateInstanceW   (STI.@)
 */
STDMETHODIMP StiCreateInstanceW(HINSTANCE hinst, DWORD dwVer,
                                PSTIW *ppSti, LPUNKNOWN punkOuter)
{
    TRACE("(%p, %lu, %p, %p)\n", hinst, dwVer, ppSti, punkOuter);
    return StiCreateInstance(hinst, (dwVer | STI_VERSION_FLAG_UNICODE),
                             ppSti, punkOuter);
}
