/*
 * Copyright (C) 2005 Damjan Jovanovic (on his own :-)
 * Copyright (C) 2002 Aric Stewart for CodeWeavers
 *
 * 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
 */

/* Apparently C++ code is not welcome in Wine.
 * Good luck to whoever has to maintain this C COM hell :-).
 */

#include <stdarg.h>
#include "windef.h"
#include "winbase.h"
#include "winreg.h"
#include "winerror.h"
#include "winnls.h"
#include "wine/debug.h"
#include "msvcrt/wchar.h"

#define COBJMACROS
#include "initguid.h"
#include "objbase.h"
#include "unknwn.h"
#include "sti.h"
#include "unknwn.h"
#include "sti_internal.h"

WINE_DEFAULT_DEBUG_CHANNEL(sti);

/* Globals */
static void SetVersionDependantData();

static LONG dllReferenceCount = 0;	/* Reference count for entire DLL */
static BOOL isWin32 = FALSE;		/* Is it Windows 95/98? */
FILE *logFile = 0;					/* File handle of C:\Windows\Sti_log.txt */
WCHAR *regStillImageDevices;		/* Registry section for devices */

//static struct list deviceList = LIST_INIT(deviceList);

WCHAR WINNT_STI_REG_DEVICES[] = {	
'S','y','s','t','e','m','\\',
'C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\',
'C','o','n','t','r','o','l','\\','C','l','a','s','s','\\',
'{','6','b','d','d','1','f','c','6','-',
'8','1','0','f','-',
'1','1','d','0','-',
'b','e','c','7','-',
'0','8','0','0','2','b','e','2','0','9','2','f','}',
0
};

WCHAR WIN9x_STI_REG_DEVICES[] = {
'S','y','s','t','e','m','\\',
'C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\',
'S','e','r','v','i','c','e','s','\\',
'C','l','a','s','s','\\',
'I','m','a','g','e',
0
};








/** Purely internal functions */
static void Deinitialize()
{
	fclose(logFile);
}

static void Initialize()
{
	logFile = fopen("C:\\windows\\sti_trace.log", "a");
	fprintf(logFile,
		"Severity TracerName [Process::ThreadId] Time MessageText\n");
	SetVersionDependantData();
}
#if 0
static void LoadDeviceList()
{
	Device *dev;
	HKEY key;
	WCHAR name[STI_MAX_INTERNAL_NAME_LENGTH];
	CHAR buffer[STI_MAX_INTERNAL_NAME_LENGTH];
	DWORD result;
	DWORD i;

	result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, regStillImageDevices, 0,
		KEY_READ, &key);
	if (result != ERROR_SUCCESS) {
		TRACE("Error opening %s\n", debugstr_w(regStillImageDevices));
		return;
	}
	
	i = 0;
	result = RegEnumKeyW(key, i, name, STI_MAX_INTERNAL_NAME_LENGTH);
	if (result != ERROR_SUCCESS) {
		TRACE("Error opening %s\n", debugstr_w(name));
		return;
	}

	while (result == ERROR_SUCCESS) {
		HKEY scanner;
		DWORD type, value, size;

		++i;
		dev = (Device*) malloc(sizeof(Device));
		if (!dev)
			return;
		dev->info.dwSize = sizeof(STI_DEVICE_INFORMATIONW);
		
		result = RegOpenKeyExW(key, name, 0, KEY_READ, &scanner);
		if (result != ERROR_SUCCESS) {
			free(dev);
			return;
		}
		
		size = sizeof(DWORD);
		result = RegQueryValueExA(scanner, "DeviceType", 0, &type, (BYTE*) &value, &size);
		if (result != ERROR_SUCCESS) {
			TRACE("ERROR %lu\n", result);
		}
		dev->info.DeviceType = value << 16;
		size = sizeof(DWORD);
		RegQueryValueExA(scanner, "DeviceSubType", 0, &type, (BYTE*) &value, &size);
		dev->info.DeviceType |= (value & 0xFFFF);

		strcpyW(dev->info.szDeviceInternalNames, name);
		
		size = sizeof(DWORD);
		RegQueryValueExA(scanner, "Capabilities", 0, &type,
			(BYTE*) &dev->info.DeviceCapabilities.dwGeneric, &size);

		size = sizeof(DWORD);
		result = RegQueryValueExA(scanner, "HardwareConfig", 0, &type,
			(BYTE*) &dev->info.dwHardwareConfiguration, &size);
		if (result != ERROR_SUCCESS) {
			TRACE("ERROR %lu\n", result);
		}
		
		/* FIXME: Is this the "VendorDescription" or "ProviderName" key? */
		size = STI_MAX_INTERNAL_NAME_LENGTH;
		result = RegQueryValueExA(scanner, "Vendor", 0, &type,
			(BYTE*) &buffer, &size);
		if (result != ERROR_SUCCESS) {
			TRACE("ERROR %lu\n", result);
		}
		MultiByteToWideChar(CP_ACP, 0,
			buffer, -1,
			dev->szVendorDescription, size);
		dev->info.pszVendorDescription = dev->szVendorDescription;
		
		size = STI_MAX_INTERNAL_NAME_LENGTH;
		RegQueryValueExA(scanner, "DriverDesc", 0, &type,
			(BYTE*) buffer, &size);
		MultiByteToWideChar(CP_ACP, 0,
			buffer, -1,
			dev->szDeviceDescription, size);
		dev->info.pszDeviceDescription = dev->szDeviceDescription;
		
		/* FIXME: This is only set when the device is detected */
		dev->szPortName[0] = 0;
if (i == 1) {
	static WCHAR name[] = 
{'\\','\\','.','\\','U','S','B','S','C','A','N','0',0};
	memcpy(dev->szPortName, name, 26);
}
		dev->info.pszPortName = dev->szPortName;
		
		/* FIXME: What goes here? */
		dev->szPropProvider[0] = 0;
		dev->info.pszPropProvider = dev->szPropProvider;
		
		size = STI_MAX_INTERNAL_NAME_LENGTH;
		RegQueryValueExA(scanner, "FriendlyName", 0, &type,
			(BYTE*) buffer, &size);
		MultiByteToWideChar(CP_ACP, 0,
			buffer, -1,
			dev->szLocalName, size);
		dev->info.pszLocalName = dev->szLocalName;
		
		dev->isHWNotificationEnabled = TRUE;
		
		STI_AddDevice(dev);

		RegCloseKey(scanner);
		result = RegEnumKeyW(key, i, name, STI_MAX_INTERNAL_NAME_LENGTH);
	}
	
	RegCloseKey(key);
}
#endif
static void SetVersionDependantData()
{
	OSVERSIONINFOA osVerInfo;

	osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo);
	isWin32 = FALSE;
	if (GetVersionExA(&osVerInfo) &&
		(osVerInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS))
			isWin32 = TRUE;
	
	if (isWin32) {
		regStillImageDevices = WIN9x_STI_REG_DEVICES;
	} else {
		regStillImageDevices = WINNT_STI_REG_DEVICES;
	}
}


static IClassFactory *factory = NULL;
static DWORD dllRefCount = 0;

typedef HRESULT (*CreationFunction)(IUnknown *, REFIID, void **);
typedef struct {
	/* Exposed interfaces */
	IClassFactory iClassFactory;
	
	/* Fields */
	int m_refCount;
	CreationFunction m_creationFunction;
} ClassFactory;


static HRESULT STDMETHODCALLTYPE FACTORY_QueryInterface(IClassFactory *icf, REFIID riid,
														void **ppv);
static ULONG   STDMETHODCALLTYPE FACTORY_AddRef(IClassFactory *icf);
static ULONG   STDMETHODCALLTYPE FACTORY_Release(IClassFactory *icf);
static HRESULT STDMETHODCALLTYPE FACTORY_CreateInstance(IClassFactory *icf,
														IUnknown *punkOuter,
														REFIID riid, void **ppv);
static HRESULT STDMETHODCALLTYPE FACTORY_LockServer(IClassFactory *icf, BOOL flock);

static IClassFactoryVtbl FactoryVtbl = {
	FACTORY_QueryInterface,
	FACTORY_AddRef,
	FACTORY_Release,
	FACTORY_CreateInstance,
	FACTORY_LockServer
};

static HRESULT STDMETHODCALLTYPE FACTORY_QueryInterface(IClassFactory *icf,
														REFIID riid, void **ppv)
{
	ClassFactory *This = IMPL(ClassFactory, iClassFactory, icf);
	if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IClassFactory))
		*ppv = &This->iClassFactory;
	else
	{
		*ppv = 0;
		return E_NOINTERFACE;
	}
	IClassFactory_AddRef((IClassFactory*) ppv);
	return NOERROR;
}

static ULONG STDMETHODCALLTYPE FACTORY_AddRef(IClassFactory *icf)
{
	ClassFactory *This = IMPL(ClassFactory, iClassFactory, icf);
	InterlockedIncrement(&dllRefCount);
	return ++This->m_refCount;
}

static ULONG STDMETHODCALLTYPE FACTORY_Release(IClassFactory *icf)
{
	ClassFactory *This = IMPL(ClassFactory, iClassFactory, icf);
	InterlockedDecrement(&dllRefCount);
	if (--This->m_refCount == 0)
	{
		free(This);
		return 0;
	}
	return This->m_refCount;
}

static HRESULT STDMETHODCALLTYPE FACTORY_CreateInstance(IClassFactory *icf,
														IUnknown *punkOuter,
														REFIID riid, void **ppv)
{
	ClassFactory *This = IMPL(ClassFactory, iClassFactory, icf);
	return (*This->m_creationFunction)(punkOuter, riid, ppv);
}

static HRESULT STDMETHODCALLTYPE FACTORY_LockServer(IClassFactory *icf, BOOL flock)
{
	if (flock)
		InterlockedIncrement(&dllRefCount);
	else
		InterlockedDecrement(&dllRefCount);
	return NOERROR;
}

static HRESULT CreateClassFactory(REFCLSID rclsid, CreationFunction creationFunction,
								  REFIID riid, void **ppv)
{
	ClassFactory *This;
	HRESULT ret;
	
	*ppv = 0;
	This = (ClassFactory*) malloc(sizeof(ClassFactory));
	if (!This)
		return E_OUTOFMEMORY;
	This->iClassFactory.lpVtbl = &FactoryVtbl;
	This->m_refCount = 1;
	This->m_creationFunction = creationFunction;
	ret = IClassFactory_QueryInterface(&This->iClassFactory, riid, ppv);
	IClassFactory_Release(&This->iClassFactory);
	return ret;
}

static HRESULT CreateStillImageInterface(IUnknown *punkOuter, REFIID riid, void **ppv)
{
	if (IsEqualIID(riid, &IID_IStillImageW))
		return StiCreateInstanceW(GetCurrentProcess(), STI_VERSION, (PSTIW) ppv, punkOuter);
	else if (IsEqualIID(riid, &IID_IStillImageA))
		return StiCreateInstanceA(GetCurrentProcess(), STI_VERSION, (PSTIA) ppv, punkOuter);
	else if (IsEqualIID(riid, &IID_IStiDevice))
		return CreateStiDevice(punkOuter, ppv);
	else if (IsEqualIID(riid, &IID_IStiDeviceControl))
		return CreateStiDeviceControl(punkOuter, ppv);
	else
		return E_NOINTERFACE;
}

HRESULT WINAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
	*ppv = 0;
	if (IsEqualCLSID(rclsid, &GUID_DEVCLASS_IMAGE))
	{
		if (!factory)
		{
			HRESULT ret = CreateClassFactory(&GUID_DEVCLASS_IMAGE,
											 CreateStillImageInterface,
											 &IID_IClassFactory, &factory);
			if (ret != NO_ERROR)
				return ret;
		}
		return FACTORY_QueryInterface(factory, riid, ppv);
	}
	else
		return E_FAIL;
}

HRESULT WINAPI DllCanUnloadNow()
{
	return (dllRefCount == 0) ? S_OK : S_FALSE;
}

HRESULT WINAPI DllRegisterServer()
{
	return NOERROR;
}

HRESULT WINAPI DllUnregisterServer()
{
	return NOERROR;
}

BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID reserved)
{
	TRACE("(%p, %lu, %p)\n", hinst, reason, reserved);
	if (dllReferenceCount == 0)
		Initialize();
	
	if ((reason == DLL_PROCESS_ATTACH) || (reason == DLL_THREAD_ATTACH))
		InterlockedIncrement(&dllReferenceCount);
	else
		InterlockedDecrement(&dllReferenceCount);
	
	if (dllReferenceCount == 0)
		Deinitialize();
	
	TRACE("New DLL reference count: %ld\n", dllReferenceCount);
	return TRUE;
}
