/* libusb-win32, Generic Windows USB Library
* Copyright (c) 2002-2006 Stephan Meyer <ste_meyer@web.de>
* Copyright (c) 2010 Travis Robinson <libusbdotnet@gmail.com>
*
* 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 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
*/

#ifdef __GNUC__
	#if !defined(WINVER)
		#define WINVER 0x0500
	#endif
	#if !defined(_WIN32_IE)
		#define _WIN32_IE 0x0501
	#endif
#endif

#define INITGUID
#include "libusb-win32_version.h"
#include "libwdi.h"
#include "msapi_utf8.h"

#include <windows.h>
#include <commdlg.h>
#include <dbt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <initguid.h>
#include <commctrl.h>
#include <setupapi.h>
#include <time.h>

#define __INF_WIZARD_C__
#include "inf_wizard_rc.rc"

#define MAX_TEXT_LENGTH 256

// Copied from libwdi
#define safe_free(p) do {if (p != NULL) {free((void*)p); p = NULL;}} while(0)
#define safe_min(a, b) min((size_t)(a), (size_t)(b))
#define safe_strcp(dst, dst_max, src, count) do {memcpy(dst, src, safe_min(count, dst_max)); \
	((char*)dst)[safe_min(count, dst_max)-1] = 0;} while(0)
#define safe_strcpy(dst, dst_max, src) safe_strcp(dst, dst_max, src, safe_strlen(src)+1)
#define safe_strncat(dst, dst_max, src, count) strncat(dst, src, safe_min(count, dst_max - safe_strlen(dst) - 1))
#define safe_strcat(dst, dst_max, src) safe_strncat(dst, dst_max, src, safe_strlen(src)+1)
#define safe_strcmp(str1, str2) strcmp(((str1==NULL)?"<NULL>":str1), ((str2==NULL)?"<NULL>":str2))
#define safe_strncmp(str1, str2, count) strncmp(((str1==NULL)?"<NULL>":str1), ((str2==NULL)?"<NULL>":str2), count)
#define safe_closehandle(h) do {if (h != INVALID_HANDLE_VALUE) {CloseHandle(h); h = INVALID_HANDLE_VALUE;}} while(0)
#define safe_sprintf _snprintf
#define safe_strlen(str) ((((char*)str)==NULL)?0:strlen(str))
#define safe_swprintf _snwprintf
#define safe_strdup _strdup

#define _STRINGIFY(x) #x
#define STRINGIFY(x) _STRINGIFY(x)


// Used for device notification
DEFINE_GUID(GUID_DEVINTERFACE_USB_HUB, 0xf18a0e88, 0xc30c, 0x11d0, 0x88, \
			0x15, 0x00, 0xa0, 0xc9, 0x06, 0xbe, 0xd8);

DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 0xA5DCBF10L, 0x6530, 0x11D2, \
			0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED);

typedef struct
{
	struct wdi_device_info* wdi;

	char description[MAX_PATH];
	char manufacturer[MAX_PATH];

	char inf_name[MAX_PATH];
	char inf_dir [MAX_PATH];
	char inf_path[MAX_PATH];

	BOOL user_allocated_wdi;
	BOOL modified; // unused

	VS_FIXEDFILEINFO driver_info;
} device_context_t;

typedef struct
{
	UINT controlID;
	const char* message;
}create_tooltip_t;

const char info_text_0[] =
"This program will create an .inf file for your device.\n\n"
"Before clicking \"Next\" make sure that your device is connected to the "
"system.\n";

const char info_text_1[] =
"A windows driver installation package has been created for the following "
"device:";

const char package_contents_fmt_0[] =
"This package contains %s v%d.%d.%d.%d drivers and support for the following platforms: %s.";

const char package_contents_fmt_1[] =
"This package contains an inf file only.";


const char list_header_text[] =
"Select your device from the list of detected devices below. "
"If your device isn't listed then either connect it or click \"Next\" "
"and enter your device description manually.";

create_tooltip_t tooltips_dlg2[]=
{
	{ID_TEXT_VID,
	"A VID is a 16-bit vendor number (Vendor ID). A vendor ID is "
	"necessary for developing a USB product. The USB-IF is responsible "
	"for issuing USB vendor ID's to product manufacturers."},

	{ID_TEXT_PID,
	"A PID is a 16-bit product number (Product ID)."},

	{ID_TEXT_MI,
	"If not blank, creates a driver package targeting a specific interface."},

	{ID_TEXT_MANUFACTURER,
	"Manufacturer or vendor string."},

	{ID_TEXT_DEV_NAME,
	"Name or description."},

	{0,NULL}
};

create_tooltip_t tooltips_dlg3[]=
{
	{ID_BUTTON_INSTALLNOW,
	"Install this driver package now."},

	{ID_BUTTON_NEXT,
	"Finish without installing."},

	{0,NULL}
};

create_tooltip_t tooltips_dlg1[]=
{
	{ID_LIST,
	LPSTR_TEXTCALLBACK},

	{0,NULL}
};

HICON mIcon;

struct wdi_device_info* wdi_dev_list = NULL;

HINSTANCE g_hInst = NULL;
WNDPROC device_list_wndproc_orig;

TOOLINFO g_toolItem;

HWND g_hwndTrackingTT = NULL;
BOOL g_TrackingMouse = FALSE;

HWND create_tooltip(HWND hMain, HINSTANCE hInstance, UINT max_tip_width, create_tooltip_t tool_tips[]);
HWND CreateTrackingToolTip(HWND hDlg, TCHAR* pText);

HWND create_label(char* text, HWND hParent, HINSTANCE hInstance, UINT x, UINT y, UINT cx, UINT cy, DWORD dwStyle, UINT uID);
HWND create_labeled_text(char* label, char* text,
						 HWND hParent, HINSTANCE hInstance,
						 UINT left, UINT top, UINT height,
						 UINT label_width, UINT text_width,
						 UINT uIDLabel, UINT uIDText);

BOOL CALLBACK dialog_proc_0(HWND dialog, UINT message,
							WPARAM wParam, LPARAM lParam);
BOOL CALLBACK dialog_proc_1(HWND dialog, UINT message,
							WPARAM wParam, LPARAM lParam);
BOOL CALLBACK dialog_proc_2(HWND dialog, UINT message,
							WPARAM wParam, LPARAM lParam);
BOOL CALLBACK dialog_proc_3(HWND dialog, UINT message,
							WPARAM wParam, LPARAM lParam);

static void device_list_init(HWND list);
static void device_list_refresh(HWND list);
static void device_list_add(HWND list, device_context_t *device);
static void device_list_clean(HWND list);

static int save_file(HWND dialog, device_context_t *device);

void close_file(FILE** file);

int infwizard_install_driver(HWND dialog, device_context_t *device);
int infwizard_prepare_driver(HWND dialog, device_context_t *device);

void output_debug(char* format,...)
{
	va_list args;
	char msg[256];

	va_start (args, format);
	vsprintf(msg, format, args);
	va_end (args);

	OutputDebugStringA(msg);
}

int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prev_instance,
					 LPSTR cmd_line, int cmd_show)
{
	device_context_t device;
	int next_dialog;

	LoadLibrary("comctl32.dll");
	InitCommonControls();

	memset(&device, 0, sizeof(device));

	next_dialog = ID_DIALOG_0;

	mIcon = LoadIcon(instance, MAKEINTRESOURCE(IDR_MAIN_ICON));

	g_hInst = instance;

	while (next_dialog)
	{
		g_TrackingMouse = FALSE;

		switch (next_dialog)
		{
		case ID_DIALOG_0:
			next_dialog = (int)DialogBoxParam(instance,
				MAKEINTRESOURCE(next_dialog),
				NULL, (DLGPROC)dialog_proc_0,
				(LPARAM)&device);

			break;
		case ID_DIALOG_1:
			next_dialog = (int)DialogBoxParam(instance,
				MAKEINTRESOURCE(next_dialog),
				NULL, (DLGPROC)dialog_proc_1,
				(LPARAM)&device);
			break;
		case ID_DIALOG_2:
			next_dialog = (int)DialogBoxParam(instance,
				MAKEINTRESOURCE(next_dialog),
				NULL, (DLGPROC)dialog_proc_2,
				(LPARAM)&device);
			break;
		case ID_DIALOG_3:
			next_dialog = (int)DialogBoxParam(instance,
				MAKEINTRESOURCE(next_dialog),
				NULL, (DLGPROC)dialog_proc_3,
				(LPARAM)&device);
			break;
		default:
			;
		}
	}

	if (device.user_allocated_wdi)
	{
		device.user_allocated_wdi = FALSE;
		if (device.wdi)
		{
			free(device.wdi);
			device.wdi = NULL;
		}
	}

	if (wdi_dev_list)
	{
		wdi_destroy_list(wdi_dev_list);
		wdi_dev_list = NULL;
	}

	if (mIcon)
	{
		DestroyIcon(mIcon);
		mIcon = NULL;
	}
	return 0;
}

BOOL CALLBACK dialog_proc_0(HWND dialog, UINT message,
							WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_INITDIALOG:
		SendMessage(dialog,WM_SETICON,ICON_SMALL, (LPARAM)mIcon);
		SendMessage(dialog,WM_SETICON,ICON_BIG,   (LPARAM)mIcon);
		SetWindowText(GetDlgItem(dialog, ID_INFO_TEXT), info_text_0);
		return TRUE;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case ID_BUTTON_NEXT:
			EndDialog(dialog, ID_DIALOG_1);
			return TRUE ;
		case ID_BUTTON_CANCEL:
		case IDCANCEL:
			EndDialog(dialog, 0);
			return TRUE ;
		}
	}

	return FALSE;
}

INT_PTR CALLBACK device_list_wndproc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	TCHAR tipText[80];
	POINT pt;
	static int oldX, oldY;
	int newX, newY;
	LVHITTESTINFO hitTestInfo;
	LVITEM lvitem;
	device_context_t* dev_context;
	BOOL ignored;

	switch(message)
	{
	case WM_MOUSELEAVE:
		// The mouse pointer has left our window.
		// Deactivate the tooltip.
		SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&g_toolItem);
		g_TrackingMouse = FALSE;
		return FALSE;

	case WM_MOUSEMOVE:

		if (!g_TrackingMouse)
		{
			// The mouse has just entered the window.

			// Request notification when the mouse leaves.
			TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) };
			tme.hwndTrack = hDlg;
			tme.dwFlags = TME_LEAVE;
			TrackMouseEvent(&tme);

			// Activate the tooltip.
			SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE,
				(WPARAM)TRUE, (LPARAM)&g_toolItem);
			g_TrackingMouse = TRUE;
		}

		newX = LOWORD(lParam);
		newY = HIWORD(lParam);

		// Make sure the mouse has actually moved. The presence of the tooltip
		// causes Windows to send the message continuously.
		if ((newX != oldX) || (newY != oldY))
		{
			oldX = newX;
			oldY = newY;

			memset(&hitTestInfo,0,sizeof(hitTestInfo));
			hitTestInfo.pt.x = newX;
			hitTestInfo.pt.y = newY;

			if ((ListView_HitTest(hDlg, &hitTestInfo) == -1) || newX > ListView_GetColumnWidth(hDlg, 0))
			{
				safe_sprintf(tipText, sizeof(tipText) - 1, TEXT("%s"), TEXT(""));
				SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE,FALSE, (LPARAM)&g_toolItem);
			}
			else
			{
				SendMessage(g_hwndTrackingTT, TTM_SETDELAYTIME,TTDT_INITIAL, 1000);

				memset(&lvitem, 0 , sizeof(lvitem));

				lvitem.iItem = hitTestInfo.iItem;
				lvitem.mask =  LVIF_PARAM;
				ignored = ListView_GetItem(hDlg,&lvitem);

				dev_context = (device_context_t*)lvitem.lParam;
				// Update the text.
				safe_sprintf(tipText, sizeof(tipText)-1 , TEXT("%s"), wdi_get_vendor_name(dev_context->wdi->vid));
				SendMessage(g_hwndTrackingTT, TTM_TRACKACTIVATE,TRUE, (LPARAM)&g_toolItem);

			}
			g_toolItem.lpszText = tipText;
			SendMessage(g_hwndTrackingTT, TTM_SETTOOLINFO, 0, (LPARAM)&g_toolItem);

			// Position the tooltip.
			// The coordinates are adjusted so that the tooltip does not
			// overlap the mouse pointer.
			pt.x = newX;
			pt.y = newY;

			ClientToScreen(hDlg, &pt);
			SendMessage(g_hwndTrackingTT, TTM_TRACKPOSITION,
				0, (LPARAM)MAKELONG(pt.x + 10, pt.y - 20));
		}
		break;
	}
	return CallWindowProc(device_list_wndproc_orig, hDlg, message, wParam, lParam);
}

BOOL CALLBACK dialog_proc_1(HWND dialog, UINT message,
							WPARAM wParam, LPARAM lParam)
{
	static HDEVNOTIFY notification_handle_hub = NULL;
	static HDEVNOTIFY notification_handle_dev = NULL;
	DEV_BROADCAST_HDR *hdr = (DEV_BROADCAST_HDR *) lParam;
	DEV_BROADCAST_DEVICEINTERFACE dev_if;
	static device_context_t *device = NULL;
	HWND list = GetDlgItem(dialog, ID_LIST);
	LVITEM item;

	switch (message)
	{
	case WM_INITDIALOG:
		SendMessage(dialog,WM_SETICON,ICON_SMALL, (LPARAM)mIcon);
		SendMessage(dialog,WM_SETICON,ICON_BIG,   (LPARAM)mIcon);

		device = (device_context_t *)lParam;
		if (device->user_allocated_wdi)
		{
			if (device->wdi)
			{
				free(device->wdi);
				device->wdi = NULL;
			}
			device->user_allocated_wdi = FALSE;
		}
		g_hwndTrackingTT = CreateTrackingToolTip(list, TEXT(" "));

#if defined(_WIN64)
		device_list_wndproc_orig = (WNDPROC)SetWindowLongPtr(list, GWLP_WNDPROC, (UINT_PTR)device_list_wndproc);
#else
		device_list_wndproc_orig = (WNDPROC)SetWindowLongPtr(list, GWL_WNDPROC, (UINT_PTR)device_list_wndproc);
#endif

		memset(device, 0, sizeof(*device));

		SetWindowText(GetDlgItem(dialog, ID_LIST_HEADER_TEXT), list_header_text);
		device_list_init(list);
		device_list_refresh(list);

		dev_if.dbcc_size = sizeof(dev_if);
		dev_if.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;

		dev_if.dbcc_classguid = GUID_DEVINTERFACE_USB_HUB;
		notification_handle_hub = RegisterDeviceNotification(dialog, &dev_if, 0);

		dev_if.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE;
		notification_handle_dev = RegisterDeviceNotification(dialog, &dev_if, 0);

		return TRUE;

	case WM_DEVICECHANGE:
		switch (wParam)
		{
		case DBT_DEVICEREMOVECOMPLETE:
			if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
				device_list_refresh(list);
			break;
		case DBT_DEVICEARRIVAL:
			if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
				device_list_refresh(list);
			break;
		default:
			;
		}
		return TRUE;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case ID_BUTTON_NEXT:
			if (notification_handle_hub)
				UnregisterDeviceNotification(notification_handle_hub);
			if (notification_handle_dev)
				UnregisterDeviceNotification(notification_handle_dev);

			memset(&item, 0, sizeof(item));
			item.mask = LVIF_TEXT | LVIF_PARAM;
			item.iItem = ListView_GetNextItem(list, -1, LVNI_SELECTED);

			memset(device, 0, sizeof(*device));

			if (item.iItem >= 0)
			{
				if (ListView_GetItem(list, &item))
				{
					if (item.lParam)
					{
						memcpy(device, (void *)item.lParam, sizeof(*device));
					}
				}
			}

			if (!device->wdi)
			{
				device->user_allocated_wdi = TRUE;
				device->wdi = malloc(sizeof(struct wdi_device_info));
				memset(device->wdi,0,sizeof(struct wdi_device_info));

				device->wdi->vid = 0x12AB;
				device->wdi->pid = 0x12AB;
			}

			if (!device->manufacturer[0])
				strcpy(device->manufacturer, "Insert manufacturer name");
			if (!device->description[0])
				strcpy(device->description,  "Insert device description");

			device_list_clean(list);

			EndDialog(dialog, ID_DIALOG_2);
			return TRUE;

		case ID_BUTTON_BACK:
			device_list_clean(list);
			if (notification_handle_hub)
				UnregisterDeviceNotification(notification_handle_hub);
			if (notification_handle_dev)
				UnregisterDeviceNotification(notification_handle_dev);
			EndDialog(dialog, ID_DIALOG_0);
			return TRUE ;

		case ID_BUTTON_CANCEL:
		case IDCANCEL:
			device_list_clean(list);
			if (notification_handle_hub)
				UnregisterDeviceNotification(notification_handle_hub);
			if (notification_handle_dev)
				UnregisterDeviceNotification(notification_handle_dev);
			EndDialog(dialog, 0);
			return TRUE ;
		}
	}

	return FALSE;
}

BOOL CALLBACK dialog_proc_2(HWND dialog, UINT message,
							WPARAM wParam, LPARAM lParam)
{
	static device_context_t *device = NULL;
	static HWND hToolTip;
	char tmp[MAX_TEXT_LENGTH];
	int val;

	switch (message)
	{

	case WM_INITDIALOG:
		SendMessage(dialog,WM_SETICON,ICON_SMALL, (LPARAM)mIcon);
		SendMessage(dialog,WM_SETICON,ICON_BIG,   (LPARAM)mIcon);

		device = (device_context_t *)lParam;

		if (device)
		{
			wdi_is_driver_supported(WDI_LIBUSB0, &device->driver_info);

			//g_hwndTrackingTT = CreateTrackingToolTip(dialog,TEXT(" "));
			hToolTip = create_tooltip(dialog, g_hInst, 300, tooltips_dlg2);

			memset(tmp, 0, sizeof(tmp));
			safe_sprintf(tmp,sizeof(tmp) - 1, "0x%04X", device->wdi->vid);
			SetWindowText(GetDlgItem(dialog, ID_TEXT_VID), tmp);

			memset(tmp, 0, sizeof(tmp));
			safe_sprintf(tmp,sizeof(tmp) - 1, "0x%04X", device->wdi->pid);
			SetWindowText(GetDlgItem(dialog, ID_TEXT_PID), tmp);


			memset(tmp, 0, sizeof(tmp));
			if (device->wdi->is_composite)
				safe_sprintf(tmp,sizeof(tmp) - 1, "0x%02X", device->wdi->mi);
			SetWindowText(GetDlgItem(dialog, ID_TEXT_MI), tmp);

			SetWindowTextU(GetDlgItem(dialog, ID_TEXT_MANUFACTURER),
				device->manufacturer);

			SetWindowTextU(GetDlgItem(dialog, ID_TEXT_DEV_NAME),
				device->description);
		}
		return TRUE;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case ID_BUTTON_NEXT:
			//memset(device, 0, sizeof(*device));
			device->wdi->is_composite=false;

			GetWindowTextU(GetDlgItem(dialog, ID_TEXT_MANUFACTURER),
				device->manufacturer, sizeof(tmp));

			GetWindowTextU(GetDlgItem(dialog, ID_TEXT_DEV_NAME),
				device->description, sizeof(tmp));

			GetWindowText(GetDlgItem(dialog, ID_TEXT_VID), tmp, sizeof(tmp));
			if(sscanf(tmp, "0x%04x", &val) == 1)
				device->wdi->vid = (WORD)val;

			GetWindowText(GetDlgItem(dialog, ID_TEXT_PID), tmp, sizeof(tmp));
			if(sscanf(tmp, "0x%04x", &val) == 1)
				device->wdi->pid = (WORD)val;

			GetWindowText(GetDlgItem(dialog, ID_TEXT_MI), tmp, sizeof(tmp));

			if (sscanf(tmp, "0x%02x", &val) == 1)
			{
				device->wdi->mi = (BYTE)val;
				device->wdi->is_composite=true;
			}
			if (save_file(dialog, device))
				EndDialog(dialog, ID_DIALOG_3);
			return TRUE ;
		case ID_BUTTON_BACK:
			EndDialog(dialog, ID_DIALOG_1);
			return TRUE ;
		case ID_BUTTON_CANCEL:
		case IDCANCEL:
			EndDialog(dialog, 0);
			return TRUE ;
		}
	}

	return FALSE;
}
HWND create_label(char* text, HWND hParent, HINSTANCE hInstance, UINT x, UINT y, UINT cx, UINT cy, DWORD dwStyle, UINT uID)
{
	return CreateWindowU("Static", text, WS_CHILD | WS_VISIBLE | dwStyle,
		x, y, cx, cy,
		hParent, (HMENU)((UINT_PTR)uID), hInstance, 0);
}

HWND create_labeled_text(char* label, char* text,
						 HWND hParent, HINSTANCE hInstance,
						 UINT left, UINT top, UINT height,
						 UINT label_width, UINT text_width,
						 UINT uIDLabel, UINT uIDText)
{
	HWND hwnd = NULL;
	HFONT dlgFont;
	LOGFONT logFont;
	HFONT labelFont;

	// Get the font from the parent dialog
	dlgFont = (HFONT)SendMessage(hParent,WM_GETFONT,0,0);

	if (label)
	{
		// convert it to a logfont
		GetObject(dlgFont,sizeof(logFont),&logFont);

		// make it bold
		logFont.lfWeight*=2;

		// convert it back to HFONT
		labelFont = CreateFontIndirectA(&logFont);
	}
	else
	{
		labelFont = dlgFont;
	}

	if (label)
	{
		// create the label text and set the label (bold) font
		hwnd = create_label(label, hParent, hInstance, left, top, label_width, height, SS_LEFT, uIDLabel);
		SendMessage(hwnd, WM_SETFONT, (WPARAM)labelFont, TRUE);
	}
	if (text)
	{
		if (label)
		{
			text_width-=5;
			left+=label_width+5;
		}
		// create the label text and set the dialog font
		hwnd = create_label(text, hParent, hInstance, left, top, text_width, height, SS_LEFT, uIDText);
		SendMessage(hwnd,WM_SETFONT, (WPARAM)dlgFont, TRUE);
	}
	return hwnd;
}

BOOL CALLBACK dialog_proc_3(HWND dialog, UINT message,
							WPARAM wParam, LPARAM lParam)
{
	static device_context_t *device = NULL;
	char* bufferLabel = NULL;
	char* bufferText = NULL;
	int ret;
	UINT x,y;
	UINT TXT_WIDTH = 200;
	UINT LBL_WIDTH = 150;
	UINT LBL_HEIGHT = 15;
	UINT LBL_SEP = 5;
	HWND hwnd;
	static HBRUSH hBrushStatic = NULL;

	RECT rect;

	switch (message)
	{
	case WM_INITDIALOG:
		SendMessage(dialog,WM_SETICON,ICON_SMALL, (LPARAM)mIcon);
		SendMessage(dialog,WM_SETICON,ICON_BIG,   (LPARAM)mIcon);

		device = (device_context_t *)lParam;

		create_tooltip(dialog, g_hInst, 300, tooltips_dlg3);

		bufferLabel  = malloc(MAX_TEXT_LENGTH*2);
		bufferText   = bufferLabel+MAX_TEXT_LENGTH;
		if (bufferLabel)
		{
			GetWindowRect(GetDlgItem(dialog,IDG_MAIN),&rect);
			TXT_WIDTH = rect.right-rect.left-30-LBL_WIDTH;

			y = 40;
			x = 30;
			safe_sprintf(bufferLabel, MAX_TEXT_LENGTH, "%s", info_text_1);
			create_labeled_text(bufferLabel,NULL, dialog, g_hInst, x, y, LBL_HEIGHT * 2,LBL_WIDTH+TXT_WIDTH,0, ID_TEXT_HIGHLIGHT_INFO, ID_TEXT_HIGHLIGHT_INFO);

			y += LBL_HEIGHT*2+LBL_SEP*2;
			safe_strcpy(bufferLabel, MAX_TEXT_LENGTH, "Vendor ID:");
			safe_sprintf(bufferText, MAX_TEXT_LENGTH, "0x%04X", device->wdi->vid);
			create_labeled_text(bufferLabel,bufferText,dialog,g_hInst,x,y,LBL_HEIGHT,LBL_WIDTH,TXT_WIDTH, ID_INFO_TEXT, ID_INFO_TEXT);

			y += LBL_HEIGHT+LBL_SEP;
			safe_strcpy(bufferLabel, MAX_TEXT_LENGTH, "Product ID:");
			safe_sprintf(bufferText, MAX_TEXT_LENGTH, "0x%04X", device->wdi->pid);
			create_labeled_text(bufferLabel,bufferText,dialog,g_hInst,x,y,LBL_HEIGHT,LBL_WIDTH,TXT_WIDTH, ID_INFO_TEXT, ID_INFO_TEXT);

			if (device->wdi->is_composite)
			{
				y += LBL_HEIGHT+LBL_SEP;
				safe_strcpy(bufferLabel, MAX_TEXT_LENGTH, "Interface # (MI):");
				safe_sprintf(bufferText, MAX_TEXT_LENGTH, "0x%02X", device->wdi->mi);
				create_labeled_text(bufferLabel,bufferText,dialog,g_hInst,x,y,LBL_HEIGHT,LBL_WIDTH,TXT_WIDTH, ID_INFO_TEXT, ID_INFO_TEXT);
			}

			y += LBL_HEIGHT+LBL_SEP;
			safe_strcpy(bufferLabel, MAX_TEXT_LENGTH, "Device description:");
			safe_sprintf(bufferText, MAX_TEXT_LENGTH, "%s", device->description);
			create_labeled_text(bufferLabel,bufferText,dialog,g_hInst,x,y,LBL_HEIGHT,LBL_WIDTH,TXT_WIDTH, ID_INFO_TEXT, ID_INFO_TEXT);

			y += LBL_HEIGHT+LBL_SEP;
			safe_strcpy(bufferLabel, MAX_TEXT_LENGTH, "Manufacturer:");
			safe_sprintf(bufferText, MAX_TEXT_LENGTH, "%s", device->manufacturer);
			create_labeled_text(bufferLabel,bufferText,dialog,g_hInst,x,y,LBL_HEIGHT,LBL_WIDTH,TXT_WIDTH, ID_INFO_TEXT, ID_INFO_TEXT);

			y += LBL_HEIGHT+LBL_SEP*2;
			if (device->driver_info.dwSignature)
			{
				safe_sprintf(bufferLabel, MAX_TEXT_LENGTH, package_contents_fmt_0, "libusb-win32",
					(int)device->driver_info.dwFileVersionMS>>16, (int)device->driver_info.dwFileVersionMS&0xFFFF,
					(int)device->driver_info.dwFileVersionLS>>16, (int)device->driver_info.dwFileVersionLS&0xFFFF,
					"x86, x64, ia64");

			}
			else
			{
				safe_sprintf(bufferLabel, MAX_TEXT_LENGTH, "%s", package_contents_fmt_1);
			}
			hwnd = create_labeled_text(NULL,bufferLabel,dialog,g_hInst,x,y,LBL_HEIGHT*2, 0, LBL_WIDTH+TXT_WIDTH, ID_TEXT_HIGHLIGHT_INFO, ID_TEXT_HIGHLIGHT_INFO);

			free(bufferLabel);

		}
		if ((device->driver_info.dwSignature) && GetFileAttributesU(device->inf_path)!=INVALID_FILE_ATTRIBUTES)
			EnableWindow(GetDlgItem(dialog, ID_BUTTON_INSTALLNOW), TRUE);
		else
			EnableWindow(GetDlgItem(dialog, ID_BUTTON_INSTALLNOW), FALSE);

		return TRUE;
	case WM_CTLCOLORSTATIC:
		{
			///
			/// By responding to this message, the parent window can use the
			/// specified device context handle to set the text and background
			/// colors of the static control.
			///
			/// If an application processes this message, the return value is a
			/// handle to a brush that the system uses to paint the background
			/// of the static control.
			///
			if(ID_TEXT_HIGHLIGHT_INFO == GetDlgCtrlID((HWND)lParam))
			{
				SetBkColor((HDC) wParam, (COLORREF) GetSysColor(COLOR_BTNFACE));

				SetTextColor((HDC) wParam, GetSysColor(COLOR_ACTIVECAPTION));
				SetBkMode((HDC) wParam, TRANSPARENT);

				if(!hBrushStatic)
				{
					hBrushStatic = CreateSolidBrush( (COLORREF) GetSysColor(COLOR_BTNFACE));
				}

				return PtrToUlong(hBrushStatic);
			}

			// Let Windows do default handling
			return FALSE;
		}
	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case ID_BUTTON_INSTALLNOW:
			// Disable the install now button (forever)
			EnableWindow(GetDlgItem(dialog,ID_BUTTON_INSTALLNOW),FALSE);

			// Set the wait cursor and installing text
			SetCursor(LoadCursor(NULL,IDC_WAIT));
			SetWindowText(GetDlgItem(dialog, IDL_INSTALLING_TEXT), "Installing driver, please wait..");

			// Install the driver
			ret = infwizard_install_driver(dialog, device);

			// Clear installing text and restore the arrow cursor
			SetWindowText(GetDlgItem(dialog, IDL_INSTALLING_TEXT), "");
			SetCursor(LoadCursor(NULL,IDC_ARROW));

			// infwizard_install_driver() will display a message if it fails
			if (ret == ERROR_SUCCESS)
			{
				MessageBoxA(dialog,"Installation successful.",
					"Driver Install Complete", MB_OK | MB_APPLMODAL);

				// Close the wizard
				EndDialog(dialog, 0);
			}
			return TRUE;
		case ID_BUTTON_NEXT:
		case IDCANCEL:
			EndDialog(dialog, 0);
			return TRUE ;
		}
	}

	return FALSE;
}

static void device_list_init(HWND list)
{
	LVCOLUMN lvc;
	int ignored;

	ignored = (int)ListView_SetExtendedListViewStyle(list, LVS_EX_FULLROWSELECT);

	memset(&lvc, 0, sizeof(lvc));

	lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
	lvc.fmt = LVCFMT_LEFT;

	lvc.cx = 70;
	lvc.iSubItem = 0;
	lvc.pszText = "Vendor ID";
	ignored = ListView_InsertColumn(list, 1, &lvc);

	lvc.cx = 70;
	lvc.iSubItem = 1;
	lvc.pszText = "Product ID";
	ignored = ListView_InsertColumn(list, 2, &lvc);

	lvc.cx = 260;
	lvc.iSubItem = 2;
	lvc.pszText = "Description";
	ignored = ListView_InsertColumn(list, 3, &lvc);

	lvc.cx = 40;
	lvc.iSubItem = 3;
	lvc.pszText = "MI";
	ignored = ListView_InsertColumn(list, 4, &lvc);
}

static void device_list_refresh(HWND list)
{
	int ret;
	device_context_t *device;
	struct wdi_device_info* wdi_dev_info;
	struct wdi_options_create_list options;
	const char* vendor_name;

	memset(&options,0,sizeof(options));
	options.list_all=TRUE;
	options.list_hubs=FALSE;
	options.trim_whitespaces=TRUE;

	device_list_clean(list);

	if (wdi_dev_list)
		wdi_destroy_list(wdi_dev_list);

	ret = wdi_create_list(&wdi_dev_list,&options);
	if (ret == WDI_SUCCESS)
	{
		for(wdi_dev_info=wdi_dev_list; wdi_dev_info!=NULL; wdi_dev_info=wdi_dev_info->next)
		{
			device = (device_context_t *) malloc(sizeof(device_context_t));
			memset(device, 0, sizeof(*device));
			device->wdi=wdi_dev_info;
			safe_strcpy(device->description,MAX_PATH,device->wdi->desc);

			if ((vendor_name = wdi_get_vendor_name(device->wdi->vid)))
			{
				safe_strcpy(device->manufacturer,MAX_PATH, vendor_name);
			}

			device_list_add(list, device);
		}
	}
}

static void device_list_add(HWND list, device_context_t *device)
{
	LVITEM item;
	char vid[32];
	char pid[32];
	char mi[32];
	int ignored;

	memset(&item, 0, sizeof(item));
	memset(vid, 0, sizeof(vid));
	memset(pid, 0, sizeof(pid));
	memset(mi, 0, sizeof(mi));

	sprintf(vid, "0x%04X", device->wdi->vid);
	sprintf(pid, "0x%04X", device->wdi->pid);
	if (device->wdi->is_composite)
	{
		sprintf(mi, "0x%02X", device->wdi->mi);
	}

	item.mask = LVIF_TEXT | LVIF_PARAM;
	item.lParam = (LPARAM)device;

	ignored = ListView_InsertItem(list, &item);

	ListView_SetItemText(list, 0, 0, vid);
	ListView_SetItemText(list, 0, 1, pid);
	ListView_SetItemTextU(list, 0, 2, device->description);
	ListView_SetItemText(list, 0, 3, mi);
}

static void device_list_clean(HWND list)
{
	LVITEM item;
	BOOL ignored;

	memset(&item, 0, sizeof(LVITEM));

	while (ListView_GetItem(list, &item))
	{
		if (item.lParam)
			free((void *)item.lParam);

		ignored = ListView_DeleteItem(list, 0);
		memset(&item, 0, sizeof(LVITEM));
	}
}

/*
 * Converts a name + ext UTF-8 pair to a valid MS filename.
 * Returned string is allocated and needs to be freed manually
 */
static char* to_valid_filename(char* name, char* ext)
{
	size_t i, j, k;
	bool found;
	char* ret;
	wchar_t unauthorized[] = L"\x0001\x0002\x0003\x0004\x0005\x0006\x0007\x0008\x000a"
		L"\x000b\x000c\x000d\x000e\x000f\x0010\x0011\x0012\x0013\x0014\x0015\x0016\x0017"
		L"\x0018\x0019\x001a\x001b\x001c\x001d\x001e\x001f\x007f\"*/:<>?\\|";
	wchar_t to_underscore[] = L" \t";
	wchar_t *wname, *wext, *wret;

	if ((name == NULL) || (ext == NULL)) {
		return NULL;
	}

	if (strlen(name) > WDI_MAX_STRLEN) return NULL;

	// Convert to UTF-16
	wname = utf8_to_wchar(name);
	wext = utf8_to_wchar(ext);
	if ((wname == NULL) || (wext == NULL)) {
		safe_free(wname); safe_free(wext); return NULL;
	}

	// The returned UTF-8 string will never be larger than the sum of its parts
	wret = calloc(2*(wcslen(wname) + wcslen(wext) + 2), 1);
	if (wret == NULL) {
		safe_free(wname); safe_free(wext); return NULL;
	}
	wcscpy(wret, wname);
	safe_free(wname);
	wcscat(wret, wext);
	safe_free(wext);

	for (i=0, k=0; i<wcslen(wret); i++) {
		found = false;
		for (j=0; j<wcslen(unauthorized); j++) {
			if (wret[i] == unauthorized[j]) {
				found = true; break;
			}
		}
		if (found) continue;
		found = false;
		for (j=0; j<wcslen(to_underscore); j++) {
			if (wret[i] == to_underscore[j]) {
				wret[k++] = '_';
				found = true; break;
			}
		}
		if (found) continue;
		wret[k++] = wret[i];
	}
	wret[k] = 0;
	ret = wchar_to_utf8(wret);
	safe_free(wret);
	return ret;
}

static int save_file(HWND dialog, device_context_t *device)
{
	OPENFILENAME open_file;
	char* valid_name;
	int length;
	char ofd_filter[MAX_PATH];

	memset(&open_file, 0, sizeof(open_file));
	memset(device->inf_path, 0, sizeof(device->inf_path));
	memset(device->inf_dir,0,sizeof(device->inf_dir));
	memset(device->inf_name,0,sizeof(device->inf_name));

	if (strlen(device->description))
	{
		if (_stricmp(device->description,"Insert device description")!=0)
		{
			valid_name = to_valid_filename(device->description, ".inf");
			if (valid_name != NULL)
			{
				strncpy(device->inf_path, valid_name, MAX_PATH);
				free(valid_name);
			}
		}
	}
	if (!strlen(device->inf_path))
		strcpy(device->inf_path, "your_file.inf");

	memcpy(ofd_filter,
		"inf files (*.inf)\0*.inf\0\0",
		sizeof("inf files (*.inf)\0*.inf\0\0"));

	open_file.lStructSize = sizeof(OPENFILENAME);
	open_file.hwndOwner = dialog;
	open_file.lpstrFile = device->inf_path;
	open_file.nMaxFile = sizeof(device->inf_path);
	open_file.lpstrFilter = ofd_filter;
	open_file.nFilterIndex = 1;
	open_file.lpstrFileTitle = device->inf_name;
	open_file.nMaxFileTitle = sizeof(device->inf_name);
	open_file.lpstrInitialDir = NULL;
	open_file.Flags = OFN_PATHMUSTEXIST;
	open_file.lpstrDefExt = "inf";

	if (GetSaveFileNameU(&open_file))
	{
		safe_strcpy(device->inf_dir, MAX_PATH, device->inf_path);

		// strip the filename
		length = (int)strlen(device->inf_dir);
		while (length)
		{
			length--;
			if (device->inf_dir[length]=='\\' || device->inf_dir[length]=='/')
			{
				device->inf_dir[length]='\0';
				break;
			}

			device->inf_dir[length]='\0';
		}

		return infwizard_prepare_driver(dialog, device) == WDI_SUCCESS;
	}
	return FALSE;
}

int infwizard_prepare_driver(HWND dialog, device_context_t *device)
{
	struct wdi_options_prepare_driver options;
	int ret;

	memset(&options,0,sizeof(options));
	options.driver_type = WDI_LIBUSB0;
	options.vendor_name = device->manufacturer;

	if (device->wdi->desc)
		free(device->wdi->desc);
	device->wdi->desc = safe_strdup(device->description);

	if ((ret = wdi_prepare_driver(device->wdi, device->inf_dir, device->inf_name, &options) != WDI_SUCCESS))
	{
		MessageBoxA(dialog, wdi_strerror(ret),"Error Preparing Driver", MB_OK|MB_ICONWARNING);
	}

	return ret;
}

int infwizard_install_driver(HWND dialog, device_context_t *device)
{
	struct wdi_options_install_driver options;
	int ret;

	memset(&options,0,sizeof(options));
	options.hWnd = dialog;
	if ((ret = wdi_install_driver(device->wdi, device->inf_dir, device->inf_name, &options)) != WDI_SUCCESS)
	{
		MessageBoxA(dialog, wdi_strerror(ret),"Error Installing Driver", MB_OK|MB_ICONWARNING);
	}

	return ret;
}

/*
* Create a tooltip for the controls in tool_tips
*/
HWND create_tooltip(HWND hMain, HINSTANCE hInstance, UINT max_tip_width, create_tooltip_t tool_tips[])
{
	HWND hTip;
	TOOLINFO toolInfo = {0};
	int i;

	// Create the tooltip window
	hTip = CreateWindowExA(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,	hMain, NULL,
		hInstance, NULL);

	if (hTip == NULL) {
		return (HWND)NULL;
	}

	// Associate the tooltip to the control
	toolInfo.cbSize = sizeof(toolInfo);
	toolInfo.hwnd = hMain;
	toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;

	for (i=0; tool_tips[i].controlID != 0 && tool_tips[i].message != NULL; i++)
	{
		toolInfo.uId =(UINT_PTR)GetDlgItem(hMain,tool_tips[i].controlID);
		toolInfo.lpszText = (LPSTR)tool_tips[i].message;
		SendMessage(hTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
	}

	SendMessage(hTip, TTM_SETMAXTIPWIDTH, 0, max_tip_width);

	return hTip;
}
HWND CreateTrackingToolTip(HWND hDlg, TCHAR* pText)
{
	// Create a tooltip.
	HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST,
		TOOLTIPS_CLASS, NULL,
		WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		hDlg, NULL, g_hInst,NULL);

	if (!hwndTT)
	{
		return NULL;
	}

	// Set up tool information.
	// In this case, the "tool" is the entire parent window.
	g_toolItem.cbSize = sizeof(TOOLINFO);
	g_toolItem.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE;
	g_toolItem.hwnd = hDlg;
	g_toolItem.hinst = g_hInst;
	g_toolItem.lpszText = pText;
	g_toolItem.uId = (UINT_PTR)hDlg;
	GetClientRect (hDlg, &g_toolItem.rect);

	// Associate the tooltip with the tool window.
	SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &g_toolItem);
	SendMessage(hwndTT, TTM_SETMAXTIPWIDTH, 0, 300);

	return hwndTT;
}

// These are used to flag end users about the driver they are going to replace
enum driver_type {
	DT_SYSTEM,
	DT_LIBUSB,
	DT_UNKNOWN,
	DT_NONE,
	NB_DRIVER_TYPES,
};

// Retrieve the driver type according to its service string
int get_driver_type(struct wdi_device_info* dev)
{
	int i;
	const char* libusb_name[] = { "WinUSB", "libusb0" };
	const char* system_name[] = { "usbhub", "usbccgp", "USBSTOR", "HidUsb"};

	if ((dev == NULL) || (dev->driver == NULL)) {
		return DT_NONE;
	}
	for (i=0; i<sizeof(libusb_name)/sizeof(libusb_name[0]); i++) {
		if (safe_strcmp(dev->driver, libusb_name[i]) == 0) {
			return DT_LIBUSB;
		}
	}
	for (i=0; i<sizeof(system_name)/sizeof(system_name[0]); i++) {
		if (safe_strcmp(dev->driver, system_name[i]) == 0) {
			return DT_SYSTEM;
		}
	}
	return DT_UNKNOWN;
}
