/*
 * Still Image class installer
 *
 * Copyright (C) 2005 Damjan Jovanovic
 *
 * 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
 */

#include <stdarg.h>
#include "windef.h"
#include "winbase.h"
#include "winreg.h"
#include "winuser.h"

#include "setupapi.h"
#include "wine/unicode.h"

/* DeviceData section -> registry */
static DWORD ProcessDeviceData(HINF hinf, WCHAR *sectionName, HKEY hkey)
{
	WCHAR name[MAX_INF_STRING_LENGTH], value[MAX_INF_STRING_LENGTH];
	INFCONTEXT infContext;
	DWORD ret;

	if (!SetupFindFirstLineW(hinf, sectionName, NULL, &infContext))
		return GetLastError();

	/* Copy everything in this section to the registry*/
	do
	{
		if (!SetupGetStringFieldW(&infContext, 0, name, MAX_INF_STRING_LENGTH, NULL))
			continue;
		if (!SetupGetStringFieldW(&infContext, 1, value, MAX_INF_STRING_LENGTH, NULL))
			return GetLastError();
		ret = RegSetValueExW(hkey, name, 0, REG_SZ,
			(BYTE*) value, (strlenW(value)+1) * sizeof(WCHAR));
		if (ret != NO_ERROR)
			return ret;
	} while (SetupFindNextLine(&infContext, &infContext));

	return NO_ERROR;
}

/* Events section -> registry */
static DWORD ProcessEvents(HINF hinf, WCHAR *sectionName, HKEY hkey)
{
	static WCHAR str_GUID[] = {'G','U','I','D',0};
	static WCHAR str_LaunchApplications[] =
		{'L','a','u','n','c','h','A','p','p','l','i','c','a','t','i','o','n','s',0};

	INFCONTEXT infContext;
	WCHAR eventName[MAX_INF_STRING_LENGTH];
	WCHAR displayString[MAX_INF_STRING_LENGTH];
	WCHAR guid[MAX_INF_STRING_LENGTH];
	WCHAR app[MAX_INF_STRING_LENGTH];
	HKEY eventKey;
	DWORD ret;

	if (!SetupFindFirstLineW(hinf, sectionName, NULL, &infContext))
		return GetLastError();

	/* Copy everything in this section to the registry */
	do
	{
		eventKey = INVALID_HANDLE_VALUE;

		if (!SetupGetStringFieldW(&infContext, 0, eventName, MAX_INF_STRING_LENGTH, NULL))
			goto done;

		if (!SetupGetStringFieldW(&infContext, 1, displayString, MAX_INF_STRING_LENGTH, NULL))
		{
			ret = GetLastError();
			goto done;
		}

		if (!SetupGetStringFieldW(&infContext, 2, guid, MAX_INF_STRING_LENGTH, NULL))
		{
			ret = GetLastError();
			goto done;
		}

		if (!SetupGetStringFieldW(&infContext, 3, app, MAX_INF_STRING_LENGTH, NULL))
		{
			ret = GetLastError();
			goto done;
		}

		ret = RegCreateKeyW(hkey, eventName, &eventKey);
		if (ret != NO_ERROR)
			goto done;

		ret = RegSetValueExW(eventKey, NULL, 0, REG_SZ,
			(BYTE*) displayString, (strlenW(displayString)+1) * sizeof(WCHAR));
		if (ret != NO_ERROR)
			goto done;

		ret = RegSetValueExW(eventKey, str_GUID, 0, REG_SZ,
			(BYTE*) guid, (strlenW(guid)+1) * sizeof(WCHAR));
		if (ret != NO_ERROR)
			goto done;

		ret = RegSetValueExW(eventKey, str_LaunchApplications, 0, REG_SZ,
			(BYTE*) app, (strlenW(app)+1) * sizeof(WCHAR));
		
done:
		if (eventKey != INVALID_HANDLE_VALUE)
			RegCloseKey(eventKey);
		if (ret != NO_ERROR)
			return ret;
		
	} while (SetupFindNextLine(&infContext, &infContext));

	return NO_ERROR;
}

static DWORD ProcessDDInstall(HINF hinf, WCHAR *sectionName, HKEY hkey)
{
	static WCHAR str_AUTO[] = {'A','U','T','O',0};
	static WCHAR str_Capabilities[] =
		{'C','a','p','a','b','i','l','i','t','i','e','s',0};
	static WCHAR str_Connection[] = {'C','o','n','n','e','c','t','i','o','n',0};
	static WCHAR str_CreateFileName[] =
		{'C','r','e','a','t','e','F','i','l','e','N','a','m','e',0};
	static WCHAR str_DeviceData[] = {'D','e','v','i','c','e','D','a','t','a',0};
	static WCHAR str_DeviceSubType[] =
		{'D','e','v','i','c','e','S','u','b','T','y','p','e',0};
	static WCHAR str_DeviceType[] = {'D','e','v','i','c','e','T','y','p','e',0};
	static WCHAR str_Events[] = {'E','v','e','n','t','s',0};
	static WCHAR str_no[] = {'n','o',0};
	static WCHAR str_PortSelect[] = {'P','o','r','t','S','e','l','e','c','t',0};
	static WCHAR str_PropertyPages[] =
		{'P','r','o','p','e','r','t','y','P','a','g','e','s',0};
	static WCHAR str_StillImage[] = {'S','t','i','l','l','I','m','a','g','e',0};
	static WCHAR str_SubClass[] = {'S','u','b','C','l','a','s','s',0};
	static WCHAR str_UninstallSection[] =
		{'U','n','i','n','s','t','a','l','l','S','e','c','t','i','o','n',0};
	
	WCHAR line[MAX_INF_STRING_LENGTH];
	WCHAR *portName = str_AUTO;
	int shouldUserSelectPortNameForNonPnpHardware = 1;
	INFCONTEXT infContext;
	DWORD ret;

	/* First line */
	if (!SetupFindFirstLineW(hinf, sectionName, NULL, &infContext))
		return GetLastError();

	/* Rest of section */
	do
	{
		/* Read what's on the left side of the = sign */
		if (!SetupGetStringFieldW(&infContext, 0, line, MAX_INF_STRING_LENGTH, NULL))
			continue;

		/* Now process it */
		if (strcmpW(line, str_SubClass) == 0)
		{
			if (!SetupGetStringFieldW(&infContext, 1, line, MAX_INF_STRING_LENGTH, NULL))
				return GetLastError();
			if (strcmpW(line, str_StillImage) != 0)
				return ERROR_GENERAL_SYNTAX;
			ret = RegSetValueExW(hkey, str_SubClass, 0, REG_SZ,
				(BYTE*) str_StillImage, (strlenW(str_StillImage)+1)*sizeof(WCHAR));
			if (ret != NO_ERROR)
				return ret;
		}
		else if (strcmpW(line, str_DeviceType) == 0)
		{
			INT deviceType;
			if (!SetupGetIntField(&infContext, 1, &deviceType))
				return GetLastError();
			ret = RegSetValueExW(hkey, str_DeviceType, 0, REG_DWORD,
				(BYTE*) &deviceType, sizeof(deviceType));
			if (ret != NO_ERROR)
				return ret;
		}
		else if (strcmpW(line, str_DeviceSubType) == 0)
		{
			INT deviceSubType;
			if (!SetupGetIntField(&infContext, 1, &deviceSubType))
				return GetLastError();
			ret = RegSetValueExW(hkey, str_DeviceSubType, 0, REG_DWORD,
				(BYTE*) &deviceSubType, sizeof(deviceSubType));
			if (ret != NO_ERROR)
				return ret;
		}
		else if (strcmpW(line, str_Connection) == 0)
		{
			if (!SetupGetStringFieldW(&infContext, 1, line, MAX_INF_STRING_LENGTH, NULL))
				return GetLastError();
			/* FIXME: handle this for serial/parallel port scanners */
		}
		else if (strcmpW(line, str_Capabilities) == 0)
		{
			INT capabilities;
			if (!SetupGetIntField(&infContext, 1, &capabilities))
				return GetLastError();
			ret = RegSetValueExW(hkey, str_Capabilities, 0, REG_DWORD,
				(BYTE*) &capabilities, sizeof(capabilities));
			if (ret != NO_ERROR)
				return ret;
		}
		else if (strcmpW(line, str_PropertyPages) == 0)
		{
			if (!SetupGetStringFieldW(&infContext, 1, line, MAX_INF_STRING_LENGTH, NULL))
				return GetLastError();
			ret = RegSetValueExW(hkey, str_PropertyPages, 0, REG_SZ,
				(BYTE*) line, (strlenW(line)+1) * sizeof(WCHAR));
			if (ret != NO_ERROR)
				return ret;
		}
		else if (strcmpW(line, str_DeviceData) == 0)
		{
			HKEY deviceDataKey;
			if (!SetupGetStringFieldW(&infContext, 1, line, MAX_INF_STRING_LENGTH, NULL))
				return GetLastError();
			ret = RegCreateKeyW(hkey, str_DeviceData, &deviceDataKey);
			if (ret != NO_ERROR)
				return ret;
			ret = ProcessDeviceData(hinf, line, deviceDataKey);
			RegCloseKey(deviceDataKey);
			if (ret != NO_ERROR)
				return ret;
		}
		else if (strcmpW(line, str_Events) == 0)
		{
			HKEY eventsKey;
			if (!SetupGetStringFieldW(&infContext, 1, line, MAX_INF_STRING_LENGTH, NULL))
				return GetLastError();
			ret = RegCreateKeyW(hkey, str_Events, &eventsKey);
			if (ret != NO_ERROR)
				return ret;
			ret = ProcessEvents(hinf, line, eventsKey);
			RegCloseKey(eventsKey);
			if (ret != NO_ERROR)
				return ret;
		}
		else if (strcmpW(line, str_PortSelect) == 0)
		{
			if (!SetupGetStringFieldW(&infContext, 1, line, MAX_INF_STRING_LENGTH, NULL))
				return GetLastError();
			if (strcmpW(line, str_no) == 0)
				shouldUserSelectPortNameForNonPnpHardware = 0;
		}
		else if (strcmpW(line, str_UninstallSection) == 0)
		{
			/* FIXME: add code? */
		}
	} while (SetupFindNextLine(&infContext, &infContext));

	/* Write the "CreateFileName" ("PortName" in Windows 98) to the registry */
 	/* FIXME: for non-PnP hardware, */
	/* if shouldUserSelectPortNameForNonPnpHardware is non-zero, */
	/* need to ask the user for the port (COMx, LPTx, etc.) here */
	return RegSetValueExW(hkey, str_CreateFileName, 0, REG_SZ,
		(BYTE*) portName, (strlenW(portName)+1) * sizeof(WCHAR));
}

static DWORD ProcessDriverInfo(SP_DRVINFO_DATA *driver, HKEY hkey)
{
	static WCHAR str_FriendlyName[] =
		{'F','r','i','e','n','d','l','y','N','a','m','e',0};
	static WCHAR str_IsPnP[] = {'I','s','P','n','P',0};
	static WCHAR str_Vendor[] = {'V','e','n','d','o','r',0};
	
	DWORD ret;

	/* driver.Description -> FriendlyName */
	ret = RegSetValueExW(hkey, str_FriendlyName, 0, REG_SZ,
		(BYTE*) driver->Description, 
		(strlenW(driver->Description)+1) * sizeof(WCHAR));
	if (ret != NO_ERROR)
		return ret;

	/* driver.MfgName -> Vendor */
	ret = RegSetValueExW(hkey, str_Vendor, 0, REG_SZ,
		(BYTE*) driver->MfgName, (strlenW(driver->MfgName)+1) * sizeof(WCHAR));
	if (ret != NO_ERROR)
		return ret;

	/* FIXME: Proper IsPnP value (detect it somehow...) */
	{
		DWORD value = 1;
		ret = RegSetValueExW(hkey, str_IsPnP, 0, REG_DWORD, (BYTE*) &value, sizeof(value));
	}

	return ret;
}

DWORD WINAPI ClassInstall(IN DI_FUNCTION InstallFunction,
			  IN HDEVINFO DeviceInfoSet,
			  IN PSP_DEVINFO_DATA DeviceInfoData OPTIONAL)
{
	DWORD ret = ERROR_DI_DO_DEFAULT;
	HINF hinf = INVALID_HANDLE_VALUE;
	HKEY hkey = INVALID_HANDLE_VALUE;

	switch (InstallFunction)
	{
		/* Windows uses all these, but sti_ci */
		/* only adds registry keys to the class. */
		/* So, only DIF_INSTALLDEVICE is really needed :-) */
		case DIF_SELECTDEVICE:
		case DIF_REMOVE:
		case DIF_FIRSTTIMESETUP:
		case DIF_INSTALLWIZARD:
		case DIF_DESTROYWIZARDDATA:
		case DIF_ENABLECLASS:
			break;
		case DIF_INSTALLDEVICE:
		{
			SP_DRVINFO_DATA_W driver;
			SP_DRVINFO_DETAIL_DATA_W driverDetail;
			WCHAR sectionWithExtension[MAX_INF_STRING_LENGTH];
			
			/* What driver is being installed? */
			driver.cbSize = sizeof(driver);
			if (!SetupDiGetSelectedDriverW(DeviceInfoSet, DeviceInfoData, &driver))
			{
				ret = GetLastError();
				break;
			}

			/* What is the driver's INF file? */
			driverDetail.cbSize = sizeof(driverDetail);
			if (!SetupDiGetDriverInfoDetailW(DeviceInfoSet, DeviceInfoData,
				&driver, &driverDetail, sizeof(driverDetail), NULL))
			{
				/* It's ok if the buffer isn't big enough: */
				/* the field we want isn't at the end of it anyway :-) */
				if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
				{
					ret = GetLastError();
					break;
				}
			}

			/* Open the INF file */
			hinf = SetupOpenInfFileW(driverDetail.InfFileName, 0,
				INF_STYLE_WIN4, NULL);
			if (hinf == INVALID_HANDLE_VALUE)
			{
				ret = GetLastError();
				break;
			}

			/* FIXME: Call SetupDiRegisterDeviceInfo() at this point */
			/* for non-PnP hardware */

			/* Create the driver key */
			hkey = SetupDiCreateDevRegKeyW(DeviceInfoSet, DeviceInfoData, 
				DICS_FLAG_GLOBAL, 0, DIREG_DRV, 0, 0);
			if (hkey == INVALID_HANDLE_VALUE)
			{
				ret = GetLastError();
				break;
			}

			/* Which part of the INF file is being installed? */
			if (!SetupDiGetActualSectionToInstallW(hinf, driverDetail.SectionName,
				sectionWithExtension, MAX_INF_STRING_LENGTH, NULL, NULL))
			{
				ret = GetLastError();
				break;
			}

			/* Now for the actual installation: */

			/* Add stuff obtained from SP_DRVINFO_DATA */
			ret = ProcessDriverInfo(&driver, hkey);
			if (ret != NO_ERROR)
				goto fail;

			/* Add stuff from the DDInstall section */
			ret = ProcessDDInstall(hinf, sectionWithExtension, hkey);
			if (ret != NO_ERROR)
				goto fail;

			/* STI-specific registry entries successfully installed. */
			/* Still need the other entries, let setupapi add them. */
			ret = ERROR_DI_DO_DEFAULT;
			break;

		fail:
			/* Can't have semi-installed devices floating around. */
			/* Zap the entire driver key. */
			RegCloseKey(hkey);
			hkey = INVALID_HANDLE_VALUE;
			SetupDiDeleteDevRegKey(DeviceInfoSet, DeviceInfoData, 
				DICS_FLAG_GLOBAL, 0, DIREG_DRV);
			break;
		}
	}

	/* Clean up */
	if (hinf != INVALID_HANDLE_VALUE)
		SetupCloseInfFile(hinf);
	if (hkey != INVALID_HANDLE_VALUE)
		RegCloseKey(hkey);

	return ret;
}


BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved)
{
    return TRUE;
}
