/*
 * Add/Remove Programs applet
 * Partially based on Wine Uninstaller
 *
 * Copyright 2000 Andreas Mohr
 * Copyright 2004 Hannu Valtonen
 * Copyright 2005 Jonathan Ernst
 * Copyright 2001-2002, 2008 Owen Rudge
 *
 * 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 St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 */

#define NONAMELESSUNION

#include "config.h"
#include "wine/port.h"

#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <windef.h>
#include <winbase.h>
#include <winuser.h>
#include <wingdi.h>
#include <winreg.h>
#include <shellapi.h>
#include <commctrl.h>
#include <commdlg.h>
#include <cpl.h>

#include "wine/unicode.h"
#include "wine/list.h"
#include "wine/debug.h"
#include "appwiz.h"
#include "res.h"

WINE_DEFAULT_DEBUG_CHANNEL(appwizcpl);

/* define a maximum length for various buffers we use */
#define MAX_STRING_LEN    1024

typedef struct APPINFO
{
    struct list entry;
    int id;

    LPWSTR title;
    LPWSTR path;
    LPWSTR path_modify;

    LPWSTR icon;
    int iconIdx;

    LPWSTR publisher;
    LPWSTR version;

    HKEY regroot;
    WCHAR regkey[MAX_STRING_LEN];
} APPINFO;

static struct list app_list = LIST_INIT( app_list );
HINSTANCE hInst;

static WCHAR btnRemove[MAX_STRING_LEN];
static WCHAR btnModifyRemove[MAX_STRING_LEN];

static const WCHAR openW[] = {'o','p','e','n',0};

/* names of registry keys */
static const WCHAR BackSlashW[] = { '\\', 0 };
static const WCHAR DisplayNameW[] = {'D','i','s','p','l','a','y','N','a','m','e',0};
static const WCHAR DisplayIconW[] = {'D','i','s','p','l','a','y','I','c','o','n',0};
static const WCHAR DisplayVersionW[] = {'D','i','s','p','l','a','y','V','e','r',
    's','i','o','n',0};
static const WCHAR PublisherW[] = {'P','u','b','l','i','s','h','e','r',0};
static const WCHAR ContactW[] = {'C','o','n','t','a','c','t',0};
static const WCHAR HelpLinkW[] = {'H','e','l','p','L','i','n','k',0};
static const WCHAR HelpTelephoneW[] = {'H','e','l','p','T','e','l','e','p','h',
    'o','n','e',0};
static const WCHAR ModifyPathW[] = {'M','o','d','i','f','y','P','a','t','h',0};
static const WCHAR NoModifyW[] = {'N','o','M','o','d','i','f','y',0};
static const WCHAR ReadmeW[] = {'R','e','a','d','m','e',0};
static const WCHAR URLUpdateInfoW[] = {'U','R','L','U','p','d','a','t','e','I',
    'n','f','o',0};
static const WCHAR CommentsW[] = {'C','o','m','m','e','n','t','s',0};
static const WCHAR UninstallCommandlineW[] = {'U','n','i','n','s','t','a','l','l',
    'S','t','r','i','n','g',0};
static const WCHAR WindowsInstallerW[] = {'W','i','n','d','o','w','s','I','n','s','t','a','l','l','e','r',0};
static const WCHAR SystemComponentW[] = {'S','y','s','t','e','m','C','o','m','p','o','n','e','n','t',0};

static const WCHAR PathUninstallW[] = {
        'S','o','f','t','w','a','r','e','\\',
        'M','i','c','r','o','s','o','f','t','\\',
        'W','i','n','d','o','w','s','\\',
        'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
        'U','n','i','n','s','t','a','l','l',0 };

/******************************************************************************
 * Name       : DllMain
 * Description: Entry point for DLL file
 */
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
                    LPVOID lpvReserved)
{
    TRACE("(%p, %d, %p)\n", hinstDLL, fdwReason, lpvReserved);

    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            hInst = hinstDLL;
            break;
    }
    return TRUE;
}

/******************************************************************************
 * Name       : FreeAppInfo
 * Description: Frees memory used by an AppInfo structure, and any children.
 */
static void FreeAppInfo(APPINFO *info)
{
    HeapFree(GetProcessHeap(), 0, info->title);
    HeapFree(GetProcessHeap(), 0, info->path);
    HeapFree(GetProcessHeap(), 0, info->path_modify);
    HeapFree(GetProcessHeap(), 0, info->icon);
    HeapFree(GetProcessHeap(), 0, info->publisher);
    HeapFree(GetProcessHeap(), 0, info->version);
    HeapFree(GetProcessHeap(), 0, info);
}

/******************************************************************************
 * Name       : ReadApplicationsFromRegistry
 * Description: Creates a linked list of uninstallable applications from the
 *              registry.
 * Parameters : root    - Which registry root to read from
 * Returns    : TRUE if successful, FALSE otherwise
 */
static BOOL ReadApplicationsFromRegistry(HKEY root)
{
    HKEY hkeyApp;
    int i;
    static int id = 0;
    DWORD sizeOfSubKeyName, displen, uninstlen;
    DWORD dwNoModify, dwType, value, size;
    WCHAR subKeyName[256];
    WCHAR *command;
    APPINFO *info = NULL;
    LPWSTR iconPtr;

    sizeOfSubKeyName = sizeof(subKeyName) / sizeof(subKeyName[0]);

    for (i = 0; RegEnumKeyExW(root, i, subKeyName, &sizeOfSubKeyName, NULL,
        NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS; ++i)
    {
        RegOpenKeyExW(root, subKeyName, 0, KEY_READ, &hkeyApp);
        size = sizeof(value);
        if (!RegQueryValueExW(hkeyApp, SystemComponentW, NULL, &dwType, (LPBYTE)&value, &size)
            && dwType == REG_DWORD && value == 1)
        {
            RegCloseKey(hkeyApp);
            sizeOfSubKeyName = sizeof(subKeyName) / sizeof(subKeyName[0]);
            continue;
        }
        displen = 0;
        uninstlen = 0;
        if (!RegQueryValueExW(hkeyApp, DisplayNameW, 0, 0, NULL, &displen))
        {
            size = sizeof(value);
            if (!RegQueryValueExW(hkeyApp, WindowsInstallerW, NULL, &dwType, (LPBYTE)&value, &size)
                && dwType == REG_DWORD && value == 1)
            {
                static const WCHAR fmtW[] = {'m','s','i','e','x','e','c',' ','/','x','%','s',0};
                int len = lstrlenW(fmtW) + lstrlenW(subKeyName);

                if (!(command = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)))) goto err;
                wsprintfW(command, fmtW, subKeyName);
            }
            else if (!RegQueryValueExW(hkeyApp, UninstallCommandlineW, 0, 0, NULL, &uninstlen))
            {
                if (!(command = HeapAlloc(GetProcessHeap(), 0, uninstlen))) goto err;
                RegQueryValueExW(hkeyApp, UninstallCommandlineW, 0, 0, (LPBYTE)command, &uninstlen);
            }
            else
            {
                RegCloseKey(hkeyApp);
                sizeOfSubKeyName = sizeof(subKeyName) / sizeof(subKeyName[0]);
                continue;
            }

            info = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct APPINFO));
            if (!info) goto err;

            info->title = HeapAlloc(GetProcessHeap(), 0, displen);

            if (!info->title)
                goto err;

            RegQueryValueExW(hkeyApp, DisplayNameW, 0, 0, (LPBYTE)info->title,
                &displen);

            /* now get DisplayIcon */
            displen = 0;
            RegQueryValueExW(hkeyApp, DisplayIconW, 0, 0, NULL, &displen);

            if (displen == 0)
                info->icon = 0;
            else
            {
                info->icon = HeapAlloc(GetProcessHeap(), 0, displen);

                if (!info->icon)
                    goto err;

                RegQueryValueExW(hkeyApp, DisplayIconW, 0, 0, (LPBYTE)info->icon,
                    &displen);

                /* separate the index from the icon name, if supplied */
                iconPtr = strchrW(info->icon, ',');

                if (iconPtr)
                {
                    *iconPtr++ = 0;
                    info->iconIdx = atoiW(iconPtr);
                }
            }

            /* publisher, version */
            if (RegQueryValueExW(hkeyApp, PublisherW, 0, 0, NULL, &displen) ==
                ERROR_SUCCESS)
            {
                info->publisher = HeapAlloc(GetProcessHeap(), 0, displen);

                if (!info->publisher)
                    goto err;

                RegQueryValueExW(hkeyApp, PublisherW, 0, 0, (LPBYTE)info->publisher,
                    &displen);
            }

            if (RegQueryValueExW(hkeyApp, DisplayVersionW, 0, 0, NULL, &displen) ==
                ERROR_SUCCESS)
            {
                info->version = HeapAlloc(GetProcessHeap(), 0, displen);

                if (!info->version)
                    goto err;

                RegQueryValueExW(hkeyApp, DisplayVersionW, 0, 0, (LPBYTE)info->version,
                    &displen);
            }

            /* Check if NoModify is set */
            dwType = REG_DWORD;
            dwNoModify = 0;
            displen = sizeof(DWORD);

            if (RegQueryValueExW(hkeyApp, NoModifyW, NULL, &dwType, (LPBYTE)&dwNoModify, &displen)
                != ERROR_SUCCESS)
            {
                dwNoModify = 0;
            }

            /* Some installers incorrectly create a REG_SZ instead of a REG_DWORD */
            if (dwType == REG_SZ)
                dwNoModify = (*(BYTE *)&dwNoModify == '1');

            /* Fetch the modify path */
            if (!dwNoModify)
            {
                size = sizeof(value);
                if (!RegQueryValueExW(hkeyApp, WindowsInstallerW, NULL, &dwType, (LPBYTE)&value, &size)
                    && dwType == REG_DWORD && value == 1)
                {
                    static const WCHAR fmtW[] = {'m','s','i','e','x','e','c',' ','/','i','%','s',0};
                    int len = lstrlenW(fmtW) + lstrlenW(subKeyName);

                    if (!(info->path_modify = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)))) goto err;
                    wsprintfW(info->path_modify, fmtW, subKeyName);
                }
                else if (!RegQueryValueExW(hkeyApp, ModifyPathW, 0, 0, NULL, &displen))
                {
                    if (!(info->path_modify = HeapAlloc(GetProcessHeap(), 0, displen))) goto err;
                    RegQueryValueExW(hkeyApp, ModifyPathW, 0, 0, (LPBYTE)info->path_modify, &displen);
                }
            }

            /* registry key */
            RegOpenKeyExW(root, NULL, 0, KEY_READ, &info->regroot);
            lstrcpyW(info->regkey, subKeyName);
            info->path = command;

            info->id = id++;
            list_add_tail( &app_list, &info->entry );
        }

        RegCloseKey(hkeyApp);
        sizeOfSubKeyName = sizeof(subKeyName) / sizeof(subKeyName[0]);
    }

    return TRUE;
err:
    RegCloseKey(hkeyApp);
    if (info) FreeAppInfo(info);
    HeapFree(GetProcessHeap(), 0, command);
    return FALSE;
}


/******************************************************************************
 * Name       : AddApplicationsToList
 * Description: Populates the list box with applications.
 * Parameters : hWnd    - Handle of the dialog box
 */
static void AddApplicationsToList(HWND hWnd, HIMAGELIST hList)
{
    APPINFO *iter;
    LVITEMW lvItem;
    HICON hIcon;
    int index;

    LIST_FOR_EACH_ENTRY( iter, &app_list, APPINFO, entry )
    {
        if (!iter->title[0]) continue;

        /* get the icon */
        index = 0;

        if (iter->icon)
        {
            if (ExtractIconExW(iter->icon, iter->iconIdx, NULL, &hIcon, 1) == 1)
            {
                index = ImageList_AddIcon(hList, hIcon);
                DestroyIcon(hIcon);
            }
        }

        lvItem.mask = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM;
        lvItem.iItem = iter->id;
        lvItem.iSubItem = 0;
        lvItem.pszText = iter->title;
        lvItem.iImage = index;
        lvItem.lParam = iter->id;

        index = ListView_InsertItemW(hWnd, &lvItem);

        /* now add the subitems (columns) */
        ListView_SetItemTextW(hWnd, index, 1, iter->publisher);
        ListView_SetItemTextW(hWnd, index, 2, iter->version);
    }
}

/******************************************************************************
 * Name       : RemoveItemsFromList
 * Description: Clears the application list box.
 * Parameters : hWnd    - Handle of the dialog box
 */
static void RemoveItemsFromList(HWND hWnd)
{
    SendDlgItemMessageW(hWnd, IDL_PROGRAMS, LVM_DELETEALLITEMS, 0, 0);
}

/******************************************************************************
 * Name       : EmptyList
 * Description: Frees memory used by the application linked list.
 */
static inline void EmptyList(void)
{
    APPINFO *info, *next;
    LIST_FOR_EACH_ENTRY_SAFE( info, next, &app_list, APPINFO, entry )
    {
        list_remove( &info->entry );
        FreeAppInfo( info );
    }
}

/******************************************************************************
 * Name       : UpdateButtons
 * Description: Enables/disables the Add/Remove button depending on current
 *              selection in list box.
 * Parameters : hWnd    - Handle of the dialog box
 */
static void UpdateButtons(HWND hWnd)
{
    APPINFO *iter;
    LVITEMW lvItem;
    LRESULT selitem = SendDlgItemMessageW(hWnd, IDL_PROGRAMS, LVM_GETNEXTITEM, -1,
       LVNI_FOCUSED | LVNI_SELECTED);
    BOOL enable_modify = FALSE;

    if (selitem != -1)
    {
        lvItem.iItem = selitem;
        lvItem.mask = LVIF_PARAM;

        if (SendDlgItemMessageW(hWnd, IDL_PROGRAMS, LVM_GETITEMW, 0, (LPARAM) &lvItem))
        {
            LIST_FOR_EACH_ENTRY( iter, &app_list, APPINFO, entry )
            {
                if (iter->id == lvItem.lParam)
                {
                    /* Decide whether to display Modify/Remove as one button or two */
                    enable_modify = (iter->path_modify != NULL);

                    /* Update title as appropriate */
                    if (iter->path_modify == NULL)
                        SetWindowTextW(GetDlgItem(hWnd, IDC_ADDREMOVE), btnModifyRemove);
                    else
                        SetWindowTextW(GetDlgItem(hWnd, IDC_ADDREMOVE), btnRemove);

                    break;
                }
            }
        }
    }

    /* Enable/disable other buttons if necessary */
    EnableWindow(GetDlgItem(hWnd, IDC_ADDREMOVE), (selitem != -1));
    EnableWindow(GetDlgItem(hWnd, IDC_SUPPORT_INFO), (selitem != -1));
    EnableWindow(GetDlgItem(hWnd, IDC_MODIFY), enable_modify);
}

/******************************************************************************
 * Name       : InstallProgram
 * Description: Search for potential Installer and execute it.
 * Parameters : hWnd    - Handle of the dialog box
 */
static void InstallProgram(HWND hWnd)
{
    static const WCHAR filters[] = {'%','s','%','c','*','i','n','s','t','a','l','*','.','e','x','e',';','*','s','e','t','u','p','*','.','e','x','e',';','*','.','m','s','i','%','c','%','s','%','c','*','.','e','x','e','%','c','%','s','%','c','*','.','*','%','c',0}
;
    OPENFILENAMEW ofn;
    WCHAR titleW[MAX_STRING_LEN];
    WCHAR filter_installs[MAX_STRING_LEN];
    WCHAR filter_programs[MAX_STRING_LEN];
    WCHAR filter_all[MAX_STRING_LEN];
    WCHAR FilterBufferW[MAX_PATH];
    WCHAR FileNameBufferW[MAX_PATH];

    LoadStringW(hInst, IDS_CPL_TITLE, titleW, sizeof(titleW)/sizeof(WCHAR));
    LoadStringW(hInst, IDS_FILTER_INSTALLS, filter_installs, sizeof(filter_installs)/sizeof(WCHAR));
    LoadStringW(hInst, IDS_FILTER_PROGRAMS, filter_programs, sizeof(filter_programs)/sizeof(WCHAR));
    LoadStringW(hInst, IDS_FILTER_ALL, filter_all, sizeof(filter_all)/sizeof(WCHAR));

    snprintfW( FilterBufferW, MAX_PATH, filters, filter_installs, 0, 0,
               filter_programs, 0, 0, filter_all, 0, 0 );
    memset(&ofn, 0, sizeof(OPENFILENAMEW));
    ofn.lStructSize = sizeof(OPENFILENAMEW);
    ofn.hwndOwner = hWnd;
    ofn.hInstance = hInst;
    ofn.lpstrFilter = FilterBufferW;
    ofn.nFilterIndex = 0;
    ofn.lpstrFile = FileNameBufferW;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrTitle = titleW;
    ofn.Flags = OFN_HIDEREADONLY | OFN_ENABLESIZING;
    FileNameBufferW[0] = 0;

    if (GetOpenFileNameW(&ofn))
    {
        SHELLEXECUTEINFOW sei;
        memset(&sei, 0, sizeof(sei));
        sei.cbSize = sizeof(sei);
        sei.lpVerb = openW;
        sei.nShow = SW_SHOWDEFAULT;
        sei.fMask = 0;
        sei.lpFile = ofn.lpstrFile;

        ShellExecuteExW(&sei);
    }
}

/******************************************************************************
 * Name       : UninstallProgram
 * Description: Executes the specified program's installer.
 * Parameters : id      - the internal ID of the installer to remove
 * Parameters : button  - ID of button pressed (Modify or Remove)
 */
static void UninstallProgram(int id, DWORD button)
{
    APPINFO *iter;
    STARTUPINFOW si;
    PROCESS_INFORMATION info;
    WCHAR errormsg[MAX_STRING_LEN];
    WCHAR sUninstallFailed[MAX_STRING_LEN];
    BOOL res;

    LoadStringW(hInst, IDS_UNINSTALL_FAILED, sUninstallFailed,
        sizeof(sUninstallFailed) / sizeof(sUninstallFailed[0]));

    LIST_FOR_EACH_ENTRY( iter, &app_list, APPINFO, entry )
    {
        if (iter->id == id)
        {
            TRACE("Uninstalling %s (%s)\n", wine_dbgstr_w(iter->title),
                wine_dbgstr_w(iter->path));

            memset(&si, 0, sizeof(STARTUPINFOW));
            si.cb = sizeof(STARTUPINFOW);
            si.wShowWindow = SW_NORMAL;

            res = CreateProcessW(NULL, (button == IDC_MODIFY) ? iter->path_modify : iter->path,
                NULL, NULL, FALSE, 0, NULL, NULL, &si, &info);

            if (res)
            {
                CloseHandle(info.hThread);

                /* wait for the process to exit */
                WaitForSingleObject(info.hProcess, INFINITE);
                CloseHandle(info.hProcess);
            }
            else
            {
                wsprintfW(errormsg, sUninstallFailed, iter->path);

                if (MessageBoxW(0, errormsg, iter->title, MB_YESNO |
                    MB_ICONQUESTION) == IDYES)
                {
                    /* delete the application's uninstall entry */
                    RegDeleteKeyW(iter->regroot, iter->regkey);
                    RegCloseKey(iter->regroot);
                }
            }

            break;
        }
    }
}

/**********************************************************************************
 * Name       : SetInfoDialogText
 * Description: Sets the text of a label in a window, based upon a registry entry
 *              or string passed to the function.
 * Parameters : hKey         - registry entry to read from, NULL if not reading
 *                             from registry
 *              lpKeyName    - key to read from, or string to check if hKey is NULL
 *              lpAltMessage - alternative message if entry not found
 *              hWnd         - handle of dialog box
 *              iDlgItem     - ID of label in dialog box
 */
static void SetInfoDialogText(HKEY hKey, LPCWSTR lpKeyName, LPCWSTR lpAltMessage,
  HWND hWnd, int iDlgItem)
{
    WCHAR buf[MAX_STRING_LEN];
    DWORD buflen;
    HWND hWndDlgItem;

    hWndDlgItem = GetDlgItem(hWnd, iDlgItem);

    /* if hKey is null, lpKeyName contains the string we want to check */
    if (hKey == NULL)
    {
        if (lpKeyName && lpKeyName[0])
            SetWindowTextW(hWndDlgItem, lpKeyName);
        else
            SetWindowTextW(hWndDlgItem, lpAltMessage);
    }
    else
    {
        buflen = MAX_STRING_LEN;

        if ((RegQueryValueExW(hKey, lpKeyName, 0, 0, (LPBYTE) buf, &buflen) ==
           ERROR_SUCCESS) && buf[0])
            SetWindowTextW(hWndDlgItem, buf);
        else
            SetWindowTextW(hWndDlgItem, lpAltMessage);
    }
}

/******************************************************************************
 * Name       : SupportInfoDlgProc
 * Description: Callback procedure for support info dialog
 * Parameters : hWnd    - hWnd of the window
 *              msg     - reason for calling function
 *              wParam  - additional parameter
 *              lParam  - additional parameter
 * Returns    : Depends on the message
 */
static INT_PTR CALLBACK SupportInfoDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    APPINFO *iter;
    HKEY hkey;
    WCHAR oldtitle[MAX_STRING_LEN];
    WCHAR buf[MAX_STRING_LEN];
    WCHAR key[MAX_STRING_LEN];
    WCHAR notfound[MAX_STRING_LEN];

    switch(msg)
    {
        case WM_INITDIALOG:
            LIST_FOR_EACH_ENTRY( iter, &app_list, APPINFO, entry )
            {
                if (iter->id == (int) lParam)
                {
                    lstrcpyW(key, PathUninstallW);
                    lstrcatW(key, BackSlashW);
                    lstrcatW(key, iter->regkey);

                    /* check the application's registry entries */
                    RegOpenKeyExW(iter->regroot, key, 0, KEY_READ, &hkey);

                    /* Load our "not specified" string */
                    LoadStringW(hInst, IDS_NOT_SPECIFIED, notfound,
                        sizeof(notfound) / sizeof(notfound[0]));

                    /* Update the data for items already read into the structure */
                    SetInfoDialogText(NULL, iter->publisher, notfound, hWnd,
                        IDC_INFO_PUBLISHER);
                    SetInfoDialogText(NULL, iter->version, notfound, hWnd,
                        IDC_INFO_VERSION);

                    /* And now update the data for those items in the registry */
                    SetInfoDialogText(hkey, ContactW, notfound, hWnd,
                        IDC_INFO_CONTACT);
                    SetInfoDialogText(hkey, HelpLinkW, notfound, hWnd,
                        IDC_INFO_SUPPORT);
                    SetInfoDialogText(hkey, HelpTelephoneW, notfound, hWnd,
                        IDC_INFO_PHONE);
                    SetInfoDialogText(hkey, ReadmeW, notfound, hWnd,
                        IDC_INFO_README);
                    SetInfoDialogText(hkey, URLUpdateInfoW, notfound, hWnd,
                        IDC_INFO_UPDATES);
                    SetInfoDialogText(hkey, CommentsW, notfound, hWnd,
                        IDC_INFO_COMMENTS);

                    /* Update the main label with the app name */
                    if (GetWindowTextW(GetDlgItem(hWnd, IDC_INFO_LABEL), oldtitle,
                        MAX_STRING_LEN) != 0)
                    {
                        wsprintfW(buf, oldtitle, iter->title);
                        SetWindowTextW(GetDlgItem(hWnd, IDC_INFO_LABEL), buf);
                    }

                    RegCloseKey(hkey);

                    break;
                }
            }

            return TRUE;

        case WM_DESTROY:
            return 0;

        case WM_COMMAND:
            switch (LOWORD(wParam))
            {
                case IDOK:
                    EndDialog(hWnd, TRUE);
                    break;

            }

            return TRUE;
    }

    return FALSE;
}

/******************************************************************************
 * Name       : SupportInfo
 * Description: Displays the Support Information dialog
 * Parameters : hWnd    - Handle of the main dialog
 *              id      - ID of the application to display information for
 */
static void SupportInfo(HWND hWnd, int id)
{
    DialogBoxParamW(hInst, MAKEINTRESOURCEW(IDD_INFO), hWnd, SupportInfoDlgProc, id);
}

/* Definition of column headers for AddListViewColumns function */
typedef struct AppWizColumn {
   int width;
   int fmt;
   int title;
} AppWizColumn;

static const AppWizColumn columns[] = {
    {200, LVCFMT_LEFT, IDS_COLUMN_NAME},
    {150, LVCFMT_LEFT, IDS_COLUMN_PUBLISHER},
    {100, LVCFMT_LEFT, IDS_COLUMN_VERSION},
};

/******************************************************************************
 * Name       : AddListViewColumns
 * Description: Adds column headers to the list view control.
 * Parameters : hWnd    - Handle of the list view control.
 * Returns    : TRUE if completed successfully, FALSE otherwise.
 */
static BOOL AddListViewColumns(HWND hWnd)
{
    WCHAR buf[MAX_STRING_LEN];
    LVCOLUMNW lvc;
    UINT i;

    lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH;

    /* Add the columns */
    for (i = 0; i < sizeof(columns) / sizeof(columns[0]); i++)
    {
        lvc.iSubItem = i;
        lvc.pszText = buf;

        /* set width and format */
        lvc.cx = columns[i].width;
        lvc.fmt = columns[i].fmt;

        LoadStringW(hInst, columns[i].title, buf, sizeof(buf) / sizeof(buf[0]));

        if (ListView_InsertColumnW(hWnd, i, &lvc) == -1)
            return FALSE;
    }

    return TRUE;
}

/******************************************************************************
 * Name       : AddListViewImageList
 * Description: Creates an ImageList for the list view control.
 * Parameters : hWnd    - Handle of the list view control.
 * Returns    : Handle of the image list.
 */
static HIMAGELIST AddListViewImageList(HWND hWnd)
{
    HIMAGELIST hSmall;
    HICON hDefaultIcon;

    hSmall = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
                              ILC_COLOR32 | ILC_MASK, 1, 1);

    /* Add default icon to image list */
    hDefaultIcon = LoadIconW(hInst, MAKEINTRESOURCEW(ICO_MAIN));
    ImageList_AddIcon(hSmall, hDefaultIcon);
    DestroyIcon(hDefaultIcon);

    SendMessageW(hWnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)hSmall);

    return hSmall;
}

/******************************************************************************
 * Name       : ResetApplicationList
 * Description: Empties the app list, if need be, and recreates it.
 * Parameters : bFirstRun  - TRUE if this is the first time this is run, FALSE otherwise
 *              hWnd       - handle of the dialog box
 *              hImageList - handle of the image list
 * Returns    : New handle of the image list.
 */
static HIMAGELIST ResetApplicationList(BOOL bFirstRun, HWND hWnd, HIMAGELIST hImageList)
{
    static const BOOL is_64bit = sizeof(void *) > sizeof(int);
    HWND hWndListView;
    HKEY hkey;

    hWndListView = GetDlgItem(hWnd, IDL_PROGRAMS);

    /* if first run, create the image list and add the listview columns */
    if (bFirstRun)
    {
        if (!AddListViewColumns(hWndListView))
            return NULL;
    }
    else /* we need to remove the existing things first */
    {
        RemoveItemsFromList(hWnd);
        ImageList_Destroy(hImageList);

        /* reset the list, since it's probably changed if the uninstallation was
           successful */
        EmptyList();
    }

    /* now create the image list and add the applications to the listview */
    hImageList = AddListViewImageList(hWndListView);

    if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE, PathUninstallW, 0, KEY_READ, &hkey))
    {
        ReadApplicationsFromRegistry(hkey);
        RegCloseKey(hkey);
    }
    if (is_64bit &&
        !RegOpenKeyExW(HKEY_LOCAL_MACHINE, PathUninstallW, 0, KEY_READ|KEY_WOW64_32KEY, &hkey))
    {
        ReadApplicationsFromRegistry(hkey);
        RegCloseKey(hkey);
    }
    if (!RegOpenKeyExW(HKEY_CURRENT_USER, PathUninstallW, 0, KEY_READ, &hkey))
    {
        ReadApplicationsFromRegistry(hkey);
        RegCloseKey(hkey);
    }

    AddApplicationsToList(hWndListView, hImageList);
    UpdateButtons(hWnd);

    return(hImageList);
}

/******************************************************************************
 * Name       : MainDlgProc
 * Description: Callback procedure for main tab
 * Parameters : hWnd    - hWnd of the window
 *              msg     - reason for calling function
 *              wParam  - additional parameter
 *              lParam  - additional parameter
 * Returns    : Depends on the message
 */
static INT_PTR CALLBACK MainDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    int selitem;
    static HIMAGELIST hImageList;
    LPNMHDR nmh;
    LVITEMW lvItem;

    switch(msg)
    {
        case WM_INITDIALOG:
            hImageList = ResetApplicationList(TRUE, hWnd, hImageList);

            if (!hImageList)
                return FALSE;

            return TRUE;

        case WM_DESTROY:
            RemoveItemsFromList(hWnd);
            ImageList_Destroy(hImageList);

            EmptyList();

            return 0;

        case WM_NOTIFY:
            nmh = (LPNMHDR) lParam;

            switch (nmh->idFrom)
            {
                case IDL_PROGRAMS:
                    switch (nmh->code)
                    {
                        case LVN_ITEMCHANGED:
                            UpdateButtons(hWnd);
                            break;
                    }
                    break;
            }

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam))
            {
                case IDC_INSTALL:
                    InstallProgram(hWnd);
                    break;

                case IDC_ADDREMOVE:
                case IDC_MODIFY:
                    selitem = SendDlgItemMessageW(hWnd, IDL_PROGRAMS,
                        LVM_GETNEXTITEM, -1, LVNI_FOCUSED|LVNI_SELECTED);

                    if (selitem != -1)
                    {
                        lvItem.iItem = selitem;
                        lvItem.mask = LVIF_PARAM;

                        if (SendDlgItemMessageW(hWnd, IDL_PROGRAMS, LVM_GETITEMW,
                          0, (LPARAM) &lvItem))
                            UninstallProgram(lvItem.lParam, LOWORD(wParam));
                    }

                    hImageList = ResetApplicationList(FALSE, hWnd, hImageList);

                    break;

                case IDC_SUPPORT_INFO:
                    selitem = SendDlgItemMessageW(hWnd, IDL_PROGRAMS,
                        LVM_GETNEXTITEM, -1, LVNI_FOCUSED | LVNI_SELECTED);

                    if (selitem != -1)
                    {
                        lvItem.iItem = selitem;
                        lvItem.mask = LVIF_PARAM;

                        if (SendDlgItemMessageW(hWnd, IDL_PROGRAMS, LVM_GETITEMW,
                          0, (LPARAM) &lvItem))
                            SupportInfo(hWnd, lvItem.lParam);
                    }

                    break;
            }

            return TRUE;
    }

    return FALSE;
}

static int CALLBACK propsheet_callback( HWND hwnd, UINT msg, LPARAM lparam )
{
    switch (msg)
    {
    case PSCB_INITIALIZED:
        SendMessageW( hwnd, WM_SETICON, ICON_BIG, (LPARAM)LoadIconW( hInst, MAKEINTRESOURCEW(ICO_MAIN) ));
        break;
    }
    return 0;
}

/******************************************************************************
 * Name       : StartApplet
 * Description: Main routine for applet
 * Parameters : hWnd    - hWnd of the Control Panel
 */
static void StartApplet(HWND hWnd)
{
    PROPSHEETPAGEW psp;
    PROPSHEETHEADERW psh;
    WCHAR tab_title[MAX_STRING_LEN], app_title[MAX_STRING_LEN];

    /* Load the strings we will use */
    LoadStringW(hInst, IDS_TAB1_TITLE, tab_title, sizeof(tab_title) / sizeof(tab_title[0]));
    LoadStringW(hInst, IDS_CPL_TITLE, app_title, sizeof(app_title) / sizeof(app_title[0]));
    LoadStringW(hInst, IDS_REMOVE, btnRemove, sizeof(btnRemove) / sizeof(btnRemove[0]));
    LoadStringW(hInst, IDS_MODIFY_REMOVE, btnModifyRemove, sizeof(btnModifyRemove) / sizeof(btnModifyRemove[0]));

    /* Fill out the PROPSHEETPAGE */
    psp.dwSize = sizeof (PROPSHEETPAGEW);
    psp.dwFlags = PSP_USETITLE;
    psp.hInstance = hInst;
    psp.u.pszTemplate = MAKEINTRESOURCEW (IDD_MAIN);
    psp.u2.pszIcon = NULL;
    psp.pfnDlgProc = MainDlgProc;
    psp.pszTitle = tab_title;
    psp.lParam = 0;

    /* Fill out the PROPSHEETHEADER */
    psh.dwSize = sizeof (PROPSHEETHEADERW);
    psh.dwFlags = PSH_PROPSHEETPAGE | PSH_USEICONID | PSH_USECALLBACK;
    psh.hwndParent = hWnd;
    psh.hInstance = hInst;
    psh.u.pszIcon = MAKEINTRESOURCEW(ICO_MAIN);
    psh.pszCaption = app_title;
    psh.nPages = 1;
    psh.u3.ppsp = &psp;
    psh.pfnCallback = propsheet_callback;
    psh.u2.nStartPage = 0;

    /* Display the property sheet */
    PropertySheetW (&psh);
}

static LONG start_params(const WCHAR *params)
{
    static const WCHAR install_geckoW[] = {'i','n','s','t','a','l','l','_','g','e','c','k','o',0};
    static const WCHAR install_monoW[] = {'i','n','s','t','a','l','l','_','m','o','n','o',0};

    if(!params)
        return FALSE;

    if(!strcmpW(params, install_geckoW)) {
        install_addon(ADDON_GECKO);
        return TRUE;
    }

    if(!strcmpW(params, install_monoW)) {
        install_addon(ADDON_MONO);
        return TRUE;
    }

    WARN("unknown param %s\n", debugstr_w(params));
    return FALSE;
}

/******************************************************************************
 * Name       : CPlApplet
 * Description: Entry point for Control Panel applets
 * Parameters : hwndCPL - hWnd of the Control Panel
 *              message - reason for calling function
 *              lParam1 - additional parameter
 *              lParam2 - additional parameter
 * Returns    : Depends on the message
 */
LONG CALLBACK CPlApplet(HWND hwndCPL, UINT message, LPARAM lParam1, LPARAM lParam2)
{
    INITCOMMONCONTROLSEX iccEx;

    switch (message)
    {
        case CPL_INIT:
            iccEx.dwSize = sizeof(iccEx);
            iccEx.dwICC = ICC_LISTVIEW_CLASSES | ICC_TAB_CLASSES | ICC_LINK_CLASS;

            InitCommonControlsEx(&iccEx);

            return TRUE;

        case CPL_GETCOUNT:
            return 1;

        case CPL_STARTWPARMSW:
            return start_params((const WCHAR *)lParam2);

        case CPL_INQUIRE:
        {
            CPLINFO *appletInfo = (CPLINFO *) lParam2;

            appletInfo->idIcon = ICO_MAIN;
            appletInfo->idName = IDS_CPL_TITLE;
            appletInfo->idInfo = IDS_CPL_DESC;
            appletInfo->lData = 0;

            break;
        }

        case CPL_DBLCLK:
            StartApplet(hwndCPL);
            break;
    }

    return FALSE;
}
