/*
 * Written by Thomas Heller, May 2000
 *
 * $Id: install.c,v 1.8 2000/10/12 19:25:58 theller Exp $
 */

/*
 * Windows Installer program for distutils.
 *
 * (a kind of self-extracting zip-file)
 *
 * At runtime, the exefile has appended:
 * - compressed setup-data in ini-format, containing the following sections:
 *	[metadata]
 *	author=Greg Ward
 *	author_email=gward@python.net
 *	description=Python Distribution Utilities
 *	licence=Python
 *	name=Distutils
 *	url=http://www.python.org/sigs/distutils-sig/
 *	version=0.9pre
 *
 *	[Setup]
 *	info= text to be displayed in the edit-box
 *	title= to be displayed by this program
 *	target_version = if present, python version required
 *	pyc_compile = if 0, do not compile py to pyc
 *	pyo_compile = if 0, do not compile py to pyo
 *
 * - a struct meta_data_hdr, describing the above
 * - a zip-file, containing the modules to be installed.
 *   for the format see http://www.pkware.com/appnote.html
 *
 * What does this program do?
 * - the setup-data is uncompressed and written to a temporary file.
 * - setup-data is queried with GetPrivateProfile... calls
 * - [metadata] - info is displayed in the dialog box
 * - The registry is searched for installations of python
 * - The user can select the python version to use.
 * - The python-installation directory (sys.prefix) is displayed
 * - When the start-button is pressed, files from the zip-archive
 *   are extracted to the file system. All .py filenames are stored
 *   in a list.
 */

/*
 * To Do:
 *  - install a help-button, which will display the above
 *    text to the user
 *  - should there be a possibility to display a README file
 *    before starting the installation (if one is present in the archive)
 *  - think about uninstaller
 *  - more comments about what the code does(?)
 *
 *  - think about an uninstaller (?)
 *  - evolve this into a full blown installer (???)
 */

#include <windows.h>
#include <commctrl.h>
#include "resource.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

#include "archive.h"

/* Bah: global variables */
HWND hwndMain;
HWND hDialog;

char *ini_file;			/* Full pathname of ini-file */
/* From ini-file */
char info[4096];		/* [Setup] info= */
char title[80];			/* [Setup] title= */
char target_version[10];	/* [Setup] target_version= */
char build_info[80];		/* [Setup] build_info= */

char meta_name[80];

char *arc_data;			/* memory mapped archive */
DWORD arc_size;			/* number of bytes in archive */
char install_dir[MAX_PATH];
char pythondll[MAX_PATH];
BOOL pyc_compile, pyo_compile;

BOOL success;			/* Installation successfull? */

#define WM_NUMFILES WM_USER+1
	/* wParam: 0, lParam: total number of files */
#define WM_NEXTFILE WM_USER+2
	/* wParam: number of this file */
	/* lParam: points to pathname */

enum { UNSPECIFIED, ASK, ALWAYS, NEVER } allow_overwrite = UNSPECIFIED;

static void unescape (char *str)
{
    char *dst = str;
    char *src = str;
    char *eon;
    char ch;

    while (src && *src) {
	if (*src == '\\') {
	    switch (*++src) {
	    case 'n':
		*dst++ = '\n';
		*dst++ = '\r';
		break;
	    case 'r':
		*dst++ = '\r';
		break;
	    case '0': case '1': case '2': case '3':
		ch = (char)strtol (src, &eon, 8);
		if (ch == '\n')
		    *dst++ = '\r';
		*dst++ = ch;
		src = eon;
	    }
	} else
	    *dst++ = *src++;
    }
    *dst = '\0';
}

static struct tagFile {
    char *path;
    struct tagFile *next;
} *file_list = NULL;

static void add_to_filelist (char *path)
{
    struct tagFile *p;
    p = (struct tagFile *)malloc (sizeof (struct tagFile));
    p->path = strdup (path);
    p->next = file_list;
    file_list = p;
}

static int do_compile_files (int (__cdecl * PyRun_SimpleString)(char *))
{
    struct tagFile *p;
    int total, n;
    char Buffer[MAX_PATH + 64];
    int errors = 0;

    total = 0;
    p = file_list;
    while (p) {
	++total;
	p = p->next;
    }
    SendDlgItemMessage (hDialog, IDC_PROGRESS, PBM_SETRANGE, 0,
			MAKELPARAM (0, total));
    SendDlgItemMessage (hDialog, IDC_PROGRESS, PBM_SETPOS, 0, 0);

    n = 0;
    p = file_list;
    while (p) {
	++n;
        wsprintf (Buffer,
		  "import py_compile; py_compile.compile (r'%s')",
		  p->path);
        if (PyRun_SimpleString (Buffer))
	    ++errors;
	SendDlgItemMessage (hDialog, IDC_PROGRESS, PBM_SETPOS, n, 0);
	SetDlgItemText (hDialog, IDC_INFO, p->path);
	p = p->next;
    }
    return errors;
}

static int compile_filelist (BOOL optimize_flag)
{
    void (__cdecl * Py_Initialize)(void);
    void (__cdecl * Py_Finalize)(void);
    int (__cdecl * PyRun_SimpleString)(char *);
    int *Py_OptimizeFlag;
    int errors = 0;
    HINSTANCE hPython;
    struct tagFile *p = file_list;

    if (!p)
	return 0;
    SetDlgItemText (hDialog, IDC_INFO, "Loading python...");
		
    hPython = LoadLibrary (pythondll);
    if (!hPython)
	return 1;
    Py_Initialize = (void (*)(void))GetProcAddress
	(hPython,"Py_Initialize");

    Py_Finalize = (void (*)(void))GetProcAddress (hPython,
						  "Py_Finalize");
    PyRun_SimpleString = (int (*)(char *))GetProcAddress (
	hPython, "PyRun_SimpleString");

    Py_OptimizeFlag = (int *)GetProcAddress (hPython,
					     "Py_OptimizeFlag");
    
    *Py_OptimizeFlag = optimize_flag;
    Py_Initialize ();

    errors += do_compile_files (PyRun_SimpleString);
    Py_Finalize ();
    FreeLibrary (hPython);

    return errors;
}

static BOOL SystemError (int error, char *msg)
{
    char Buffer[1024];
    int n;

    if (error) {
        LPVOID lpMsgBuf;
	FormatMessage( 
	    FORMAT_MESSAGE_ALLOCATE_BUFFER | 
	    FORMAT_MESSAGE_FROM_SYSTEM,
	    NULL,
	    error,
	    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
	    (LPSTR)&lpMsgBuf,
	    0,
	    NULL 
	    );
        strncpy (Buffer, lpMsgBuf, sizeof (Buffer));
	LocalFree (lpMsgBuf);
    } else
	Buffer[0] = '\0';
    n = lstrlen (Buffer);
    _snprintf (Buffer+n, sizeof (Buffer)-n, msg);
    MessageBox (hwndMain, Buffer, "Runtime Error", MB_OK | MB_ICONSTOP);
    return FALSE;
}

static BOOL AskOverwrite (char *filename)
{
    int result;
  again:
    if (allow_overwrite == ALWAYS)
	return TRUE;
    if (allow_overwrite == NEVER)
	return FALSE;
    if (allow_overwrite == ASK)
        return (IDYES == MessageBox (hwndMain,
			    filename,
			    "Overwrite existing file?",
			    MB_YESNO | MB_ICONQUESTION));

    result = MessageBox (hwndMain,
"Overwrite existing files?\n"
"\n"
"Press YES to ALWAYS overwrite existing files,\n"
"press NO to NEVER overwrite existing files,\n"
"press CANCEL to ASK individually.",
		         "Overwrite options",
			 MB_YESNOCANCEL | MB_ICONQUESTION);
    if (result == IDYES)
	allow_overwrite = ALWAYS;
    else if (result == IDNO)
	allow_overwrite = NEVER;
    else
	allow_overwrite = ASK;
    goto again;
}

static BOOL notify (int code, char *fmt, ...)
{
    char Buffer[1024];
    va_list marker;
    BOOL result = TRUE;
    int a, b;
    char *cp;

    va_start (marker, fmt);
    _vsnprintf (Buffer, sizeof (Buffer), fmt, marker);

    switch (code) {
/* Questions */
    case CAN_OVERWRITE:
	result = AskOverwrite (Buffer);
	break;

/* Information notification */
    case DIR_CREATED:
	break;

    case FILE_CREATED:
    case FILE_OVERWRITTEN:
	if ((cp = strrchr(fmt, '.')) && (0 == strcmp (cp, ".py")))
	     add_to_filelist (fmt);
	break;

/* Error Messages */
    case ZLIB_ERROR:
	MessageBox (GetFocus(), Buffer, "Error", MB_OK | MB_ICONWARNING);
	break;

    case SYSTEM_ERROR:
	SystemError (GetLastError(), Buffer);
	break;

    case NUM_FILES:
	a = va_arg (marker, int);
	b = va_arg (marker, int);
	SendMessage (hDialog, WM_NUMFILES, 0, MAKELPARAM (0, a));
	SendMessage (hDialog, WM_NEXTFILE, b, (LPARAM)fmt);
    }
    va_end (marker);
    
    return result;
}

static char *MapExistingFile (char *pathname, DWORD *psize)
{
    HANDLE hFile, hFileMapping;
    DWORD nSizeLow, nSizeHigh;
    char *data;

    hFile = CreateFile (pathname,
	GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
	FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
	return NULL;
    nSizeLow = GetFileSize (hFile, &nSizeHigh);
    hFileMapping = CreateFileMapping (hFile,
	NULL, PAGE_READONLY, 0, 0, NULL);
    CloseHandle (hFile);

    if (hFileMapping == INVALID_HANDLE_VALUE)
	return NULL;
    
    data = MapViewOfFile (hFileMapping,
	FILE_MAP_READ, 0, 0, 0);

    CloseHandle (hFileMapping);
    *psize = nSizeLow;
    return data;
}

static char *ExtractIniFile (char *data, DWORD size)
{
    /* read the end of central directory record */
    struct eof_cdir *pe = (struct eof_cdir *)&data[size - sizeof
						  (struct eof_cdir)];
    
    int arc_start = size - sizeof (struct eof_cdir) - pe->nBytesCDir -
	pe->ofsCDir;

    int ofs = arc_start - sizeof (struct meta_data_hdr);

    /* read meta_data info */
    struct meta_data_hdr *pmd = (struct meta_data_hdr *)&data[ofs];
    char *src, *dst;
    char *ini_file;
    char tempdir[MAX_PATH];

    if (pe->tag != 0x06054b50) {
	SystemError (0, "Setup program invalid or damaged");
	return NULL;
    }

    if (pmd->tag != 0x12345679 || ofs < 0) {
	SystemError (0, "Setup program invalid or damaged");
	return NULL;
    }

    src = ((char *)pmd) - pmd->uncomp_size;
    ini_file = malloc (MAX_PATH); /* will be returned, so do not free it */
    if (!ini_file)
	return NULL;
    if (!GetTempPath (sizeof (tempdir), tempdir)
	|| !GetTempFileName (tempdir, "~du", 0, ini_file)) {
	SystemError (GetLastError(), "Could not create temporary file");
	return NULL;
    }
    
    dst = map_new_file (CREATE_ALWAYS, ini_file, NULL, pmd->uncomp_size,
			0, 0, notify);
    if (!dst)
	return NULL;
    memcpy (dst, src, pmd->uncomp_size);
    UnmapViewOfFile(dst);
    return ini_file;
}

static void PumpMessages (void)
{
    MSG msg;
    while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
}

LRESULT CALLBACK
WindowProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    HFONT hFont;
    int h;
    PAINTSTRUCT ps;
    switch (msg) {
    case WM_PAINT:
	hdc = BeginPaint (hwnd, &ps);
	h = GetSystemMetrics (SM_CYSCREEN) / 10;
	hFont = CreateFont (h, 0, 0, 0, 700, TRUE,
			    0, 0, 0, 0, 0, 0, 0, "Times Roman");
	hFont = SelectObject (hdc, hFont);
	SetBkMode (hdc, TRANSPARENT);
	TextOut (hdc, 15, 15, title, strlen (title));
	SetTextColor (hdc, RGB (255, 255, 255));
	TextOut (hdc, 10, 10, title, strlen (title));
	DeleteObject (SelectObject (hdc, hFont));
	EndPaint (hwnd, &ps);
	return 0;
    }
    return DefWindowProc (hwnd, msg, wParam, lParam);
}

static HWND CreateBackground (char *title)
{
    WNDCLASS wc;
    HWND hwnd;
    char buffer[4096];

    wc.style = CS_VREDRAW | CS_HREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbWndExtra = 0;
    wc.cbClsExtra = 0;
    wc.hInstance = GetModuleHandle (NULL);
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.hbrBackground = CreateSolidBrush (RGB (0, 0, 128));
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "SetupWindowClass";

    if (!RegisterClass (&wc))
	MessageBox (hwndMain,
		    "Could not register window class",
		    "Setup.exe", MB_OK);

    wsprintf (buffer, "Setup %s", title);
    hwnd = CreateWindow ("SetupWindowClass",
			 buffer,
			 0,
			 0, 0,
			 GetSystemMetrics (SM_CXFULLSCREEN),
			 GetSystemMetrics (SM_CYFULLSCREEN),
			 NULL,
			 NULL,
			 GetModuleHandle (NULL),
			 NULL);
    ShowWindow (hwnd, SW_SHOWMAXIMIZED);
    UpdateWindow (hwnd);
    return hwnd;
}

/*
 * Center a window on the screen
 */
static void CenterWindow (HWND hwnd)
{
	RECT rc;
	int w, h;

	GetWindowRect (hwnd, &rc);
	w = GetSystemMetrics (SM_CXSCREEN);
	h = GetSystemMetrics (SM_CYSCREEN);
	MoveWindow (hwnd, (w - (rc.right-rc.left))/2, (h - (rc.bottom-rc.top))/2,
		rc.right-rc.left, rc.bottom-rc.top, FALSE);
}

#include <prsht.h>

BOOL CALLBACK
IntroDlgProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    LPNMHDR lpnm;
    char Buffer[4096];
    switch (msg) {
    case WM_INITDIALOG:
	CenterWindow (GetParent (hwnd));
	wsprintf (Buffer,
		  "This Wizard will install %s on your computer. "
		  "Click Next to continue or Cancel to exit the Setup Wizard.",
		  meta_name);
	SetDlgItemText (hwnd, IDC_TITLE, Buffer);
	SetDlgItemText (hwnd, IDC_INTRO_TEXT, info);
	SetDlgItemText (hwnd, IDC_BUILD_INFO, build_info);
	return FALSE;

    case WM_NOTIFY:
        lpnm = (LPNMHDR) lParam;

        switch (lpnm->code) {
	case PSN_SETACTIVE:
	    PropSheet_SetWizButtons(GetParent(hwnd), PSWIZB_NEXT);
	    break;

	case PSN_WIZNEXT:
	    break;

	case PSN_RESET:
	    break;
		
	default:
	    break;
	}
    }
    return FALSE;
}

/*
 * Fill the listbox specified by hwnd with all python versions found
 * in the registry. version, if not NULL or empty, is the version
 * required.
 */
static BOOL GetPythonVersions (HWND hwnd, HKEY hkRoot, LPSTR version)
{
    DWORD index = 0;
    char core_version[80];
    HKEY hKey;
    BOOL result = TRUE;
    DWORD bufsize;

    if (ERROR_SUCCESS != RegOpenKeyEx (hkRoot,
				     "Software\\Python\\PythonCore",
				     0,	KEY_READ, &hKey))
	return FALSE;
    bufsize = sizeof (core_version);
    while (ERROR_SUCCESS == RegEnumKeyEx (hKey, index,
					  core_version, &bufsize, NULL,
					  NULL, NULL, NULL)) {
	char subkey_name[80], vers_name[80], prefix_buf[MAX_PATH+1];
	int itemindex;
	DWORD value_size;
	HKEY hk;

	bufsize = sizeof (core_version);
	++index;
	if (version && *version && strcmp (version, core_version))
		continue;

	wsprintf (vers_name, "Python Version %s (found in registry)",
		  core_version);
	itemindex = SendMessage (hwnd, LB_ADDSTRING, 0,
				 (LPARAM)(LPSTR)vers_name);
	wsprintf (subkey_name,
		  "Software\\Python\\PythonCore\\%s\\InstallPath",
		  core_version);
	value_size = sizeof (subkey_name);
	RegOpenKeyEx (hkRoot, subkey_name, 0, KEY_READ, &hk);
	RegQueryValueEx (hk, NULL, NULL, NULL, prefix_buf,
			 &value_size);
	RegCloseKey (hk);
	SendMessage (hwnd, LB_SETITEMDATA, itemindex,
		     (LPARAM)(LPSTR)strdup (prefix_buf));
    }
    RegCloseKey (hKey);
    return result;
}

BOOL CALLBACK
SelectPythonDlgProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    LPNMHDR lpnm;

    switch (msg) {
    case WM_INITDIALOG:
	GetPythonVersions (GetDlgItem (hwnd, IDC_VERSIONS_LIST),
			   HKEY_LOCAL_MACHINE, target_version);
	GetPythonVersions (GetDlgItem (hwnd, IDC_VERSIONS_LIST),
			   HKEY_CURRENT_USER, target_version);
	{	/* select the last entry which is the highest python
		   version found */
	    int count;
	    count = SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST,
					LB_GETCOUNT, 0, 0);
	    if (count && count != LB_ERR)
		SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST, LB_SETCURSEL,
				    count-1, 0);
	}
	goto UpdateInstallDir;
	break;

    case WM_COMMAND:
	switch (LOWORD (wParam)) {
	case IDC_VERSIONS_LIST:
	    switch (HIWORD (wParam)) {
		int id;
		char *cp;
	    case LBN_SELCHANGE:
	      UpdateInstallDir:
		PropSheet_SetWizButtons(GetParent(hwnd),
					PSWIZB_BACK | PSWIZB_NEXT);
		id = SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST,
					 LB_GETCURSEL, 0, 0);
		if (id == LB_ERR) {
		    PropSheet_SetWizButtons(GetParent(hwnd),
					    PSWIZB_BACK);
		    SetDlgItemText (hwnd, IDC_PATH, "");
		    strcpy (install_dir, "");
		    strcpy (pythondll, "");
		} else {
		    char *pbuf;
		    int result;
		    PropSheet_SetWizButtons(GetParent(hwnd),
					    PSWIZB_BACK | PSWIZB_NEXT);
		    cp = (LPSTR)SendDlgItemMessage (hwnd,
						    IDC_VERSIONS_LIST,
						    LB_GETITEMDATA,
						    id,
						    0);
		    strcpy (install_dir, cp);
		    SetDlgItemText (hwnd, IDC_PATH, install_dir);
		    result = SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST,
					LB_GETTEXTLEN, (WPARAM)id, 0);
		    pbuf = (char *)malloc (result + 1);
		    if (pbuf) {
			/* guess the name of the python-dll */
			int major, minor;
			SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST,
					    LB_GETTEXT, (WPARAM)id,
					    (LPARAM)pbuf);
			result = sscanf (pbuf, "Python Version %d.%d",
					 &major, &minor);
			if (result == 2)
			    wsprintf (pythondll, "python%d%d.dll",
				      major, minor);
			free (pbuf);
		    } else
			strcpy (pythondll, "");
		}
	    }
	    break;
	}
	return 0;

    case WM_NOTIFY:
        lpnm = (LPNMHDR) lParam;

        switch (lpnm->code) {
	    int id;
	case PSN_SETACTIVE:
	    id = SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST,
				     LB_GETCURSEL, 0, 0);
	    if (id == LB_ERR)
		PropSheet_SetWizButtons(GetParent(hwnd),
					PSWIZB_BACK);
	    else
		PropSheet_SetWizButtons(GetParent(hwnd),
					PSWIZB_BACK | PSWIZB_NEXT);
	    break;

	case PSN_WIZNEXT:
	    break;

	case PSN_WIZFINISH:
	    break;

	case PSN_RESET:
	    break;
		
	default:
	    break;
	}
    }
    return 0;
}

BOOL CALLBACK
InstallFilesDlgProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    LPNMHDR lpnm;
    char Buffer[4096];

    switch (msg) {
    case WM_INITDIALOG:
	wsprintf (Buffer,
		  "Click Next to begin the installation of %s. "
		  "If you want to review or change any of your "
		  " installation settings, click Back. "
		  "Click Cancel to exit the wizard.",
		  meta_name);
	SetDlgItemText (hwnd, IDC_TITLE, Buffer);
	break;

    case WM_NUMFILES:
	SendDlgItemMessage (hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, lParam);
	PumpMessages ();
	return TRUE;

    case WM_NEXTFILE:
	SendDlgItemMessage (hwnd, IDC_PROGRESS, PBM_SETPOS, wParam,
			    0);
	SetDlgItemText (hwnd, IDC_INFO, (LPSTR)lParam);
	PumpMessages ();
	return TRUE;

    case WM_NOTIFY:
        lpnm = (LPNMHDR) lParam;

        switch (lpnm->code) {
	case PSN_SETACTIVE:
	    PropSheet_SetWizButtons(GetParent(hwnd),
				    PSWIZB_BACK | PSWIZB_NEXT);

	    break;

	case PSN_WIZFINISH:
	    break;

	case PSN_WIZNEXT:
	    /* Handle a Next button click here */
	    hDialog = hwnd;

	    /* Make sure the installation directory name ends in a */
	    /* backslash */
	    if (install_dir[strlen(install_dir)-1] != '\\')
		strcat (install_dir, "\\");
	    /* Strip the trailing backslash again */
	    install_dir[strlen(install_dir)-1] = '\0';
	    

	    /* Extract all files from the archive */
	    SetDlgItemText (hwnd, IDC_TITLE, "Installing files...");
	    success = unzip_archive (install_dir, arc_data,
				    arc_size, notify);
	    /* Compile the py-files */
	    if (pyc_compile) {
		SetDlgItemText (hwnd, IDC_TITLE,
				"Compiling files to .pyc...");
		compile_filelist (FALSE);
	    }
	    if (pyo_compile) {
		SetDlgItemText (hwnd, IDC_TITLE,
				"Compiling files to .pyo...");
		compile_filelist (TRUE);
	    }
	    break;

	case PSN_RESET:
	    break;
		
	default:
	    break;
	}
    }
    return 0;
}


BOOL CALLBACK
FinishedDlgProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    LPNMHDR lpnm;

    switch (msg) {
    case WM_INITDIALOG:
	if (!success)
	    SetDlgItemText (hwnd, IDC_INFO, "Installation failed.");
	break;

    case WM_NOTIFY:
        lpnm = (LPNMHDR) lParam;

        switch (lpnm->code) {
	case PSN_SETACTIVE: /* Enable the Finish button */
	    PropSheet_SetWizButtons(GetParent(hwnd), PSWIZB_FINISH);
	    break;

	case PSN_WIZNEXT:
	    break;

	case PSN_WIZFINISH:
	    break;

	case PSN_RESET:
	    break;
		
	default:
	    break;
	}
    }
    return 0;
}

void RunWizard (HWND hwnd)
{
    PROPSHEETPAGE   psp =       {0};
    HPROPSHEETPAGE  ahpsp[4] =  {0};
    PROPSHEETHEADER psh =       {0};

    /* Display module information */
    psp.dwSize =        sizeof(psp);
    psp.dwFlags =       PSP_DEFAULT|PSP_HIDEHEADER;
    psp.hInstance =     GetModuleHandle (NULL);
    psp.lParam =        0;
    psp.pfnDlgProc =    IntroDlgProc;
    psp.pszTemplate =   MAKEINTRESOURCE(IDD_INTRO);

    ahpsp[0] =          CreatePropertySheetPage(&psp);

    /* Select python version to use */
    psp.dwFlags =       PSP_DEFAULT|PSP_HIDEHEADER;
    psp.pszTemplate =       MAKEINTRESOURCE(IDD_SELECTPYTHON);
    psp.pfnDlgProc =        SelectPythonDlgProc;

    ahpsp[1] =              CreatePropertySheetPage(&psp);

    /* Install the files */
    psp.dwFlags =	    PSP_DEFAULT|PSP_HIDEHEADER;
    psp.pszTemplate =       MAKEINTRESOURCE(IDD_INSTALLFILES);
    psp.pfnDlgProc =        InstallFilesDlgProc;

    ahpsp[2] =              CreatePropertySheetPage(&psp);

    /* Show success or failure */
    psp.dwFlags =           PSP_DEFAULT|PSP_HIDEHEADER;
    psp.pszTemplate =       MAKEINTRESOURCE(IDD_FINISHED);
    psp.pfnDlgProc =        FinishedDlgProc;

    ahpsp[3] =              CreatePropertySheetPage(&psp);

    /* Create the property sheet */
    psh.dwSize =            sizeof(psh);
    psh.hInstance =         GetModuleHandle (NULL);
    psh.hwndParent =        hwnd;
    psh.phpage =            ahpsp;
    psh.dwFlags =           PSH_WIZARD/*97*//*|PSH_WATERMARK|PSH_HEADER*/;
    psh.pszbmWatermark =    NULL;
    psh.pszbmHeader =       NULL;
    psh.nStartPage =        0;
    psh.nPages =            4;

    PropertySheet(&psh);
}

int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInst,
		    LPSTR lpszCmdLine, INT nCmdShow)
{
    char modulename[MAX_PATH];

    GetModuleFileName (NULL, modulename, sizeof (modulename));

    /* Map the executable file to memory */
    arc_data = MapExistingFile (modulename, &arc_size);
    if (!arc_data) {
	SystemError (GetLastError(), "Could not open archive");
	return 1;
    }

    /* Extract the configuration data into a temporary file */
    ini_file = ExtractIniFile (arc_data, arc_size);
    if (!ini_file) {
	return 1;
    }

    /* Read installation information */
    GetPrivateProfileString ("Setup", "title", "", title,
			     sizeof (title), ini_file);
    unescape (title);

    GetPrivateProfileString ("Setup", "info", "", info,
			     sizeof (info), ini_file);
    unescape (info);

    GetPrivateProfileString ("Setup", "build_info", "", build_info,
			     sizeof (build_info), ini_file);

    pyc_compile = GetPrivateProfileInt ("Setup", "target_compile", 1,
					ini_file);
    pyo_compile = GetPrivateProfileInt ("Setup", "target_optimize", 1,
					ini_file);

    GetPrivateProfileString ("Setup", "target_version", "",
			     target_version, sizeof (target_version),
			     ini_file);

    GetPrivateProfileString ("metadata", "name", "",
			     meta_name, sizeof (meta_name),
			     ini_file);

    hwndMain = CreateBackground (title);

    RunWizard (hwndMain);

    /* Clean up */
    UnmapViewOfFile (arc_data);
    if (ini_file)
	DeleteFile (ini_file);

    return 0;
}
