/*
 * addrm - Dialog box property sheet for "Add/Remove"
 */

#include "tweakui.h"

#pragma BEGIN_CONST_DATA

const static DWORD CODESEG rgdwHelp[] = {
        IDC_UNINSTALL,          IDH_UNINSTALL,
        IDC_UNINSTALLTEXT,      IDH_UNINSTALL,
        IDC_UNINSTALLNEW,       IDH_UNINSTALLNEW,
        IDC_LVDELETE,           IDH_UNINSTALLDELETE,
        IDC_UNINSTALLEDIT,      IDH_UNINSTALLEDIT,
        0,                      0,
};

#pragma END_CONST_DATA


typedef unsigned char ARIFL;            /* Random flags */
#define ariflDelPending 2       /* Delete this on apply */
#define ariflEdited 4           /* Rewrite this key */

#define ctchKeyMac  63          /* Maximum length supported by shell32 */

typedef struct ARI {            /* ari - add/remove info */
    ARIFL arifl;
    TCH tszKey[MAX_PATH];
    TCH tszCmd[MAX_PATH];
} ARI, *PARI;

#define iariPlvi(plvi) ((UINT)(plvi)->lParam)
#define pariIari(iari) (&padii->pari[iari])
#define pariPlvi(plvi) pariIari(iariPlvi(plvi))

typedef struct ADII {
    BOOL fDamaged;
    Declare_Gxa(ARI, ari);
} ADII;

ADII adii;
#define padii (&adii)

/*****************************************************************************
 *
 *  AddRm_AddKey
 *
 *  Add an add/remove entry.  We use a listview control instead of a listbox,
 *  because we need to be able to process right-clicks in order to display a
 *  context menu.
 *
 *  Returns the resulting item number.
 *
 *****************************************************************************/

int PASCAL
AddRm_AddKey(HWND hwnd, LPCTSTR ptszDesc)
{
    return LV_AddItem(hwnd, padii->cari++, ptszDesc, -1, -1);
}

/*****************************************************************************
 *
 *  AddRm_OnInitDialog
 *
 *  Create and fill the Add/Remove list box.
 *
 *****************************************************************************/

BOOL PASCAL
AddRm_OnInitDialog(HWND hwnd)
{
    padii->fDamaged = FALSE;

    if (Misc_InitPgxa(&padii->gxa, cbX(ARI))) {
        HKEY hk;
        if (_RegOpenKey(g_hkLMSMWCV, c_tszUninstall, &hk) == 0) {
            int ihk;
            PARI pari;
            for (ihk = 0;
                 (pari = (PARI)Misc_AllocPx(&padii->gxa)) &&
                 (RegEnumKey(hk, ihk, pari->tszKey, cbX(pari->tszKey)) == 0);
                 ihk++) {

                if (pari->tszKey[0]) {          /* Don't want default key */
                    TCH tszDesc[MAX_PATH];
                    if (GetRegStr(hk, pari->tszKey, c_tszDisplayName,
                                  tszDesc, cbX(tszDesc)) &&
                        GetRegStr(hk, pari->tszKey, c_tszUninstallString,
                                  pari->tszCmd, cbX(pari->tszCmd))) {
                        pari->arifl = 0;
                        AddRm_AddKey(hwnd, tszDesc);
                        if (lstrlen(pari->tszKey) > ctchKeyMac) {
                            padii->fDamaged = TRUE;
                        }
                    }
                }
            }
            RegCloseKey(hk);
        }
        return 1;
    } else {
        return 0;
    }
}

/*****************************************************************************
 *
 *  AddRm_Dirtify
 *
 *      Mark an entry as dirty.  Used when somebody does an in-place edit
 *      of an entry.
 *
 *****************************************************************************/

void PASCAL
AddRm_Dirtify(LPARAM iari)
{
    pariIari(iari)->arifl |= ariflEdited;
}

/*****************************************************************************
 *
 *      DIGRESSION
 *
 *  The Edit dialog box.
 *
 *****************************************************************************/

typedef struct AREI {           /* Add/Remove Edit Info */
    HWND hwnd;                  /* List view */
    int iItem;                  /* Item number being edited (-1 if add) */
} AREI, *PAREI;

/*****************************************************************************
 *
 *  AddRm_Edit_IsDlgItemPresent
 *
 *  Leading and trailing spaces are ignored.
 *
 *****************************************************************************/

BOOL PASCAL
AddRm_Edit_IsDlgItemPresent(HWND hdlg, int id)
{
    TCH tsz[MAX_PATH];
    GetDlgItemText(hdlg, id, tsz, cA(tsz));
    return Misc_Trim(tsz)[0];
}

/*****************************************************************************
 *
 *  AddRm_Edit_OnCommand_OnEditChange
 *
 *      Enable/disable the OK button based on whether the texts are present.
 *
 *****************************************************************************/

void PASCAL
AddRm_Edit_OnCommand_OnEditChange(HWND hdlg)
{
    EnableDlgItem(hdlg, IDOK,
                    AddRm_Edit_IsDlgItemPresent(hdlg, IDC_UNINSTALLDESC) &&
                    AddRm_Edit_IsDlgItemPresent(hdlg, IDC_UNINSTALLCMD));
}

/*****************************************************************************
 *
 *  AddRm_Edit_OnInitDialog
 *
 *      Fill in the fields with stuff.
 *
 *****************************************************************************/

void PASCAL
AddRm_Edit_OnInitDialog(HWND hdlg, PAREI parei)
{
    SetWindowLongPtr(hdlg, DWLP_USER, (LPARAM)parei);
    if (parei->iItem != -1) {
        LV_ITEM lvi;
        TCH tszDesc[MAX_PATH];
        lvi.pszText = tszDesc;
        lvi.cchTextMax = cA(tszDesc);
        Misc_LV_GetItemInfo(parei->hwnd, &lvi, parei->iItem,
                            LVIF_PARAM | LVIF_TEXT);

        SetDlgItemTextLimit(hdlg, IDC_UNINSTALLDESC, lvi.pszText, MAX_PATH);
        SetDlgItemTextLimit(hdlg, IDC_UNINSTALLCMD,
                                       pariPlvi(&lvi)->tszCmd,
                                       cA(pariPlvi(&lvi)->tszCmd));
    }
    AddRm_Edit_OnCommand_OnEditChange(hdlg);
}

/*****************************************************************************
 *
 *  AddRm_Edit_OnOk
 *
 *      Save the information back out.
 *
 *****************************************************************************/

void PASCAL
AddRm_Edit_OnOk(HWND hdlg)
{
    PAREI parei = (PAREI)GetWindowLongPtr(hdlg, DWLP_USER);
    LV_ITEM lvi;
    TCH tszDesc[MAX_PATH];
    /*
     *  Need to get the description early, so that the new entry sorts
     *  into the right place.
     */
    lvi.pszText = tszDesc;
    GetDlgItemText(hdlg, IDC_UNINSTALLDESC, lvi.pszText, cA(tszDesc));

    if (parei->iItem == -1) {
        PARI pari = (PARI)Misc_AllocPx(&padii->gxa);
        if (pari) {
            pari->arifl = 0;
            pari->tszKey[0] = TEXT('\0');
            parei->iItem = AddRm_AddKey(parei->hwnd, lvi.pszText);
            Misc_LV_SetCurSel(parei->hwnd, parei->iItem);
        } else {
            goto failed;
        }
    }
    lvi.iItem = parei->iItem;
    Misc_LV_GetItemInfo(parei->hwnd, &lvi, parei->iItem, LVIF_PARAM);

    pariPlvi(&lvi)->arifl |= ariflEdited;

    GetDlgItemText(hdlg, IDC_UNINSTALLCMD,
                   pariPlvi(&lvi)->tszCmd, cA(pariPlvi(&lvi)->tszCmd));

    lvi.mask ^= LVIF_TEXT ^ LVIF_PARAM;
    ListView_SetItem(parei->hwnd, &lvi);

    Common_SetDirty(GetParent(parei->hwnd));

    failed:;
}

/*****************************************************************************
 *
 *  AddRm_Edit_OnCommand
 *
 *****************************************************************************/

void PASCAL
AddRm_Edit_OnCommand(HWND hdlg, int id, UINT codeNotify)
{
    switch (id) {
    case IDCANCEL:
        EndDialog(hdlg, 0); break;

    case IDOK:
        AddRm_Edit_OnOk(hdlg);
        EndDialog(hdlg, 0);
        break;

    case IDC_UNINSTALLDESC:
    case IDC_UNINSTALLCMD:
        if (codeNotify == EN_CHANGE) AddRm_Edit_OnCommand_OnEditChange(hdlg);
        break;
    }
}

/*****************************************************************************
 *
 *  AddRm_Edit_DlgProc
 *
 *      Dialog procedure.
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA
const static DWORD CODESEG rgdwHelpEdit[] = {
        IDC_UNINSTALLDESCTEXT,  IDH_UNINSTALLEDITDESC,
        IDC_UNINSTALLCMDTEXT,   IDH_UNINSTALLEDITCOMMAND,
        0,                      0,
};
#pragma END_CONST_DATA

/*
 * The HANDLE_WM_* macros weren't designed to be used from a dialog
 * proc, so we need to handle the messages manually.  (But carefully.)
 */

INT_PTR EXPORT
AddRm_Edit_DlgProc(HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam)
{
    switch (wm) {
    case WM_INITDIALOG: AddRm_Edit_OnInitDialog(hdlg, (PAREI)lParam); break;

    case WM_COMMAND:
        AddRm_Edit_OnCommand(hdlg,
                                (int)GET_WM_COMMAND_ID(wParam, lParam),
                                (UINT)GET_WM_COMMAND_CMD(wParam, lParam));
        break;

    case WM_CONTEXTMENU: Common_OnContextMenu(wParam, &rgdwHelpEdit[0]); break;

    default: return 0;  /* Unhandled */
    }
    return 1;           /* Handled */
}


/*****************************************************************************
 *
 *      END DIGRESSION
 *
 *****************************************************************************/


/*****************************************************************************
 *
 *  AddRm_OnEdit
 *
 *****************************************************************************/

void PASCAL
AddRm_OnEdit(HWND hwnd, int iItem)
{
    AREI arei = { hwnd, iItem };
    DialogBoxParam(hinstCur, MAKEINTRESOURCE(IDD_UNINSTALLEDIT), hwnd,
                          AddRm_Edit_DlgProc, (LPARAM)&arei);
}

/*****************************************************************************
 *
 *  AddRm_OnNew
 *
 *****************************************************************************/

#define AddRm_OnNew(hdlg) AddRm_OnEdit(GetDlgItem(hdlg, IDC_UNINSTALL), -1)

/*****************************************************************************
 *
 *  AddRm_OnDelete
 *
 *      Mark it for deletion, but don't actually nuke it until later.
 *
 *****************************************************************************/

void PASCAL
AddRm_OnDelete(HWND hwnd, int iItem)
{
    LV_ITEM lvi;
    TCH tszDesc[MAX_PATH];
    lvi.pszText = tszDesc;
    lvi.cchTextMax = cA(tszDesc);
    Misc_LV_GetItemInfo(hwnd, &lvi, iItem, LVIF_PARAM | LVIF_TEXT);
    if (MessageBoxId(GetParent(hwnd), IDS_ADDRMWARN, lvi.pszText,
                     MB_YESNO | MB_DEFBUTTON2) == IDYES) {
        pariPlvi(&lvi)->arifl |= ariflDelPending;
        ListView_DeleteItem(hwnd, iItem);
        Misc_LV_EnsureSel(hwnd, iItem);
        Common_SetDirty(GetParent(hwnd));
    }
}

/*****************************************************************************
 *
 *  AddRm_CreateUniqueKeyName
 *
 *  Find a name that doesn't yet exist.
 *
 *  Search numerically until something works, or a weird error occurs.
 *
#if 0
 *  To avoid O(n^2) behavior, we start with the number of keys.
#endif
 *
 *****************************************************************************/

BOOL PASCAL
AddRm_CreateUniqueKeyName(HKEY hkUninst, LPTSTR ptsz)
{
    BOOL fRc;
    int i;
    for (i = 0; ; i++) {
        LONG cb;
        wsprintf(ptsz, c_tszPercentU, i);
        cb = 0;
        switch (RegQueryValue(hkUninst, ptsz, 0, &cb)) {
        case ERROR_SUCCESS: break;
        case ERROR_FILE_NOT_FOUND: fRc = 1; goto done;
        default: fRc = 0; goto done;    /* Unknown error */
        }
    }
done:;
    return fRc;
}

/*****************************************************************************
 *
 *  AddRm_OnApply_DoDeletes
 *
 *      Delete the keys that are marked as "delete pending".
 *
 *      pari->tszKey[0] is TEXT('\0') if the key was never in the registry.
 *      (E.g., you "New" it, and then delete it.)
 *
 *****************************************************************************/

void PASCAL
AddRm_OnApply_DoDeletes(HKEY hkUninst)
{
    int iari;
    for (iari = 0; iari < padii->cari; iari++) {
        if (pariIari(iari)->arifl & ariflDelPending) {
            if (pariIari(iari)->tszKey[0]) {
                RegDeleteTree(hkUninst, pariIari(iari)->tszKey);
            }
        }
    }
}

/*****************************************************************************
 *
 *  AddRm_OnApply_DoEdits
 *
 *      Apply all the edits.
 *
 *****************************************************************************/

void PASCAL
AddRm_OnApply_DoEdits(HWND hdlg, HKEY hkUninst)
{
    HWND hwnd = GetDlgItem(hdlg, IDC_UNINSTALL);
    int cItems = ListView_GetItemCount(hwnd);
    LV_ITEM lvi;

    for (lvi.iItem = 0; lvi.iItem < cItems; lvi.iItem++) {
        TCH tsz[MAX_PATH];
        PARI pari;
        lvi.pszText = tsz;
        lvi.cchTextMax = cA(tsz);
        Misc_LV_GetItemInfo(hwnd, &lvi, lvi.iItem, LVIF_PARAM | LVIF_TEXT);
        pari = pariPlvi(&lvi);

        if (pari->arifl & ariflEdited) {
            HKEY hk;
            if (pari->tszKey[0] == TEXT('\0')) {
                if (AddRm_CreateUniqueKeyName(hkUninst, pari->tszKey)) {
                } else {
                    break;                      /* Error! */
                }
            }
            if (RegCreateKey(hkUninst, pari->tszKey, &hk) == 0) {
                RegSetValuePtsz(hk, c_tszDisplayName, tsz);
                RegSetValuePtsz(hk, c_tszUninstallString, pari->tszCmd);
                RegCloseKey(hk);
            }
        }
    }
}

/*****************************************************************************
 *
 *  AddRm_OnApply
 *
 *****************************************************************************/

void PASCAL
AddRm_OnApply(HWND hdlg)
{
    HKEY hkUninst;
    if (RegCreateKey(g_hkLMSMWCV, c_tszUninstall, &hkUninst) == 0) {
        AddRm_OnApply_DoDeletes(hkUninst);
        AddRm_OnApply_DoEdits(hdlg, hkUninst);
        RegCloseKey(hkUninst);
    }
}

/*****************************************************************************
 *
 *  AddRm_OnDestroy
 *
 *      Free the memory we allocated.
 *
 *****************************************************************************/

void PASCAL
AddRm_OnDestroy(HWND hdlg)
{
    Misc_FreePgxa(&padii->gxa);
}

/*****************************************************************************
 *
 *  AddRm_OnRepair
 *
 *  Take all the keys that are too long and shorten them.
 *
 *****************************************************************************/

void PASCAL
AddRm_OnRepair(HWND hdlg)
{
    if (padii->fDamaged &&
        MessageBoxId(hdlg, IDS_ASKREPAIRADDRM, g_tszName, MB_YESNO)) {
        HKEY hkUninst;

        if (RegCreateKey(g_hkLMSMWCV, c_tszUninstall, &hkUninst) == 0) {

            int iari;
            for (iari = 0; iari < padii->cari; iari++) {
                TCHAR tszNewKey[MAX_PATH];
                PARI pari = pariIari(iari);
                if (lstrlen(pari->tszKey) > ctchKeyMac &&
                    AddRm_CreateUniqueKeyName(hkUninst, tszNewKey) &&
                    Misc_RenameReg(hkUninst, 0, pari->tszKey, tszNewKey)) {
                    lstrcpy(pari->tszKey, tszNewKey);
                }
            }
            RegCloseKey(hkUninst);
        }
    }
    padii->fDamaged = FALSE;            /* Ask only once */
}

/*****************************************************************************
 *
 *  AddRm_OnCommand
 *
 *****************************************************************************/

void PASCAL
AddRm_OnCommand(HWND hdlg, int id, UINT codeNotify)
{
    switch (id) {
    case IDC_UNINSTALLNEW:
        if (codeNotify == BN_CLICKED) {
            AddRm_OnNew(hdlg);
        }
        break;

    case IDC_UNINSTALLCHECK:
        AddRm_OnRepair(hdlg);
        break;

    }
}

/*****************************************************************************
 *
 *  Oh yeah, we need this too.
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA

LVCI lvciAddRm[] = {
    { IDC_UNINSTALLEDIT,    AddRm_OnEdit },
    { IDC_LVDELETE,         AddRm_OnDelete },
    { 0,                    0 },
};

LVV lvvAddRm = {
    AddRm_OnCommand,
    0,                          /* AddRm_OnInitContextMenu */
    AddRm_Dirtify,
    0,                          /* AddRm_GetIcon */
    AddRm_OnInitDialog,
    AddRm_OnApply,
    AddRm_OnDestroy,
    0,                          /* AddRm_OnSelChange */
    2,                          /* iMenu */
    rgdwHelp,
    IDC_UNINSTALLEDIT,          /* Double-click action */
    lvvflCanDelete | lvvflCanRename,
    lvciAddRm,
};

#pragma END_CONST_DATA

/*****************************************************************************
 *
 *  Our window procedure.
 *
 *****************************************************************************/

INT_PTR EXPORT
AddRm_DlgProc(HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam)
{
    switch (wm) {
    case WM_SHOWWINDOW:
        if (wParam) {
            FORWARD_WM_COMMAND(hdlg, IDC_UNINSTALLCHECK, 0, 0, PostMessage);
        }
        break;
    }

    return LV_DlgProc(&lvvAddRm, hdlg, wm, wParam, lParam);
}
