/*
 * New device installer (newdev.dll)
 *
 * Copyright 2006 Hervé Poussineau (hpoussin@reactos.org)
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "newdev_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(newdev);

HANDLE hThread;

static VOID
CenterWindow(
	IN HWND hWnd)
{
	HWND hWndParent;
	RECT rcParent;
	RECT rcWindow;

	hWndParent = GetParent(hWnd);
	if (hWndParent == NULL)
		hWndParent = GetDesktopWindow();

	GetWindowRect(hWndParent, &rcParent);
	GetWindowRect(hWnd, &rcWindow);

	SetWindowPos(
		hWnd,
		HWND_TOP,
		((rcParent.right - rcParent.left) - (rcWindow.right - rcWindow.left)) / 2,
		((rcParent.bottom - rcParent.top) - (rcWindow.bottom - rcWindow.top)) / 2,
		0,
		0,
		SWP_NOSIZE);
}

static BOOL
CanDisableDevice(
	IN DEVINST DevInst,
	IN HMACHINE hMachine,
	OUT BOOL *CanDisable)
{
	CONFIGRET cr;
	ULONG Status, ProblemNumber;
	BOOL Ret = FALSE;

	cr = CM_Get_DevNode_Status_Ex(&Status,
                                  &ProblemNumber,
                                  DevInst,
                                  0,
                                  hMachine);
	if (cr == CR_SUCCESS)
	{
		*CanDisable = ((Status & DN_DISABLEABLE) != 0);
        Ret = TRUE;
	}

	return Ret;
}

static BOOL
IsDeviceStarted(
	IN DEVINST DevInst,
	IN HMACHINE hMachine,
	OUT BOOL *IsEnabled)
{
	CONFIGRET cr;
	ULONG Status, ProblemNumber;
	BOOL Ret = FALSE;

	cr = CM_Get_DevNode_Status_Ex(
		&Status,
		&ProblemNumber,
		DevInst,
		0,
		hMachine);
	if (cr == CR_SUCCESS)
	{
		*IsEnabled = ((Status & DN_STARTED) != 0);
	Ret = TRUE;
	}

	return Ret;
}

static BOOL
StartDevice(
	IN HDEVINFO DeviceInfoSet,
	IN PSP_DEVINFO_DATA DevInfoData OPTIONAL,
	IN BOOL bEnable,
	IN DWORD HardwareProfile OPTIONAL,
	OUT BOOL *bNeedReboot OPTIONAL)
{
	SP_PROPCHANGE_PARAMS pcp;
	SP_DEVINSTALL_PARAMS dp;
	DWORD LastErr;
	BOOL Ret = FALSE;

	pcp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
	pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
	pcp.HwProfile = HardwareProfile;

	if (bEnable)
	{
		/* try to enable the device on the global profile */
		pcp.StateChange = DICS_ENABLE;
		pcp.Scope = DICS_FLAG_GLOBAL;

		/* ignore errors */
		LastErr = GetLastError();
		if (SetupDiSetClassInstallParams(
			DeviceInfoSet,
			DevInfoData,
			&pcp.ClassInstallHeader,
			sizeof(SP_PROPCHANGE_PARAMS)))
		{
			SetupDiCallClassInstaller(
				DIF_PROPERTYCHANGE,
				DeviceInfoSet,
				DevInfoData);
		}
		SetLastError(LastErr);
	}

	/* try config-specific */
	pcp.StateChange = (bEnable ? DICS_ENABLE : DICS_DISABLE);
	pcp.Scope = DICS_FLAG_CONFIGSPECIFIC;

	if (SetupDiSetClassInstallParams(
			DeviceInfoSet,
			DevInfoData,
			&pcp.ClassInstallHeader,
			sizeof(SP_PROPCHANGE_PARAMS)) &&
		SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,
			DeviceInfoSet,
			DevInfoData))
	{
		dp.cbSize = sizeof(SP_DEVINSTALL_PARAMS);
		if (SetupDiGetDeviceInstallParams(
			DeviceInfoSet,
			DevInfoData,
			&dp))
		{
			if (bNeedReboot != NULL)
			{
				*bNeedReboot = ((dp.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT)) != 0);
			}

			Ret = TRUE;
		}
	}
	return Ret;
}

static DWORD WINAPI
FindDriverProc(
	IN LPVOID lpParam)
{
	PDEVINSTDATA DevInstData;
	DWORD config_flags;
	BOOL result = FALSE;

	DevInstData = (PDEVINSTDATA)lpParam;

	result = ScanFoldersForDriver(DevInstData);

	if (result)
	{
		PostMessage(DevInstData->hDialog, WM_SEARCH_FINISHED, 1, 0);
	}
	else
	{
		/* Update device configuration */
		if (SetupDiGetDeviceRegistryProperty(
			DevInstData->hDevInfo,
			&DevInstData->devInfoData,
			SPDRP_CONFIGFLAGS,
			NULL,
			(BYTE *)&config_flags,
			sizeof(config_flags),
			NULL))
		{
			config_flags |= CONFIGFLAG_FAILEDINSTALL;
			SetupDiSetDeviceRegistryPropertyW(
				DevInstData->hDevInfo,
				&DevInstData->devInfoData,
				SPDRP_CONFIGFLAGS,
				(BYTE *)&config_flags, sizeof(config_flags));
		}

		PostMessage(DevInstData->hDialog, WM_SEARCH_FINISHED, 0, 0);
	}
	return 0;
}

static DWORD WINAPI
InstallDriverProc(
	IN LPVOID lpParam)
{
	PDEVINSTDATA DevInstData;
	BOOL res;

	DevInstData = (PDEVINSTDATA)lpParam;
	res = InstallCurrentDriver(DevInstData);
	PostMessage(DevInstData->hDialog, WM_INSTALL_FINISHED, res ? 0 : 1, 0);
	return 0;
}

static VOID
PopulateCustomPathCombo(
	IN HWND hwndCombo)
{
	HKEY hKey = NULL;
	DWORD dwRegType;
	DWORD dwPathLength = 0;
	LPWSTR Buffer = NULL;
	LPCWSTR Path;
	LONG rc;

	(void)ComboBox_ResetContent(hwndCombo);

	/* RegGetValue would have been better... */
	rc = RegOpenKeyEx(
		HKEY_LOCAL_MACHINE,
		REGSTR_PATH_SETUP REGSTR_KEY_SETUP,
		0,
		KEY_QUERY_VALUE,
		&hKey);
	if (rc != ERROR_SUCCESS)
	{
		TRACE("RegOpenKeyEx() failed with error 0x%lx\n", rc);
		goto cleanup;
	}
	rc = RegQueryValueExW(
		hKey,
		L"Installation Sources",
		NULL,
		&dwRegType,
		NULL,
		&dwPathLength);
	if (rc != ERROR_SUCCESS || dwRegType != REG_MULTI_SZ)
	{
		TRACE("RegQueryValueEx() failed with error 0x%lx\n", rc);
		goto cleanup;
	}

	/* Allocate enough space to add 2 NULL chars at the end of the string */
	Buffer = HeapAlloc(GetProcessHeap(), 0, dwPathLength + 2 * sizeof(WCHAR));
	if (!Buffer)
	{
		TRACE("HeapAlloc() failed\n");
		goto cleanup;
	}
	rc = RegQueryValueExW(
		hKey,
		L"Installation Sources",
		NULL,
		NULL,
		(LPBYTE)Buffer,
		&dwPathLength);
	if (rc != ERROR_SUCCESS)
	{
		TRACE("RegQueryValueEx() failed with error 0x%lx\n", rc);
		goto cleanup;
	}
	Buffer[dwPathLength] = Buffer[dwPathLength + 1] = '\0';

	/* Populate combo box */
	for (Path = Buffer; *Path; Path += wcslen(Path) + 1)
	{
		(void)ComboBox_AddString(hwndCombo, Path);
		if (Path == Buffer)
			(void)ComboBox_SetCurSel(hwndCombo, 0);
	}

cleanup:
	if (hKey != NULL)
		RegCloseKey(hKey);
	HeapFree(GetProcessHeap(), 0, Buffer);
}

static VOID
SaveCustomPath(
	IN HWND hwndCombo)
{
	LPWSTR CustomPath = NULL;
	DWORD CustomPathLength;
	LPWSTR Buffer = NULL;
	LPWSTR pBuffer; /* Pointer into Buffer */
	int ItemsCount, Length;
	DWORD i;
	DWORD TotalLength = 0;
	BOOL UseCustomPath = TRUE;
	HKEY hKey = NULL;
	LONG rc;

	/* Get custom path */
	Length = ComboBox_GetTextLength(hwndCombo) + 1;
	CustomPath = HeapAlloc(GetProcessHeap(), 0, Length * sizeof(WCHAR));
	if (!CustomPath)
	{
		TRACE("HeapAlloc() failed\n");
		goto cleanup;
	}
	CustomPathLength = GetWindowTextW(hwndCombo, CustomPath, Length) + 1;

	/* Calculate length of the buffer */
	ItemsCount = ComboBox_GetCount(hwndCombo);
	if (ItemsCount == CB_ERR)
	{
		TRACE("ComboBox_GetCount() failed\n");
		goto cleanup;
	}
	for (i = 0; i < ItemsCount; i++)
	{
		Length = ComboBox_GetLBTextLen(hwndCombo, i);
		if (Length == CB_ERR)
		{
			TRACE("ComboBox_GetLBTextLen() failed\n");
			goto cleanup;
		}
		TotalLength += Length + 1;
	}
	TotalLength++; /* Final NULL char */

	/* Allocate buffer */
	Buffer = HeapAlloc(GetProcessHeap(), 0, (CustomPathLength + TotalLength + 1) * sizeof(WCHAR));
	if (!Buffer)
	{
		TRACE("HeapAlloc() failed\n");
		goto cleanup;
	}

	/* Fill the buffer */
	pBuffer = &Buffer[CustomPathLength];
	for (i = 0; i < ItemsCount; i++)
	{
		Length = ComboBox_GetLBText(hwndCombo, i, pBuffer);
		if (Length == CB_ERR)
		{
			TRACE("ComboBox_GetLBText() failed\n");
			goto cleanup;
		}
		else if (UseCustomPath && _wcsicmp(CustomPath, pBuffer) == 0)
			UseCustomPath = FALSE;
		pBuffer += 1 + Length;
	}
	*pBuffer = '\0'; /* Add final NULL char */

	if (!UseCustomPath)
	{
		/* Nothing to save to registry */
		goto cleanup;
	}

	TotalLength += CustomPathLength;
	wcscpy(Buffer, CustomPath);

	/* Save the buffer */
	/* RegSetKeyValue would have been better... */
	rc = RegOpenKeyEx(
		HKEY_LOCAL_MACHINE,
		REGSTR_PATH_SETUP REGSTR_KEY_SETUP,
		0,
		KEY_SET_VALUE,
		&hKey);
	if (rc != ERROR_SUCCESS)
	{
		TRACE("RegOpenKeyEx() failed with error 0x%lx\n", rc);
		goto cleanup;
	}
	rc = RegSetValueExW(
		hKey,
		L"Installation Sources",
		0,
		REG_MULTI_SZ,
		(const BYTE*)Buffer,
		TotalLength * sizeof(WCHAR));
	if (rc != ERROR_SUCCESS)
	{
		TRACE("RegSetValueEx() failed with error 0x%lx\n", rc);
		goto cleanup;
	}

cleanup:
	if (hKey != NULL)
		RegCloseKey(hKey);
	HeapFree(GetProcessHeap(), 0, CustomPath);
	HeapFree(GetProcessHeap(), 0, Buffer);
}

static INT_PTR CALLBACK
WelcomeDlgProc(
	IN HWND hwndDlg,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam)
{
	PDEVINSTDATA DevInstData;
	UNREFERENCED_PARAMETER(wParam);

	/* Retrieve pointer to the global setup data */
	DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

	switch (uMsg)
	{
		case WM_INITDIALOG:
		{
			HWND hwndControl;
			DWORD dwStyle;

			/* Get pointer to the global setup data */
			DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
			SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

			hwndControl = GetParent(hwndDlg);

			/* Center the wizard window */
			CenterWindow(hwndControl);

			/* Hide the system menu */
			dwStyle = GetWindowLongPtr(hwndControl, GWL_STYLE);
			SetWindowLongPtr(hwndControl, GWL_STYLE, dwStyle & ~WS_SYSMENU);

			/* Set title font */
			SendDlgItemMessage(
				hwndDlg,
				IDC_WELCOMETITLE,
				WM_SETFONT,
				(WPARAM)DevInstData->hTitleFont,
				(LPARAM)TRUE);

			SendDlgItemMessage(
				hwndDlg,
				IDC_DEVICE,
				WM_SETTEXT,
				0,
				(LPARAM)DevInstData->buffer);

			SendDlgItemMessage(
				hwndDlg,
				IDC_RADIO_AUTO,
				BM_SETCHECK,
				(WPARAM)TRUE,
				(LPARAM)0);
			break;
		}

		case WM_NOTIFY:
		{
			LPNMHDR lpnm = (LPNMHDR)lParam;

			switch (lpnm->code)
			{
				case PSN_SETACTIVE:
					/* Enable the Next button */
					PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_NEXT);
					break;

				case PSN_WIZNEXT:
					/* Handle a Next button click, if necessary */
					if (SendDlgItemMessage(hwndDlg, IDC_RADIO_AUTO, BM_GETCHECK, (WPARAM)0, (LPARAM)0) == BST_CHECKED)
					{
						if (PrepareFoldersToScan(DevInstData, TRUE, FALSE, NULL))
							PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_SEARCHDRV);
						else
							PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_INSTALLFAILED);
					}
					return TRUE;

				default:
					break;
			}
			break;
		}

		default:
			break;
	}

	return FALSE;
}

static void
IncludePath(HWND Dlg, BOOL Enabled)
{
	EnableWindow(GetDlgItem(Dlg, IDC_COMBO_PATH), Enabled);
	EnableWindow(GetDlgItem(Dlg, IDC_BROWSE), Enabled);
}

static void
AutoDriver(HWND Dlg, BOOL Enabled)
{
	EnableWindow(GetDlgItem(Dlg, IDC_CHECK_MEDIA), Enabled);
	EnableWindow(GetDlgItem(Dlg, IDC_CHECK_PATH), Enabled);
	IncludePath(Dlg, Enabled & IsDlgButtonChecked(Dlg, IDC_CHECK_PATH));
}

static INT_PTR CALLBACK
CHSourceDlgProc(
	IN HWND hwndDlg,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam)
{
	PDEVINSTDATA DevInstData;

	/* Retrieve pointer to the global setup data */
	DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

	switch (uMsg)
	{
		case WM_INITDIALOG:
		{
			HWND hwndControl;
			DWORD dwStyle;

			/* Get pointer to the global setup data */
			DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
			SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

			hwndControl = GetParent(hwndDlg);

			/* Center the wizard window */
			CenterWindow(hwndControl);

			/* Hide the system menu */
			dwStyle = GetWindowLongPtr(hwndControl, GWL_STYLE);
			SetWindowLongPtr(hwndControl, GWL_STYLE, dwStyle & ~WS_SYSMENU);

			PopulateCustomPathCombo(GetDlgItem(hwndDlg, IDC_COMBO_PATH));

			SendDlgItemMessage(
				hwndDlg,
				IDC_RADIO_SEARCHHERE,
				BM_SETCHECK,
				(WPARAM)TRUE,
				(LPARAM)0);
			AutoDriver(hwndDlg, TRUE);
			IncludePath(hwndDlg, FALSE);

			/* Disable manual driver choice for now */
			EnableWindow(GetDlgItem(hwndDlg, IDC_RADIO_CHOOSE), FALSE);

			break;
		}

		case WM_COMMAND:
		{
			switch (LOWORD(wParam))
			{
				case IDC_RADIO_SEARCHHERE:
					AutoDriver(hwndDlg, TRUE);
					return TRUE;

				case IDC_RADIO_CHOOSE:
					AutoDriver(hwndDlg, FALSE);
					return TRUE;

				case IDC_CHECK_PATH:
					IncludePath(hwndDlg, IsDlgButtonChecked(hwndDlg, IDC_CHECK_PATH));
					return TRUE;

				case IDC_BROWSE:
				{
					BROWSEINFO bi = { 0 };
					LPITEMIDLIST pidl;

					bi.hwndOwner = hwndDlg;
					bi.ulFlags = BIF_RETURNONLYFSDIRS;
					pidl = SHBrowseForFolder(&bi);
					if (pidl)
					{
						WCHAR Directory[MAX_PATH];
						IMalloc* malloc;

						if (SHGetPathFromIDListW(pidl, Directory))
						{
							/* Set the IDC_COMBO_PATH text */
							SetWindowTextW(GetDlgItem(hwndDlg, IDC_COMBO_PATH), Directory);
						}

						/* Free memory, if possible */
						if (SUCCEEDED(SHGetMalloc(&malloc)))
						{
							IMalloc_Free(malloc, pidl);
							IMalloc_Release(malloc);
						}
						return TRUE;
					}
					break;
				}
			}
			break;
		}

		case WM_NOTIFY:
		{
			LPNMHDR lpnm = (LPNMHDR)lParam;

			switch (lpnm->code)
			{
				case PSN_SETACTIVE:
					/* Enable the Next and Back buttons */
					PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_NEXT | PSWIZB_BACK);
					break;

				case PSN_WIZNEXT:
					/* Handle a Next button click, if necessary */
					if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_SEARCHHERE))
					{
						SaveCustomPath(GetDlgItem(hwndDlg, IDC_COMBO_PATH));
						HeapFree(GetProcessHeap(), 0, DevInstData->CustomSearchPath);
						DevInstData->CustomSearchPath = NULL;
						if (PrepareFoldersToScan(
							DevInstData,
							IsDlgButtonChecked(hwndDlg, IDC_CHECK_MEDIA),
							IsDlgButtonChecked(hwndDlg, IDC_CHECK_PATH),
							GetDlgItem(hwndDlg, IDC_COMBO_PATH)))
						{
							PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_SEARCHDRV);
						}
						else
						{
							PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_INSTALLFAILED);
						}
					}
					else
					{
						/* FIXME */;
					}
					return TRUE;

				default:
					break;
			}
			break;
		}

		default:
			break;
	}

	return FALSE;
}

static INT_PTR CALLBACK
SearchDrvDlgProc(
	IN HWND hwndDlg,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam)
{
	PDEVINSTDATA DevInstData;
	DWORD dwThreadId;

	/* Retrieve pointer to the global setup data */
	DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

	switch (uMsg)
	{
		case WM_INITDIALOG:
		{
			HWND hwndControl;
			DWORD dwStyle;

			/* Get pointer to the global setup data */
			DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
			SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

			DevInstData->hDialog = hwndDlg;
			hwndControl = GetParent(hwndDlg);

			/* Center the wizard window */
			CenterWindow(hwndControl);

			SendDlgItemMessage(
				hwndDlg,
				IDC_DEVICE,
				WM_SETTEXT,
				0,
				(LPARAM)DevInstData->buffer);

			/* Hide the system menu */
			dwStyle = GetWindowLongPtr(hwndControl, GWL_STYLE);
			SetWindowLongPtr(hwndControl, GWL_STYLE, dwStyle & ~WS_SYSMENU);
			break;
		}

		case WM_SEARCH_FINISHED:
		{
			CloseHandle(hThread);
			hThread = 0;
			if (wParam == 0)
				PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_NODRIVER);
			else
				PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_INSTALLDRV);
			break;
		}

		case WM_NOTIFY:
		{
			LPNMHDR lpnm = (LPNMHDR)lParam;

			switch (lpnm->code)
			{
				case PSN_SETACTIVE:
					PropSheet_SetWizButtons(GetParent(hwndDlg), !PSWIZB_NEXT | !PSWIZB_BACK);
					/* Yes, we can safely ignore the problem (if any) */
					SetupDiDestroyDriverInfoList(
						DevInstData->hDevInfo,
						&DevInstData->devInfoData,
						SPDIT_COMPATDRIVER);
					hThread = CreateThread(NULL, 0, FindDriverProc, DevInstData, 0, &dwThreadId);
					break;

				case PSN_KILLACTIVE:
					if (hThread != 0)
					{
						SetWindowLongPtr(hwndDlg, DWL_MSGRESULT, TRUE);
						return TRUE;
					}
					break;

				case PSN_WIZNEXT:
					/* Handle a Next button click, if necessary */
					break;

				default:
					break;
			}
			break;
		}

		default:
			break;
	}

	return FALSE;
}

static INT_PTR CALLBACK
InstallDrvDlgProc(
	IN HWND hwndDlg,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam)
{
	PDEVINSTDATA DevInstData;
	DWORD dwThreadId;

	/* Retrieve pointer to the global setup data */
	DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

	switch (uMsg)
	{
		case WM_INITDIALOG:
		{
			HWND hwndControl;
			DWORD dwStyle;

			/* Get pointer to the global setup data */
			DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
			SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

			DevInstData->hDialog = hwndDlg;
			hwndControl = GetParent(hwndDlg);

			/* Center the wizard window */
			CenterWindow(hwndControl);

			SendDlgItemMessage(
				hwndDlg,
				IDC_DEVICE,
				WM_SETTEXT,
				0,
				(LPARAM)DevInstData->drvInfoData.Description);

			/* Hide the system menu */
			dwStyle = GetWindowLongPtr(hwndControl, GWL_STYLE);
			SetWindowLongPtr(hwndControl, GWL_STYLE, dwStyle & ~WS_SYSMENU);
			break;
		}

		case WM_INSTALL_FINISHED:
		{
			CloseHandle(hThread);
			hThread = 0;
			if (wParam == 0)
			{
				/* Should we reboot? */
				SP_DEVINSTALL_PARAMS installParams;
				installParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS);
				if (SetupDiGetDeviceInstallParams(
					DevInstData->hDevInfo,
					&DevInstData->devInfoData,
					&installParams))
				{
					if (installParams.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT))
					{
						PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_NEEDREBOOT);
					}
					else
						PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_FINISHPAGE);
					break;
				}
			}
			PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_INSTALLFAILED);
			break;
		}

		case WM_NOTIFY:
		{
			LPNMHDR lpnm = (LPNMHDR)lParam;

			switch (lpnm->code)
			{
				case PSN_SETACTIVE:
					PropSheet_SetWizButtons(GetParent(hwndDlg), !PSWIZB_NEXT | !PSWIZB_BACK);
					hThread = CreateThread(NULL, 0, InstallDriverProc, DevInstData, 0, &dwThreadId);
					break;

				case PSN_KILLACTIVE:
					if (hThread != 0)
					{
						SetWindowLongPtr(hwndDlg, DWL_MSGRESULT, TRUE);
						return TRUE;
					}
					break;

				case PSN_WIZNEXT:
					/* Handle a Next button click, if necessary */
					break;

				default:
					break;
			}
			break;
		}

		default:
			break;
	}

	return FALSE;
}

static INT_PTR CALLBACK
NoDriverDlgProc(
	IN HWND hwndDlg,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam)
{
	PDEVINSTDATA DevInstData;
	HWND hwndControl;

	UNREFERENCED_PARAMETER(wParam);

	/* Get pointer to the global setup data */
	DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

	switch (uMsg)
	{
		case WM_INITDIALOG:
		{
			BOOL DisableableDevice = FALSE;

			/* Get pointer to the global setup data */
			DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
			SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

			/* Center the wizard window */
			CenterWindow(GetParent(hwndDlg));

			hwndControl = GetDlgItem(GetParent(hwndDlg), IDCANCEL);
			ShowWindow(hwndControl, SW_HIDE);
			EnableWindow(hwndControl, FALSE);

			/* Set title font */
			SendDlgItemMessage(
				hwndDlg,
				IDC_FINISHTITLE,
				WM_SETFONT,
				(WPARAM)DevInstData->hTitleFont,
				(LPARAM)TRUE);

			/* disable the "do not show this dialog anymore" checkbox
			 if the device cannot be disabled */
			CanDisableDevice(
				DevInstData->devInfoData.DevInst,
				NULL,
				&DisableableDevice);
			EnableWindow(
				GetDlgItem(hwndDlg, IDC_DONOTSHOWDLG),
				DisableableDevice);
			break;
		}

		case WM_NOTIFY:
		{
			LPNMHDR lpnm = (LPNMHDR)lParam;

			switch (lpnm->code)
			{
				case PSN_SETACTIVE:
					/* Enable the correct buttons on for the active page */
					PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_BACK | PSWIZB_FINISH);
					break;

				case PSN_WIZBACK:
					/* Handle a Back button click, if necessary */
					hwndControl = GetDlgItem(GetParent(hwndDlg), IDCANCEL);
					ShowWindow(hwndControl, SW_SHOW);
					EnableWindow(hwndControl, TRUE);
					PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_CHSOURCE);
					return TRUE;

				case PSN_WIZFINISH:
				{
					BOOL DisableableDevice = FALSE;
					BOOL IsStarted = FALSE;

					if (CanDisableDevice(DevInstData->devInfoData.DevInst,
							NULL,
							&DisableableDevice) &&
						DisableableDevice &&
						IsDeviceStarted(
							DevInstData->devInfoData.DevInst,
							NULL,
							&IsStarted) &&
						!IsStarted &&
						SendDlgItemMessage(
							hwndDlg,
							IDC_DONOTSHOWDLG,
							BM_GETCHECK,
							(WPARAM)0, (LPARAM)0) == BST_CHECKED)
					{
						/* disable the device */
						StartDevice(
							DevInstData->hDevInfo,
							&DevInstData->devInfoData,
							FALSE,
							0,
							NULL);
					}
					break;
				}

				default:
					break;
			}
			break;
		}

		default:
			break;
	}

	return FALSE;
}

static INT_PTR CALLBACK
InstallFailedDlgProc(
	IN HWND hwndDlg,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam)
{
	PDEVINSTDATA DevInstData;
	UNREFERENCED_PARAMETER(wParam);

	/* Retrieve pointer to the global setup data */
	DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

	switch (uMsg)
	{
		case WM_INITDIALOG:
		{
			HWND hwndControl;

			/* Get pointer to the global setup data */
			DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
			SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

			/* Center the wizard window */
			CenterWindow(GetParent(hwndDlg));

			hwndControl = GetDlgItem(GetParent(hwndDlg), IDCANCEL);
			ShowWindow(hwndControl, SW_HIDE);
			EnableWindow(hwndControl, FALSE);

			SendDlgItemMessage(
				hwndDlg,
				IDC_DEVICE,
				WM_SETTEXT,
				0,
				(LPARAM)DevInstData->drvInfoData.Description);

			/* Set title font */
			SendDlgItemMessage(
				hwndDlg,
				IDC_FINISHTITLE,
				WM_SETFONT,
				(WPARAM)DevInstData->hTitleFont,
				(LPARAM)TRUE);
			break;
		}

		case WM_NOTIFY:
		{
			LPNMHDR lpnm = (LPNMHDR)lParam;

			switch (lpnm->code)
			{
				case PSN_SETACTIVE:
					/* Enable the correct buttons on for the active page */
					PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_FINISH);
					break;

				case PSN_WIZBACK:
					/* Handle a Back button click, if necessary */
					break;

				case PSN_WIZFINISH:
					/* Handle a Finish button click, if necessary */
					break;

				default:
					break;
			}
			break;
		}

		default:
			break;
	}

	return FALSE;
}

static INT_PTR CALLBACK
NeedRebootDlgProc(
	IN HWND hwndDlg,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam)
{
	PDEVINSTDATA DevInstData;
	UNREFERENCED_PARAMETER(wParam);

	/* Retrieve pointer to the global setup data */
	DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

	switch (uMsg)
	{
		case WM_INITDIALOG:
		{
			HWND hwndControl;

			/* Get pointer to the global setup data */
			DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
			SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

			/* Center the wizard window */
			CenterWindow(GetParent(hwndDlg));

			hwndControl = GetDlgItem(GetParent(hwndDlg), IDCANCEL);
			ShowWindow(hwndControl, SW_HIDE);
			EnableWindow(hwndControl, FALSE);

			SendDlgItemMessage(
				hwndDlg,
				IDC_DEVICE,
				WM_SETTEXT,
				0,
				(LPARAM)DevInstData->drvInfoData.Description);

			/* Set title font */
			SendDlgItemMessage(
				hwndDlg,
				IDC_FINISHTITLE,
				WM_SETFONT,
				(WPARAM)DevInstData->hTitleFont,
				(LPARAM)TRUE);
			break;
		}

		case WM_NOTIFY:
		{
			LPNMHDR lpnm = (LPNMHDR)lParam;

			switch (lpnm->code)
			{
				case PSN_SETACTIVE:
					/* Enable the correct buttons on for the active page */
					PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_FINISH);
					break;

				case PSN_WIZBACK:
					/* Handle a Back button click, if necessary */
					break;

				case PSN_WIZFINISH:
					/* Handle a Finish button click, if necessary */
					break;

				default:
					break;
			}
			break;
		}

		default:
			break;
	}

	return FALSE;
}

static INT_PTR CALLBACK
FinishDlgProc(
	IN HWND hwndDlg,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam)
{
	PDEVINSTDATA DevInstData;
	UNREFERENCED_PARAMETER(wParam);

	/* Retrieve pointer to the global setup data */
	DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

	switch (uMsg)
	{
		case WM_INITDIALOG:
		{
			HWND hwndControl;

			/* Get pointer to the global setup data */
			DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
			SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

			/* Center the wizard window */
			CenterWindow(GetParent(hwndDlg));

			hwndControl = GetDlgItem(GetParent(hwndDlg), IDCANCEL);
			ShowWindow(hwndControl, SW_HIDE);
			EnableWindow(hwndControl, FALSE);

			SendDlgItemMessage(
				hwndDlg,
				IDC_DEVICE,
				WM_SETTEXT,
				0,
				(LPARAM)DevInstData->drvInfoData.Description);

			/* Set title font */
			SendDlgItemMessage(
				hwndDlg,
				IDC_FINISHTITLE,
				WM_SETFONT,
				(WPARAM)DevInstData->hTitleFont,
				(LPARAM)TRUE);
			break;
		}

		case WM_NOTIFY:
		{
			LPNMHDR lpnm = (LPNMHDR)lParam;

			switch (lpnm->code)
			{
				case PSN_SETACTIVE:
					/* Enable the correct buttons on for the active page */
					PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_FINISH);
					break;

				case PSN_WIZBACK:
					/* Handle a Back button click, if necessary */
					break;

				case PSN_WIZFINISH:
					/* Handle a Finish button click, if necessary */
					break;

				default:
					break;
			}
			break;
		}

		default:
			break;
	}

	return FALSE;
}

static HFONT
CreateTitleFont(VOID)
{
	NONCLIENTMETRICSW ncm;
	LOGFONTW LogFont;
	HDC hdc;
	INT FontSize;
	HFONT hFont;

	ncm.cbSize = sizeof(NONCLIENTMETRICSW);
	SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0);

	LogFont = ncm.lfMessageFont;
	LogFont.lfWeight = FW_BOLD;
	wcscpy(LogFont.lfFaceName, L"MS Shell Dlg");

	hdc = GetDC(NULL);
	FontSize = 12;
	LogFont.lfHeight = 0 - GetDeviceCaps (hdc, LOGPIXELSY) * FontSize / 72;
	hFont = CreateFontIndirectW(&LogFont);
	ReleaseDC(NULL, hdc);

	return hFont;
}

BOOL
DisplayWizard(
	IN PDEVINSTDATA DevInstData,
	IN HWND hwndParent,
	IN UINT startPage)
{
	PROPSHEETHEADER psh;
	HPROPSHEETPAGE ahpsp[IDD_MAXIMUMPAGE + 1];
	PROPSHEETPAGE psp;

	/* Create the Welcome page */
	ZeroMemory(&psp, sizeof(PROPSHEETPAGE));
	psp.dwSize = sizeof(PROPSHEETPAGE);
	psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER;
	psp.hInstance = hDllInstance;
	psp.lParam = (LPARAM)DevInstData;
	psp.pfnDlgProc = (DLGPROC) WelcomeDlgProc;
	psp.pszTemplate = MAKEINTRESOURCE(IDD_WELCOMEPAGE);
	ahpsp[IDD_WELCOMEPAGE] = CreatePropertySheetPage(&psp);

	/* Create the Select Source page */
	psp.dwFlags = PSP_DEFAULT | PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
	psp.pfnDlgProc = (DLGPROC) CHSourceDlgProc;
	psp.pszTemplate = MAKEINTRESOURCE(IDD_CHSOURCE);
	ahpsp[IDD_CHSOURCE] = CreatePropertySheetPage(&psp);

	/* Create the Search driver page */
	psp.dwFlags = PSP_DEFAULT | PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
	psp.pfnDlgProc = (DLGPROC) SearchDrvDlgProc;
	psp.pszTemplate = MAKEINTRESOURCE(IDD_SEARCHDRV);
	ahpsp[IDD_SEARCHDRV] = CreatePropertySheetPage(&psp);

	/* Create the Install driver page */
	psp.dwFlags = PSP_DEFAULT | PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
	psp.pfnDlgProc = (DLGPROC) InstallDrvDlgProc;
	psp.pszTemplate = MAKEINTRESOURCE(IDD_INSTALLDRV);
	ahpsp[IDD_INSTALLDRV] = CreatePropertySheetPage(&psp);

	/* Create the No driver page */
	psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER;
	psp.pfnDlgProc = (DLGPROC) NoDriverDlgProc;
	psp.pszTemplate = MAKEINTRESOURCE(IDD_NODRIVER);
	ahpsp[IDD_NODRIVER] = CreatePropertySheetPage(&psp);

	/* Create the Install failed page */
	psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER;
	psp.pfnDlgProc = (DLGPROC) InstallFailedDlgProc;
	psp.pszTemplate = MAKEINTRESOURCE(IDD_INSTALLFAILED);
	ahpsp[IDD_INSTALLFAILED] = CreatePropertySheetPage(&psp);

	/* Create the Need reboot page */
	psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER;
	psp.pfnDlgProc = (DLGPROC) NeedRebootDlgProc;
	psp.pszTemplate = MAKEINTRESOURCE(IDD_NEEDREBOOT);
	ahpsp[IDD_NEEDREBOOT] = CreatePropertySheetPage(&psp);

	/* Create the Finish page */
	psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER;
	psp.pfnDlgProc = (DLGPROC) FinishDlgProc;
	psp.pszTemplate = MAKEINTRESOURCE(IDD_FINISHPAGE);
	ahpsp[IDD_FINISHPAGE] = CreatePropertySheetPage(&psp);

	/* Create the property sheet */
	psh.dwSize = sizeof(PROPSHEETHEADER);
	psh.dwFlags = PSH_WIZARD97 | PSH_WATERMARK | PSH_HEADER;
	psh.hInstance = hDllInstance;
	psh.hwndParent = hwndParent;
	psh.nPages = IDD_MAXIMUMPAGE + 1;
	psh.nStartPage = startPage;
	psh.phpage = ahpsp;
	psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK);
	psh.pszbmHeader = MAKEINTRESOURCE(IDB_HEADER);

	/* Create title font */
	DevInstData->hTitleFont = CreateTitleFont();

	/* Display the wizard */
	PropertySheet(&psh);

	DeleteObject(DevInstData->hTitleFont);

	return TRUE;
}
