/*
 * Task dialog control
 *
 * Copyright 2017 Fabian Maurer
 * Copyright 2018 Zhiyi Zhang
 *
 * 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
 *
 */

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

#define NONAMELESSUNION

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "commctrl.h"
#include "winerror.h"
#include "comctl32.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(taskdialog);

static const UINT DIALOG_MIN_WIDTH = 240;
static const UINT DIALOG_SPACING = 5;
static const UINT DIALOG_BUTTON_WIDTH = 50;
static const UINT DIALOG_BUTTON_HEIGHT = 14;
static const UINT DIALOG_EXPANDO_ICON_WIDTH = 10;
static const UINT DIALOG_EXPANDO_ICON_HEIGHT = 10;
static const UINT DIALOG_TIMER_MS = 200;

static const UINT ID_TIMER = 1;

struct taskdialog_info
{
    HWND hwnd;
    const TASKDIALOGCONFIG *taskconfig;
    DWORD last_timer_tick;
    HFONT font;
    HFONT main_instruction_font;
    /* Control handles */
    HWND main_icon;
    HWND main_instruction;
    HWND content;
    HWND progress_bar;
    HWND *radio_buttons;
    INT radio_button_count;
    HWND *command_links;
    INT command_link_count;
    HWND expanded_info;
    HWND expando_button;
    HWND verification_box;
    HWND footer_icon;
    HWND footer_text;
    HWND *buttons;
    INT button_count;
    HWND default_button;
    /* Dialog metrics */
    struct
    {
        LONG x_baseunit;
        LONG y_baseunit;
        LONG h_spacing;
        LONG v_spacing;
    } m;
    INT selected_radio_id;
    BOOL verification_checked;
    BOOL expanded;
    BOOL has_cancel;
    WCHAR *expanded_text;
    WCHAR *collapsed_text;
};

struct button_layout_info
{
    LONG width;
    LONG line;
};

static HRESULT taskdialog_notify(struct taskdialog_info *dialog_info, UINT notification, WPARAM wparam, LPARAM lparam);
static void taskdialog_on_button_click(struct taskdialog_info *dialog_info, HWND hwnd, WORD id);
static void taskdialog_layout(struct taskdialog_info *dialog_info);

static void taskdialog_du_to_px(struct taskdialog_info *dialog_info, LONG *width, LONG *height)
{
    if (width) *width = MulDiv(*width, dialog_info->m.x_baseunit, 4);
    if (height) *height = MulDiv(*height, dialog_info->m.y_baseunit, 8);
}

static void template_write_data(char **ptr, const void *src, unsigned int size)
{
    memcpy(*ptr, src, size);
    *ptr += size;
}

static unsigned int taskdialog_get_reference_rect(const TASKDIALOGCONFIG *taskconfig, RECT *ret)
{
    HMONITOR monitor = MonitorFromWindow(taskconfig->hwndParent ? taskconfig->hwndParent : GetActiveWindow(),
                                         MONITOR_DEFAULTTOPRIMARY);
    MONITORINFO info;

    info.cbSize = sizeof(info);
    GetMonitorInfoW(monitor, &info);

    if ((taskconfig->dwFlags & TDF_POSITION_RELATIVE_TO_WINDOW) && taskconfig->hwndParent)
        GetWindowRect(taskconfig->hwndParent, ret);
    else
        *ret = info.rcWork;

    return info.rcWork.right - info.rcWork.left;
}

static WCHAR *taskdialog_get_exe_name(WCHAR *name, DWORD length)
{
    DWORD len = GetModuleFileNameW(NULL, name, length);
    if (len && len < length)
    {
        WCHAR *p;
        if ((p = wcsrchr(name, '/'))) name = p + 1;
        if ((p = wcsrchr(name, '\\'))) name = p + 1;
        return name;
    }
    else
        return NULL;
}

static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfig)
{
    unsigned int size, title_size;
    static const WORD fontsize = 0x7fff;
    static const WCHAR emptyW[] = { 0 };
    const WCHAR *titleW = NULL;
    DLGTEMPLATE *template;
    WCHAR pathW[MAX_PATH];
    char *ptr;

    /* Window title */
    if (!taskconfig->pszWindowTitle)
        titleW = taskdialog_get_exe_name(pathW, ARRAY_SIZE(pathW));
    else if (IS_INTRESOURCE(taskconfig->pszWindowTitle))
    {
        if (!LoadStringW(taskconfig->hInstance, LOWORD(taskconfig->pszWindowTitle), (WCHAR *)&titleW, 0))
            titleW = taskdialog_get_exe_name(pathW, ARRAY_SIZE(pathW));
    }
    else
        titleW = taskconfig->pszWindowTitle;
    if (!titleW)
        titleW = emptyW;
    title_size = (lstrlenW(titleW) + 1) * sizeof(WCHAR);

    size = sizeof(DLGTEMPLATE) + 2 * sizeof(WORD);
    size += title_size;
    size += 2; /* font size */

    template = Alloc(size);
    if (!template) return NULL;

    template->style = DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_VISIBLE | WS_SYSMENU;
    if (taskconfig->dwFlags & TDF_CAN_BE_MINIMIZED) template->style |= WS_MINIMIZEBOX;
    if (!(taskconfig->dwFlags & TDF_NO_SET_FOREGROUND)) template->style |= DS_SETFOREGROUND;
    if (taskconfig->dwFlags & TDF_RTL_LAYOUT) template->dwExtendedStyle = WS_EX_LAYOUTRTL | WS_EX_RIGHT | WS_EX_RTLREADING;

    ptr = (char *)(template + 1);
    ptr += 2; /* menu */
    ptr += 2; /* class */
    template_write_data(&ptr, titleW, title_size);
    template_write_data(&ptr, &fontsize, sizeof(fontsize));

    return template;
}

static HWND taskdialog_find_button(HWND *buttons, INT count, INT id)
{
    INT button_id;
    INT i;

    for (i = 0; i < count; i++)
    {
        button_id = GetWindowLongW(buttons[i], GWLP_ID);
        if (button_id == id) return buttons[i];
    }

    return NULL;
}

static void taskdialog_enable_button(const struct taskdialog_info *dialog_info, INT id, BOOL enable)
{
    HWND hwnd = taskdialog_find_button(dialog_info->command_links, dialog_info->command_link_count, id);
    if (!hwnd) hwnd = taskdialog_find_button(dialog_info->buttons, dialog_info->button_count, id);
    if (hwnd) EnableWindow(hwnd, enable);
}

static void taskdialog_click_button(struct taskdialog_info *dialog_info, INT id)
{
    if (taskdialog_notify(dialog_info, TDN_BUTTON_CLICKED, id, 0) == S_OK) EndDialog(dialog_info->hwnd, id);
}

static void taskdialog_button_set_shield(const struct taskdialog_info *dialog_info, INT id, BOOL elevate)
{
    HWND hwnd = taskdialog_find_button(dialog_info->command_links, dialog_info->command_link_count, id);
    if (!hwnd) hwnd = taskdialog_find_button(dialog_info->buttons, dialog_info->button_count, id);
    if (hwnd) SendMessageW(hwnd, BCM_SETSHIELD, 0, elevate);
}

static void taskdialog_enable_radio_button(const struct taskdialog_info *dialog_info, INT id, BOOL enable)
{
    HWND hwnd = taskdialog_find_button(dialog_info->radio_buttons, dialog_info->radio_button_count, id);
    if (hwnd) EnableWindow(hwnd, enable);
}

static void taskdialog_click_radio_button(const struct taskdialog_info *dialog_info, INT id)
{
    HWND hwnd = taskdialog_find_button(dialog_info->radio_buttons, dialog_info->radio_button_count, id);
    if (hwnd) SendMessageW(hwnd, BM_CLICK, 0, 0);
}

static HRESULT taskdialog_notify(struct taskdialog_info *dialog_info, UINT notification, WPARAM wparam, LPARAM lparam)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    return taskconfig->pfCallback
               ? taskconfig->pfCallback(dialog_info->hwnd, notification, wparam, lparam, taskconfig->lpCallbackData)
               : S_OK;
}

static void taskdialog_move_controls_vertically(HWND parent, HWND *controls, INT count, INT offset)
{
    RECT rect;
    POINT pt;
    INT i;

    for (i = 0; i < count; i++)
    {
        if (!controls[i]) continue;

        GetWindowRect(controls[i], &rect);
        pt.x = rect.left;
        pt.y = rect.top;
        MapWindowPoints(HWND_DESKTOP, parent, &pt, 1);
        SetWindowPos(controls[i], 0, pt.x, pt.y + offset, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
    }
}

static void taskdialog_toggle_expando_control(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    const WCHAR *text;
    RECT info_rect, rect;
    INT height, offset;

    dialog_info->expanded = !dialog_info->expanded;
    text = dialog_info->expanded ? dialog_info->expanded_text : dialog_info->collapsed_text;
    SendMessageW(dialog_info->expando_button, WM_SETTEXT, 0, (LPARAM)text);
    ShowWindow(dialog_info->expanded_info, dialog_info->expanded ? SW_SHOWDEFAULT : SW_HIDE);

    GetWindowRect(dialog_info->expanded_info, &info_rect);
    /* If expanded information starts up not expanded, call taskdialog_layout()
     * to to set size for expanded information control at least once */
    if (IsRectEmpty(&info_rect))
    {
        taskdialog_layout(dialog_info);
        return;
    }
    height = info_rect.bottom - info_rect.top + dialog_info->m.v_spacing;
    offset = dialog_info->expanded ? height : -height;

    /* Update vertical layout, move all controls after expanded information */
    /* Move dialog */
    GetWindowRect(dialog_info->hwnd, &rect);
    SetWindowPos(dialog_info->hwnd, 0, 0, 0, rect.right - rect.left, rect.bottom - rect.top + offset,
                 SWP_NOMOVE | SWP_NOZORDER);
    /* Move controls */
    if (!(taskconfig->dwFlags & TDF_EXPAND_FOOTER_AREA))
    {
        taskdialog_move_controls_vertically(dialog_info->hwnd, &dialog_info->progress_bar, 1, offset);
        taskdialog_move_controls_vertically(dialog_info->hwnd, &dialog_info->expando_button, 1, offset);
        taskdialog_move_controls_vertically(dialog_info->hwnd, &dialog_info->verification_box, 1, offset);
        taskdialog_move_controls_vertically(dialog_info->hwnd, &dialog_info->footer_icon, 1, offset);
        taskdialog_move_controls_vertically(dialog_info->hwnd, &dialog_info->footer_text, 1, offset);
        taskdialog_move_controls_vertically(dialog_info->hwnd, dialog_info->buttons, dialog_info->button_count, offset);
        taskdialog_move_controls_vertically(dialog_info->hwnd, dialog_info->radio_buttons,
                                            dialog_info->radio_button_count, offset);
        taskdialog_move_controls_vertically(dialog_info->hwnd, dialog_info->command_links,
                                            dialog_info->command_link_count, offset);
    }
}

static void taskdialog_on_button_click(struct taskdialog_info *dialog_info, HWND hwnd, WORD id)
{
    INT command_id;
    HWND button, radio_button;

    /* Prefer the id from hwnd because the id from WM_COMMAND is truncated to WORD */
    command_id = hwnd ? GetWindowLongW(hwnd, GWLP_ID) : id;

    if (hwnd && hwnd == dialog_info->expando_button)
    {
        taskdialog_toggle_expando_control(dialog_info);
        taskdialog_notify(dialog_info, TDN_EXPANDO_BUTTON_CLICKED, dialog_info->expanded, 0);
        return;
    }

    if (hwnd && hwnd == dialog_info->verification_box)
    {
        dialog_info->verification_checked = !dialog_info->verification_checked;
        taskdialog_notify(dialog_info, TDN_VERIFICATION_CLICKED, dialog_info->verification_checked, 0);
        return;
    }

    radio_button = taskdialog_find_button(dialog_info->radio_buttons, dialog_info->radio_button_count, command_id);
    if (radio_button)
    {
        dialog_info->selected_radio_id = command_id;
        taskdialog_notify(dialog_info, TDN_RADIO_BUTTON_CLICKED, command_id, 0);
        return;
    }

    button = taskdialog_find_button(dialog_info->command_links, dialog_info->command_link_count, command_id);
    if (!button) button = taskdialog_find_button(dialog_info->buttons, dialog_info->button_count, command_id);
    if (!button && command_id == IDOK)
    {
        button = dialog_info->command_link_count > 0 ? dialog_info->command_links[0] : dialog_info->buttons[0];
        command_id = GetWindowLongW(button, GWLP_ID);
    }

    if (button && taskdialog_notify(dialog_info, TDN_BUTTON_CLICKED, command_id, 0) == S_OK)
        EndDialog(dialog_info->hwnd, command_id);
}

static WCHAR *taskdialog_gettext(struct taskdialog_info *dialog_info, BOOL user_resource, const WCHAR *text)
{
    const WCHAR *textW = NULL;
    INT length;
    WCHAR *ret;

    if (IS_INTRESOURCE(text))
    {
        if (!(length = LoadStringW(user_resource ? dialog_info->taskconfig->hInstance : COMCTL32_hModule,
                                   (UINT_PTR)text, (WCHAR *)&textW, 0)))
            return NULL;
    }
    else
    {
        textW = text;
        length = lstrlenW(textW);
    }

    ret = Alloc((length + 1) * sizeof(WCHAR));
    if (ret) memcpy(ret, textW, length * sizeof(WCHAR));

    return ret;
}

static BOOL taskdialog_hyperlink_enabled(struct taskdialog_info *dialog_info)
{
    return dialog_info->taskconfig->dwFlags & TDF_ENABLE_HYPERLINKS;
}

static BOOL taskdialog_use_command_link(struct taskdialog_info *dialog_info)
{
    return dialog_info->taskconfig->dwFlags & (TDF_USE_COMMAND_LINKS | TDF_USE_COMMAND_LINKS_NO_ICON);
}

static void taskdialog_get_label_size(struct taskdialog_info *dialog_info, HWND hwnd, LONG max_width, SIZE *size,
                                      BOOL syslink)
{
    DWORD style = DT_EXPANDTABS | DT_CALCRECT | DT_WORDBREAK;
    HFONT hfont, old_hfont;
    HDC hdc;
    RECT rect = {0};
    WCHAR *text;
    INT text_length;

    if (syslink)
    {
        SendMessageW(hwnd, LM_GETIDEALSIZE, max_width, (LPARAM)size);
        return;
    }

    if (dialog_info->taskconfig->dwFlags & TDF_RTL_LAYOUT)
        style |= DT_RIGHT | DT_RTLREADING;
    else
        style |= DT_LEFT;

    hfont = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
    text_length = GetWindowTextLengthW(hwnd);
    text = Alloc((text_length + 1) * sizeof(WCHAR));
    if (!text)
    {
        size->cx = 0;
        size->cy = 0;
        return;
    }
    GetWindowTextW(hwnd, text, text_length + 1);
    hdc = GetDC(hwnd);
    old_hfont = SelectObject(hdc, hfont);
    rect.right = max_width;
    size->cy = DrawTextW(hdc, text, text_length, &rect, style);
    size->cx = min(max_width, rect.right - rect.left);
    if (old_hfont) SelectObject(hdc, old_hfont);
    ReleaseDC(hwnd, hdc);
    Free(text);
}

static void taskdialog_get_button_size(HWND hwnd, LONG max_width, SIZE *size)
{
    size->cx = max_width;
    size->cy = 0;
    SendMessageW(hwnd, BCM_GETIDEALSIZE, 0, (LPARAM)size);
}

static void taskdialog_get_expando_size(struct taskdialog_info *dialog_info, HWND hwnd, SIZE *size)
{
    DWORD style = DT_EXPANDTABS | DT_CALCRECT | DT_WORDBREAK;
    HFONT hfont, old_hfont;
    HDC hdc;
    RECT rect = {0};
    LONG icon_width, icon_height, text_offset;
    LONG max_width, max_text_height;

    hdc = GetDC(hwnd);
    hfont = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
    old_hfont = SelectObject(hdc, hfont);

    icon_width = DIALOG_EXPANDO_ICON_WIDTH;
    icon_height = DIALOG_EXPANDO_ICON_HEIGHT;
    taskdialog_du_to_px(dialog_info, &icon_width, &icon_height);

    GetCharWidthW(hdc, '0', '0', &text_offset);
    text_offset /= 2;

    if (dialog_info->taskconfig->dwFlags & TDF_RTL_LAYOUT)
        style |= DT_RIGHT | DT_RTLREADING;
    else
        style |= DT_LEFT;

    max_width = DIALOG_MIN_WIDTH / 2;
    taskdialog_du_to_px(dialog_info, &max_width, NULL);

    rect.right = max_width - icon_width - text_offset;
    max_text_height = DrawTextW(hdc, dialog_info->expanded_text, -1, &rect, style);
    size->cy = max(max_text_height, icon_height);
    size->cx = rect.right - rect.left;

    rect.right = max_width - icon_width - text_offset;
    max_text_height = DrawTextW(hdc, dialog_info->collapsed_text, -1, &rect, style);
    size->cy = max(size->cy, max_text_height);
    size->cx = max(size->cx, rect.right - rect.left);
    size->cx = min(size->cx, max_width);

    if (old_hfont) SelectObject(hdc, old_hfont);
    ReleaseDC(hwnd, hdc);
}

static ULONG_PTR taskdialog_get_standard_icon(LPCWSTR icon)
{
    if (icon == TD_WARNING_ICON)
        return IDI_WARNING;
    else if (icon == TD_ERROR_ICON)
        return IDI_ERROR;
    else if (icon == TD_INFORMATION_ICON)
        return IDI_INFORMATION;
    else if (icon == TD_SHIELD_ICON)
        return IDI_SHIELD;
    else
        return (ULONG_PTR)icon;
}

static void taskdialog_set_icon(struct taskdialog_info *dialog_info, INT element, HICON icon)
{
    DWORD flags = dialog_info->taskconfig->dwFlags;
    INT cx = 0, cy = 0;
    HICON hicon;

    if (!icon) return;

    if (((flags & TDF_USE_HICON_MAIN) && element == TDIE_ICON_MAIN)
        || ((flags & TDF_USE_HICON_FOOTER) && element == TDIE_ICON_FOOTER))
        hicon = icon;
    else
    {
        if (element == TDIE_ICON_FOOTER)
        {
            cx = GetSystemMetrics(SM_CXSMICON);
            cy = GetSystemMetrics(SM_CYSMICON);
        }
        hicon = LoadImageW(dialog_info->taskconfig->hInstance, (LPCWSTR)icon, IMAGE_ICON, cx, cy, LR_SHARED | LR_DEFAULTSIZE);
        if (!hicon)
            hicon = LoadImageW(NULL, (LPCWSTR)taskdialog_get_standard_icon((LPCWSTR)icon), IMAGE_ICON, cx, cy,
                               LR_SHARED | LR_DEFAULTSIZE);
    }

    if (!hicon) return;

    if (element == TDIE_ICON_MAIN)
    {
        SendMessageW(dialog_info->hwnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hicon);
        SendMessageW(dialog_info->main_icon, STM_SETICON, (WPARAM)hicon, 0);
    }
    else if (element == TDIE_ICON_FOOTER)
        SendMessageW(dialog_info->footer_icon, STM_SETICON, (WPARAM)hicon, 0);
}

static void taskdialog_set_element_text(struct taskdialog_info *dialog_info, TASKDIALOG_ELEMENTS element,
                                        const WCHAR *text)
{
    HWND hwnd = NULL;
    WCHAR *textW;

    if (element == TDE_CONTENT)
        hwnd = dialog_info->content;
    else if (element == TDE_EXPANDED_INFORMATION)
        hwnd = dialog_info->expanded_info;
    else if (element == TDE_FOOTER)
        hwnd = dialog_info->footer_text;
    else if (element == TDE_MAIN_INSTRUCTION)
        hwnd = dialog_info->main_instruction;

    if (!hwnd) return;

    textW = taskdialog_gettext(dialog_info, TRUE, text);
    SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)textW);
    Free(textW);
}

static void taskdialog_check_default_radio_buttons(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    HWND default_button;

    if (!dialog_info->radio_button_count) return;

    default_button = taskdialog_find_button(dialog_info->radio_buttons, dialog_info->radio_button_count,
                                            taskconfig->nDefaultRadioButton);

    if (!default_button && !(taskconfig->dwFlags & TDF_NO_DEFAULT_RADIO_BUTTON))
        default_button = dialog_info->radio_buttons[0];

    if (default_button)
    {
        SendMessageW(default_button, BM_SETCHECK, BST_CHECKED, 0);
        taskdialog_on_button_click(dialog_info, default_button, 0);
    }
}

static void taskdialog_add_main_icon(struct taskdialog_info *dialog_info)
{
    if (!dialog_info->taskconfig->u.hMainIcon) return;

    dialog_info->main_icon =
        CreateWindowW(WC_STATICW, NULL, WS_CHILD | WS_VISIBLE | SS_ICON, 0, 0, 0, 0, dialog_info->hwnd, NULL, 0, NULL);
    taskdialog_set_icon(dialog_info, TDIE_ICON_MAIN, dialog_info->taskconfig->u.hMainIcon);
}

static HWND taskdialog_create_label(struct taskdialog_info *dialog_info, const WCHAR *text, HFONT font, BOOL syslink)
{
    WCHAR *textW;
    HWND hwnd;
    const WCHAR *class;
    DWORD style = WS_CHILD | WS_VISIBLE;

    if (!text) return NULL;

    class = syslink ? WC_LINK : WC_STATICW;
    if (syslink) style |= WS_TABSTOP;
    textW = taskdialog_gettext(dialog_info, TRUE, text);
    hwnd = CreateWindowW(class, textW, style, 0, 0, 0, 0, dialog_info->hwnd, NULL, 0, NULL);
    Free(textW);

    SendMessageW(hwnd, WM_SETFONT, (WPARAM)font, 0);
    return hwnd;
}

static void taskdialog_add_main_instruction(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    NONCLIENTMETRICSW ncm;

    if (!taskconfig->pszMainInstruction) return;

    ncm.cbSize = sizeof(ncm);
    SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
    /* 1.25 times the height */
    ncm.lfMessageFont.lfHeight = ncm.lfMessageFont.lfHeight * 5 / 4;
    ncm.lfMessageFont.lfWeight = FW_BOLD;
    dialog_info->main_instruction_font = CreateFontIndirectW(&ncm.lfMessageFont);

    dialog_info->main_instruction =
        taskdialog_create_label(dialog_info, taskconfig->pszMainInstruction, dialog_info->main_instruction_font, FALSE);
}

static void taskdialog_add_content(struct taskdialog_info *dialog_info)
{
    dialog_info->content = taskdialog_create_label(dialog_info, dialog_info->taskconfig->pszContent, dialog_info->font,
                                                   taskdialog_hyperlink_enabled(dialog_info));
}

static void taskdialog_add_progress_bar(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    DWORD style = PBS_SMOOTH | PBS_SMOOTHREVERSE | WS_CHILD | WS_VISIBLE;

    if (!(taskconfig->dwFlags & (TDF_SHOW_PROGRESS_BAR | TDF_SHOW_MARQUEE_PROGRESS_BAR))) return;
    if (taskconfig->dwFlags & TDF_SHOW_MARQUEE_PROGRESS_BAR) style |= PBS_MARQUEE;
    dialog_info->progress_bar =
        CreateWindowW(PROGRESS_CLASSW, NULL, style, 0, 0, 0, 0, dialog_info->hwnd, NULL, 0, NULL);
}

static void taskdialog_add_radio_buttons(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    static const DWORD style = BS_AUTORADIOBUTTON | BS_MULTILINE | BS_TOP | WS_CHILD | WS_VISIBLE | WS_TABSTOP;
    WCHAR *textW;
    INT i;

    if (!taskconfig->cRadioButtons || !taskconfig->pRadioButtons) return;

    dialog_info->radio_buttons = Alloc(taskconfig->cRadioButtons * sizeof(*dialog_info->radio_buttons));
    if (!dialog_info->radio_buttons) return;

    dialog_info->radio_button_count = taskconfig->cRadioButtons;
    for (i = 0; i < dialog_info->radio_button_count; i++)
    {
        textW = taskdialog_gettext(dialog_info, TRUE, taskconfig->pRadioButtons[i].pszButtonText);
        dialog_info->radio_buttons[i] =
            CreateWindowW(WC_BUTTONW, textW, i == 0 ? style | WS_GROUP : style, 0, 0, 0, 0, dialog_info->hwnd,
                          LongToHandle(taskconfig->pRadioButtons[i].nButtonID), 0, NULL);
        SendMessageW(dialog_info->radio_buttons[i], WM_SETFONT, (WPARAM)dialog_info->font, 0);
        Free(textW);
    }
}

static void taskdialog_add_command_links(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    DWORD default_style = BS_MULTILINE | BS_LEFT | BS_TOP | WS_CHILD | WS_VISIBLE | WS_TABSTOP, style;
    BOOL is_default;
    WCHAR *textW;
    INT i;

    if (!taskconfig->cButtons || !taskconfig->pButtons || !taskdialog_use_command_link(dialog_info)) return;

    dialog_info->command_links = Alloc(taskconfig->cButtons * sizeof(*dialog_info->command_links));
    if (!dialog_info->command_links) return;

    dialog_info->command_link_count = taskconfig->cButtons;
    for (i = 0; i < dialog_info->command_link_count; i++)
    {
        is_default = taskconfig->pButtons[i].nButtonID == taskconfig->nDefaultButton;
        style = is_default ? default_style | BS_DEFCOMMANDLINK : default_style | BS_COMMANDLINK;
        textW = taskdialog_gettext(dialog_info, TRUE, taskconfig->pButtons[i].pszButtonText);
        dialog_info->command_links[i] = CreateWindowW(WC_BUTTONW, textW, style, 0, 0, 0, 0, dialog_info->hwnd,
                                                      LongToHandle(taskconfig->pButtons[i].nButtonID), 0, NULL);
        SendMessageW(dialog_info->command_links[i], WM_SETFONT, (WPARAM)dialog_info->font, 0);
        Free(textW);

        if (is_default && !dialog_info->default_button) dialog_info->default_button = dialog_info->command_links[i];
    }
}

static void taskdialog_add_expanded_info(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;

    if (!taskconfig->pszExpandedInformation) return;

    dialog_info->expanded = taskconfig->dwFlags & TDF_EXPANDED_BY_DEFAULT;
    dialog_info->expanded_info = taskdialog_create_label(dialog_info, taskconfig->pszExpandedInformation,
                                                         dialog_info->font, taskdialog_hyperlink_enabled(dialog_info));
    ShowWindow(dialog_info->expanded_info, dialog_info->expanded ? SW_SHOWDEFAULT : SW_HIDE);
}

static void taskdialog_add_expando_button(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    const WCHAR *textW;

    if (!taskconfig->pszExpandedInformation) return;

    if (!taskconfig->pszCollapsedControlText && !taskconfig->pszExpandedControlText)
    {
        dialog_info->expanded_text = taskdialog_gettext(dialog_info, FALSE, MAKEINTRESOURCEW(IDS_TD_EXPANDED));
        dialog_info->collapsed_text = taskdialog_gettext(dialog_info, FALSE, MAKEINTRESOURCEW(IDS_TD_COLLAPSED));
    }
    else
    {
        textW = taskconfig->pszExpandedControlText ? taskconfig->pszExpandedControlText
                                                   : taskconfig->pszCollapsedControlText;
        dialog_info->expanded_text = taskdialog_gettext(dialog_info, TRUE, textW);
        textW = taskconfig->pszCollapsedControlText ? taskconfig->pszCollapsedControlText
                                                    : taskconfig->pszExpandedControlText;
        dialog_info->collapsed_text = taskdialog_gettext(dialog_info, TRUE, textW);
    }

    textW = dialog_info->expanded ? dialog_info->expanded_text : dialog_info->collapsed_text;

    dialog_info->expando_button = CreateWindowW(WC_BUTTONW, textW, WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_OWNERDRAW, 0,
                                                0, 0, 0, dialog_info->hwnd, 0, 0, 0);
    SendMessageW(dialog_info->expando_button, WM_SETFONT, (WPARAM)dialog_info->font, 0);
}

static void taskdialog_add_verification_box(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    static const DWORD style = BS_AUTOCHECKBOX | BS_MULTILINE | BS_LEFT | BS_TOP | WS_CHILD | WS_VISIBLE | WS_TABSTOP;
    WCHAR *textW;

    /* TDF_VERIFICATION_FLAG_CHECKED works even if pszVerificationText is not set */
    if (taskconfig->dwFlags & TDF_VERIFICATION_FLAG_CHECKED) dialog_info->verification_checked = TRUE;

    if (!taskconfig->pszVerificationText) return;

    textW = taskdialog_gettext(dialog_info, TRUE, taskconfig->pszVerificationText);
    dialog_info->verification_box = CreateWindowW(WC_BUTTONW, textW, style, 0, 0, 0, 0, dialog_info->hwnd, 0, 0, 0);
    SendMessageW(dialog_info->verification_box, WM_SETFONT, (WPARAM)dialog_info->font, 0);
    Free(textW);

    if (taskconfig->dwFlags & TDF_VERIFICATION_FLAG_CHECKED)
        SendMessageW(dialog_info->verification_box, BM_SETCHECK, BST_CHECKED, 0);
}

static void taskdialog_add_button(struct taskdialog_info *dialog_info, HWND *button, INT_PTR id, const WCHAR *text,
                                  BOOL custom_button)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    WCHAR *textW;

    textW = taskdialog_gettext(dialog_info, custom_button, text);
    *button = CreateWindowW(WC_BUTTONW, textW, WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0, 0, 0, 0, dialog_info->hwnd,
                            (HMENU)id, 0, NULL);
    Free(textW);
    SendMessageW(*button, WM_SETFONT, (WPARAM)dialog_info->font, 0);

    if (id == taskconfig->nDefaultButton && !dialog_info->default_button) dialog_info->default_button = *button;
}

static void taskdialog_add_buttons(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    BOOL use_command_links = taskdialog_use_command_link(dialog_info);
    DWORD flags = taskconfig->dwCommonButtons;
    INT count, max_count;

    /* Allocate enough memory for the custom and the default buttons. Maximum 6 default buttons possible. */
    max_count = 6;
    if (!use_command_links && taskconfig->cButtons && taskconfig->pButtons) max_count += taskconfig->cButtons;

    dialog_info->buttons = Alloc(max_count * sizeof(*dialog_info->buttons));
    if (!dialog_info->buttons) return;

    for (count = 0; !use_command_links && count < taskconfig->cButtons; count++)
        taskdialog_add_button(dialog_info, &dialog_info->buttons[count], taskconfig->pButtons[count].nButtonID,
                              taskconfig->pButtons[count].pszButtonText, TRUE);

#define TASKDIALOG_INIT_COMMON_BUTTON(id)                                                                             \
    do                                                                                                                \
    {                                                                                                                 \
        taskdialog_add_button(dialog_info, &dialog_info->buttons[count++], ID##id, MAKEINTRESOURCEW(IDS_BUTTON_##id), \
                              FALSE);                                                                                 \
    } while (0)

    if (flags & TDCBF_OK_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(OK);
    if (flags & TDCBF_YES_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(YES);
    if (flags & TDCBF_NO_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(NO);
    if (flags & TDCBF_RETRY_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(RETRY);
    if (flags & TDCBF_CANCEL_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(CANCEL);
    if (flags & TDCBF_CLOSE_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(CLOSE);

    if (!count && !dialog_info->command_link_count) TASKDIALOG_INIT_COMMON_BUTTON(OK);
#undef TASKDIALOG_INIT_COMMON_BUTTON

    dialog_info->button_count = count;
}

static void taskdialog_add_footer_icon(struct taskdialog_info *dialog_info)
{
    if (!dialog_info->taskconfig->u2.hFooterIcon) return;

    dialog_info->footer_icon =
        CreateWindowW(WC_STATICW, NULL, WS_CHILD | WS_VISIBLE | SS_ICON, 0, 0, 0, 0, dialog_info->hwnd, NULL, 0, 0);
    taskdialog_set_icon(dialog_info, TDIE_ICON_FOOTER, dialog_info->taskconfig->u2.hFooterIcon);
}

static void taskdialog_add_footer_text(struct taskdialog_info *dialog_info)
{
    dialog_info->footer_text = taskdialog_create_label(dialog_info, dialog_info->taskconfig->pszFooter,
                                                       dialog_info->font, taskdialog_hyperlink_enabled(dialog_info));
}

static LONG taskdialog_get_dialog_width(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    BOOL syslink = taskdialog_hyperlink_enabled(dialog_info);
    LONG max_width, main_icon_width, screen_width;
    RECT rect;
    SIZE size;

    screen_width = taskdialog_get_reference_rect(taskconfig, &rect);
    if ((taskconfig->dwFlags & TDF_SIZE_TO_CONTENT) && !taskconfig->cxWidth)
    {
        max_width = DIALOG_MIN_WIDTH;
        taskdialog_du_to_px(dialog_info, &max_width, NULL);
        main_icon_width = dialog_info->m.h_spacing;
        if (dialog_info->main_icon) main_icon_width += GetSystemMetrics(SM_CXICON);
        if (dialog_info->content)
        {
            taskdialog_get_label_size(dialog_info, dialog_info->content, 0, &size, syslink);
            max_width = max(max_width, size.cx + main_icon_width + dialog_info->m.h_spacing * 2);
        }
    }
    else
    {
        max_width = max(taskconfig->cxWidth, DIALOG_MIN_WIDTH);
        taskdialog_du_to_px(dialog_info, &max_width, NULL);
    }
    max_width = min(max_width, screen_width);
    return max_width;
}

static void taskdialog_label_layout(struct taskdialog_info *dialog_info, HWND hwnd, INT start_x, LONG dialog_width,
                                    LONG *dialog_height, BOOL syslink)
{
    LONG x, y, max_width;
    SIZE size;

    if (!hwnd) return;

    x = start_x + dialog_info->m.h_spacing;
    y = *dialog_height + dialog_info->m.v_spacing;
    max_width = dialog_width - x - dialog_info->m.h_spacing;
    taskdialog_get_label_size(dialog_info, hwnd, max_width, &size, syslink);
    SetWindowPos(hwnd, 0, x, y, size.cx, size.cy, SWP_NOZORDER);
    *dialog_height = y + size.cy;
}

static void taskdialog_layout(struct taskdialog_info *dialog_info)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    BOOL syslink = taskdialog_hyperlink_enabled(dialog_info);
    static BOOL first_time = TRUE;
    RECT ref_rect;
    LONG dialog_width, dialog_height = 0;
    LONG h_spacing, v_spacing;
    LONG main_icon_right, main_icon_bottom;
    LONG expando_right, expando_bottom;
    struct button_layout_info *button_layout_infos;
    LONG button_min_width, button_height;
    LONG *line_widths, line_count, align;
    LONG footer_icon_right, footer_icon_bottom;
    LONG x, y;
    SIZE size;
    INT i;

    taskdialog_get_reference_rect(dialog_info->taskconfig, &ref_rect);
    dialog_width = taskdialog_get_dialog_width(dialog_info);

    h_spacing = dialog_info->m.h_spacing;
    v_spacing = dialog_info->m.v_spacing;

    /* Main icon */
    main_icon_right = 0;
    main_icon_bottom = 0;
    if (dialog_info->main_icon)
    {
        x = h_spacing;
        y = dialog_height + v_spacing;
        size.cx = GetSystemMetrics(SM_CXICON);
        size.cy = GetSystemMetrics(SM_CYICON);
        SetWindowPos(dialog_info->main_icon, 0, x, y, size.cx, size.cy, SWP_NOZORDER);
        main_icon_right = x + size.cx;
        main_icon_bottom = y + size.cy;
    }

    /* Main instruction */
    taskdialog_label_layout(dialog_info, dialog_info->main_instruction, main_icon_right, dialog_width, &dialog_height,
                            FALSE);

    /* Content */
    taskdialog_label_layout(dialog_info, dialog_info->content, main_icon_right, dialog_width, &dialog_height, syslink);

    /* Expanded information */
    if (!(taskconfig->dwFlags & TDF_EXPAND_FOOTER_AREA) && dialog_info->expanded)
        taskdialog_label_layout(dialog_info, dialog_info->expanded_info, main_icon_right, dialog_width, &dialog_height,
                                syslink);

    /* Progress bar */
    if (dialog_info->progress_bar)
    {
        x = main_icon_right + h_spacing;
        y = dialog_height + v_spacing;
        size.cx = dialog_width - x - h_spacing;
        size.cy = GetSystemMetrics(SM_CYVSCROLL);
        SetWindowPos(dialog_info->progress_bar, 0, x, y, size.cx, size.cy, SWP_NOZORDER);
        dialog_height = y + size.cy;
    }

    /* Radio buttons */
    for (i = 0; i < dialog_info->radio_button_count; i++)
    {
        x = main_icon_right + h_spacing;
        y = dialog_height + v_spacing;
        taskdialog_get_button_size(dialog_info->radio_buttons[i], dialog_width - x - h_spacing, &size);
        size.cx = dialog_width - x - h_spacing;
        SetWindowPos(dialog_info->radio_buttons[i], 0, x, y, size.cx, size.cy, SWP_NOZORDER);
        dialog_height = y + size.cy;
    }

    /* Command links */
    for (i = 0; i < dialog_info->command_link_count; i++)
    {
        x = main_icon_right + h_spacing;
        y = dialog_height;
        /* Only add spacing for the first command links. There is no vertical spacing between command links */
        if (!i)
            y += v_spacing;
        taskdialog_get_button_size(dialog_info->command_links[i], dialog_width - x - h_spacing, &size);
        size.cx = dialog_width - x - h_spacing;
        /* Add spacing */
        size.cy += 4;
        SetWindowPos(dialog_info->command_links[i], 0, x, y, size.cx, size.cy, SWP_NOZORDER);
        dialog_height = y + size.cy;
    }

    dialog_height = max(dialog_height, main_icon_bottom);

    expando_right = 0;
    expando_bottom = dialog_height;
    /* Expando control */
    if (dialog_info->expando_button)
    {
        x = h_spacing;
        y = dialog_height + v_spacing;
        taskdialog_get_expando_size(dialog_info, dialog_info->expando_button, &size);
        SetWindowPos(dialog_info->expando_button, 0, x, y, size.cx, size.cy, SWP_NOZORDER);
        expando_right = x + size.cx;
        expando_bottom = y + size.cy;
    }

    /* Verification box */
    if (dialog_info->verification_box)
    {
        x = h_spacing;
        y = expando_bottom + v_spacing;
        size.cx = DIALOG_MIN_WIDTH / 2;
        taskdialog_du_to_px(dialog_info, &size.cx, NULL);
        taskdialog_get_button_size(dialog_info->verification_box, size.cx, &size);
        SetWindowPos(dialog_info->verification_box, 0, x, y, size.cx, size.cy, SWP_NOZORDER);
        expando_right = max(expando_right, x + size.cx);
        expando_bottom = y + size.cy;
    }

    /* Common and custom buttons */
    button_layout_infos = Alloc(dialog_info->button_count * sizeof(*button_layout_infos));
    line_widths = Alloc(dialog_info->button_count * sizeof(*line_widths));

    button_min_width = DIALOG_BUTTON_WIDTH;
    button_height = DIALOG_BUTTON_HEIGHT;
    taskdialog_du_to_px(dialog_info, &button_min_width, &button_height);
    for (i = 0; i < dialog_info->button_count; i++)
    {
        taskdialog_get_button_size(dialog_info->buttons[i], dialog_width - expando_right - h_spacing * 2, &size);
        button_layout_infos[i].width = max(size.cx, button_min_width);
    }

    /* Separate buttons into lines */
    x = expando_right + h_spacing;
    for (i = 0, line_count = 0; i < dialog_info->button_count; i++)
    {
        button_layout_infos[i].line = line_count;
        x += button_layout_infos[i].width + h_spacing;
        line_widths[line_count] += button_layout_infos[i].width + h_spacing;

        if ((i + 1 < dialog_info->button_count) && (x + button_layout_infos[i + 1].width + h_spacing >= dialog_width))
        {
            x = expando_right + h_spacing;
            line_count++;
        }
    }
    line_count++;

    /* Try to balance lines so they are about the same size */
    for (i = 1; i < line_count - 1; i++)
    {
        int diff_now = abs(line_widths[i] - line_widths[i - 1]);
        unsigned int j, last_button = 0;
        int diff_changed;

        for (j = 0; j < dialog_info->button_count; j++)
            if (button_layout_infos[j].line == i - 1) last_button = j;

        /* Difference in length of both lines if we wrapped the last button from the last line into this one */
        diff_changed = abs(2 * button_layout_infos[last_button].width + line_widths[i] - line_widths[i - 1]);

        if (diff_changed < diff_now)
        {
            button_layout_infos[last_button].line = i;
            line_widths[i] += button_layout_infos[last_button].width;
            line_widths[i - 1] -= button_layout_infos[last_button].width;
        }
    }

    /* Calculate left alignment so all lines are as far right as possible. */
    align = dialog_width - h_spacing;
    for (i = 0; i < line_count; i++)
    {
        int new_alignment = dialog_width - line_widths[i];
        if (new_alignment < align) align = new_alignment;
    }

    /* Now that we got them all positioned, move all buttons */
    x = align;
    size.cy = button_height;
    for (i = 0; i < dialog_info->button_count; i++)
    {
        /* New line */
        if (i > 0 && button_layout_infos[i].line != button_layout_infos[i - 1].line)
        {
            x = align;
            dialog_height += size.cy + v_spacing;
        }

        y = dialog_height + v_spacing;
        size.cx = button_layout_infos[i].width;
        SetWindowPos(dialog_info->buttons[i], 0, x, y, size.cx, size.cy, SWP_NOZORDER);
        x += button_layout_infos[i].width + h_spacing;
    }

    /* Add height for last row button and spacing */
    dialog_height += size.cy + v_spacing;
    dialog_height = max(dialog_height, expando_bottom);

    Free(button_layout_infos);
    Free(line_widths);

    /* Footer icon */
    footer_icon_right = 0;
    footer_icon_bottom = dialog_height;
    if (dialog_info->footer_icon)
    {
        x = h_spacing;
        y = dialog_height + v_spacing;
        size.cx = GetSystemMetrics(SM_CXSMICON);
        size.cy = GetSystemMetrics(SM_CYSMICON);
        SetWindowPos(dialog_info->footer_icon, 0, x, y, size.cx, size.cy, SWP_NOZORDER);
        footer_icon_right = x + size.cx;
        footer_icon_bottom = y + size.cy;
    }

    /* Footer text */
    taskdialog_label_layout(dialog_info, dialog_info->footer_text, footer_icon_right, dialog_width, &dialog_height,
                            syslink);
    dialog_height = max(dialog_height, footer_icon_bottom);

    /* Expanded information */
    if ((taskconfig->dwFlags & TDF_EXPAND_FOOTER_AREA) && dialog_info->expanded)
        taskdialog_label_layout(dialog_info, dialog_info->expanded_info, 0, dialog_width, &dialog_height, syslink);

    /* Add height for spacing, title height and frame height */
    dialog_height += v_spacing;
    dialog_height += GetSystemMetrics(SM_CYCAPTION);
    dialog_height += GetSystemMetrics(SM_CXDLGFRAME);

    if (first_time)
    {
        x = (ref_rect.left + ref_rect.right - dialog_width) / 2;
        y = (ref_rect.top + ref_rect.bottom - dialog_height) / 2;
        SetWindowPos(dialog_info->hwnd, 0, x, y, dialog_width, dialog_height, SWP_NOZORDER);
        first_time = FALSE;
    }
    else
        SetWindowPos(dialog_info->hwnd, 0, 0, 0, dialog_width, dialog_height, SWP_NOMOVE | SWP_NOZORDER);
}

static void taskdialog_draw_expando_control(struct taskdialog_info *dialog_info, LPDRAWITEMSTRUCT dis)
{
    HWND hwnd;
    HDC hdc;
    RECT rect = {0};
    WCHAR *text;
    LONG icon_width, icon_height, text_offset;
    UINT style = DFCS_FLAT;
    BOOL draw_focus;

    hdc = dis->hDC;
    hwnd = dis->hwndItem;

    SendMessageW(hwnd, WM_ERASEBKGND, (WPARAM)hdc, 0);

    icon_width = DIALOG_EXPANDO_ICON_WIDTH;
    icon_height = DIALOG_EXPANDO_ICON_HEIGHT;
    taskdialog_du_to_px(dialog_info, &icon_width, &icon_height);
    rect.right = icon_width;
    rect.bottom = icon_height;
    style |= dialog_info->expanded ? DFCS_SCROLLUP : DFCS_SCROLLDOWN;
    DrawFrameControl(hdc, &rect, DFC_SCROLL, style);

    GetCharWidthW(hdc, '0', '0', &text_offset);
    text_offset /= 2;

    rect = dis->rcItem;
    rect.left += icon_width + text_offset;
    text = dialog_info->expanded ? dialog_info->expanded_text : dialog_info->collapsed_text;
    DrawTextW(hdc, text, -1, &rect, DT_WORDBREAK | DT_END_ELLIPSIS | DT_EXPANDTABS);

    draw_focus = (dis->itemState & ODS_FOCUS) && !(dis->itemState & ODS_NOFOCUSRECT);
    if(draw_focus) DrawFocusRect(hdc, &rect);
}

static void taskdialog_init(struct taskdialog_info *dialog_info, HWND hwnd)
{
    const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
    NONCLIENTMETRICSW ncm;
    HDC hdc;
    INT id;

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

    memset(dialog_info, 0, sizeof(*dialog_info));
    dialog_info->taskconfig = taskconfig;
    dialog_info->hwnd = hwnd;
    dialog_info->font = CreateFontIndirectW(&ncm.lfMessageFont);

    hdc = GetDC(dialog_info->hwnd);
    SelectObject(hdc, dialog_info->font);
    dialog_info->m.x_baseunit = GdiGetCharDimensions(hdc, NULL, &dialog_info->m.y_baseunit);
    ReleaseDC(dialog_info->hwnd, hdc);

    dialog_info->m.h_spacing = DIALOG_SPACING;
    dialog_info->m.v_spacing = DIALOG_SPACING;
    taskdialog_du_to_px(dialog_info, &dialog_info->m.h_spacing, &dialog_info->m.v_spacing);

    if (taskconfig->dwFlags & TDF_CALLBACK_TIMER)
    {
        SetTimer(hwnd, ID_TIMER, DIALOG_TIMER_MS, NULL);
        dialog_info->last_timer_tick = GetTickCount();
    }

    taskdialog_add_main_icon(dialog_info);
    taskdialog_add_main_instruction(dialog_info);
    taskdialog_add_content(dialog_info);
    taskdialog_add_expanded_info(dialog_info);
    taskdialog_add_progress_bar(dialog_info);
    taskdialog_add_radio_buttons(dialog_info);
    taskdialog_add_command_links(dialog_info);
    taskdialog_add_expando_button(dialog_info);
    taskdialog_add_verification_box(dialog_info);
    taskdialog_add_buttons(dialog_info);
    taskdialog_add_footer_icon(dialog_info);
    taskdialog_add_footer_text(dialog_info);

    /* Set default button */
    if (!dialog_info->default_button && dialog_info->command_links)
        dialog_info->default_button = dialog_info->command_links[0];
    if (!dialog_info->default_button) dialog_info->default_button = dialog_info->buttons[0];
    SendMessageW(dialog_info->hwnd, WM_NEXTDLGCTL, (WPARAM)dialog_info->default_button, TRUE);
    id = GetWindowLongW(dialog_info->default_button, GWLP_ID);
    SendMessageW(dialog_info->hwnd, DM_SETDEFID, id, 0);

    dialog_info->has_cancel =
        (taskconfig->dwFlags & TDF_ALLOW_DIALOG_CANCELLATION)
        || taskdialog_find_button(dialog_info->command_links, dialog_info->command_link_count, IDCANCEL)
        || taskdialog_find_button(dialog_info->buttons, dialog_info->button_count, IDCANCEL);

    if (!dialog_info->has_cancel) DeleteMenu(GetSystemMenu(hwnd, FALSE), SC_CLOSE, MF_BYCOMMAND);

    taskdialog_layout(dialog_info);
}

static BOOL CALLBACK takdialog_destroy_control(HWND hwnd, LPARAM lParam)
{
    DestroyWindow(hwnd);
    return TRUE;
}

static void taskdialog_destroy(struct taskdialog_info *dialog_info)
{
    EnumChildWindows(dialog_info->hwnd, takdialog_destroy_control, 0);

    if (dialog_info->taskconfig->dwFlags & TDF_CALLBACK_TIMER) KillTimer(dialog_info->hwnd, ID_TIMER);
    if (dialog_info->font) DeleteObject(dialog_info->font);
    if (dialog_info->main_instruction_font) DeleteObject(dialog_info->main_instruction_font);
    Free(dialog_info->buttons);
    Free(dialog_info->radio_buttons);
    Free(dialog_info->command_links);
    Free(dialog_info->expanded_text);
    Free(dialog_info->collapsed_text);
}

static INT_PTR CALLBACK taskdialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static const WCHAR taskdialog_info_propnameW[] = {'T','a','s','k','D','i','a','l','o','g','I','n','f','o',0};
    struct taskdialog_info *dialog_info;
    LRESULT result;

    TRACE("hwnd=%p msg=0x%04x wparam=%lx lparam=%lx\n", hwnd, msg, wParam, lParam);

    if (msg != WM_INITDIALOG)
        dialog_info = GetPropW(hwnd, taskdialog_info_propnameW);

    switch (msg)
    {
        case TDM_NAVIGATE_PAGE:
            dialog_info->taskconfig = (const TASKDIALOGCONFIG *)lParam;
            taskdialog_destroy(dialog_info);
            taskdialog_init(dialog_info, hwnd);
            taskdialog_notify(dialog_info, TDN_DIALOG_CONSTRUCTED, 0, 0);
            /* Default radio button click notification is sent before TDN_NAVIGATED */
            taskdialog_check_default_radio_buttons(dialog_info);
            taskdialog_notify(dialog_info, TDN_NAVIGATED, 0, 0);
            break;
        case TDM_CLICK_BUTTON:
            taskdialog_click_button(dialog_info, wParam);
            break;
        case TDM_ENABLE_BUTTON:
            taskdialog_enable_button(dialog_info, wParam, lParam);
            break;
        case TDM_SET_MARQUEE_PROGRESS_BAR:
        {
            BOOL marquee = wParam;
            LONG style;
            if(!dialog_info->progress_bar) break;
            style = GetWindowLongW(dialog_info->progress_bar, GWL_STYLE);
            style = marquee ? style | PBS_MARQUEE : style & (~PBS_MARQUEE);
            SetWindowLongW(dialog_info->progress_bar, GWL_STYLE, style);
            break;
        }
        case TDM_SET_PROGRESS_BAR_STATE:
            result = SendMessageW(dialog_info->progress_bar, PBM_SETSTATE, wParam, 0);
            SetWindowLongPtrW(hwnd, DWLP_MSGRESULT, result);
            break;
        case TDM_SET_PROGRESS_BAR_RANGE:
            result = SendMessageW(dialog_info->progress_bar, PBM_SETRANGE, 0, lParam);
            SetWindowLongPtrW(hwnd, DWLP_MSGRESULT, result);
            break;
        case TDM_SET_PROGRESS_BAR_POS:
            result = 0;
            if (dialog_info->progress_bar)
            {
                LONG style = GetWindowLongW(dialog_info->progress_bar, GWL_STYLE);
                if (!(style & PBS_MARQUEE)) result = SendMessageW(dialog_info->progress_bar, PBM_SETPOS, wParam, 0);
            }
            SetWindowLongPtrW(hwnd, DWLP_MSGRESULT, result);
            break;
        case TDM_SET_PROGRESS_BAR_MARQUEE:
            SendMessageW(dialog_info->progress_bar, PBM_SETMARQUEE, wParam, lParam);
            break;
        case TDM_SET_ELEMENT_TEXT:
            taskdialog_set_element_text(dialog_info, wParam, (const WCHAR *)lParam);
            taskdialog_layout(dialog_info);
            break;
        case TDM_UPDATE_ELEMENT_TEXT:
            taskdialog_set_element_text(dialog_info, wParam, (const WCHAR *)lParam);
            break;
        case TDM_CLICK_RADIO_BUTTON:
            taskdialog_click_radio_button(dialog_info, wParam);
            break;
        case TDM_ENABLE_RADIO_BUTTON:
            taskdialog_enable_radio_button(dialog_info, wParam, lParam);
            break;
        case TDM_CLICK_VERIFICATION:
        {
            BOOL checked = (BOOL)wParam;
            BOOL focused = (BOOL)lParam;
            dialog_info->verification_checked = checked;
            if (dialog_info->verification_box)
            {
                SendMessageW(dialog_info->verification_box, BM_SETCHECK, checked ? BST_CHECKED : BST_UNCHECKED, 0);
                taskdialog_notify(dialog_info, TDN_VERIFICATION_CLICKED, checked, 0);
                if (focused) SetFocus(dialog_info->verification_box);
            }
            break;
        }
        case TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE:
            taskdialog_button_set_shield(dialog_info, wParam, lParam);
            break;
        case TDM_UPDATE_ICON:
            taskdialog_set_icon(dialog_info, wParam, (HICON)lParam);
            break;
        case WM_INITDIALOG:
            dialog_info = (struct taskdialog_info *)lParam;

            taskdialog_init(dialog_info, hwnd);

            SetPropW(hwnd, taskdialog_info_propnameW, dialog_info);
            taskdialog_notify(dialog_info, TDN_DIALOG_CONSTRUCTED, 0, 0);
            taskdialog_notify(dialog_info, TDN_CREATED, 0, 0);
            /* Default radio button click notification sent after TDN_CREATED */
            taskdialog_check_default_radio_buttons(dialog_info);
            return FALSE;
        case WM_COMMAND:
            if (HIWORD(wParam) == BN_CLICKED)
            {
                taskdialog_on_button_click(dialog_info, (HWND)lParam, LOWORD(wParam));
                break;
            }
            return FALSE;
        case WM_HELP:
            taskdialog_notify(dialog_info, TDN_HELP, 0, 0);
            break;
        case WM_TIMER:
            if (ID_TIMER == wParam)
            {
                DWORD elapsed = GetTickCount() - dialog_info->last_timer_tick;
                if (taskdialog_notify(dialog_info, TDN_TIMER, elapsed, 0) == S_FALSE)
                    dialog_info->last_timer_tick = GetTickCount();
            }
            break;
        case WM_NOTIFY:
        {
            PNMLINK pnmLink = (PNMLINK)lParam;
            HWND hwndFrom = pnmLink->hdr.hwndFrom;
            if ((taskdialog_hyperlink_enabled(dialog_info))
                && (hwndFrom == dialog_info->content || hwndFrom == dialog_info->expanded_info
                    || hwndFrom == dialog_info->footer_text)
                && (pnmLink->hdr.code == NM_CLICK || pnmLink->hdr.code == NM_RETURN))
            {
                taskdialog_notify(dialog_info, TDN_HYPERLINK_CLICKED, 0, (LPARAM)pnmLink->item.szUrl);
                break;
            }
            return FALSE;
        }
        case WM_DRAWITEM:
        {
            LPDRAWITEMSTRUCT dis = (LPDRAWITEMSTRUCT)lParam;
            if (dis->hwndItem == dialog_info->expando_button)
            {
                taskdialog_draw_expando_control(dialog_info, dis);
                SetWindowLongPtrW(hwnd, DWLP_MSGRESULT, TRUE);
                break;
            }
            return FALSE;
        }
        case WM_DESTROY:
            taskdialog_notify(dialog_info, TDN_DESTROYED, 0, 0);
            RemovePropW(hwnd, taskdialog_info_propnameW);
            taskdialog_destroy(dialog_info);
            break;
        case WM_CLOSE:
            if (dialog_info->has_cancel)
            {
                if(taskdialog_notify(dialog_info, TDN_BUTTON_CLICKED, IDCANCEL, 0) == S_OK)
                    EndDialog(hwnd, IDCANCEL);
                SetWindowLongPtrW(hwnd, DWLP_MSGRESULT, 0);
                break;
            }
            return FALSE;
        default:
            return FALSE;
    }
    return TRUE;
}

/***********************************************************************
 * TaskDialogIndirect [COMCTL32.@]
 */
HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *taskconfig, int *button,
                                  int *radio_button, BOOL *verification_flag_checked)
{
    struct taskdialog_info dialog_info;
    DLGTEMPLATE *template;
    INT ret;

    TRACE("%p, %p, %p, %p\n", taskconfig, button, radio_button, verification_flag_checked);

    if (!taskconfig || taskconfig->cbSize != sizeof(TASKDIALOGCONFIG))
        return E_INVALIDARG;

    dialog_info.taskconfig = taskconfig;

    template = create_taskdialog_template(taskconfig);
    ret = (short)DialogBoxIndirectParamW(taskconfig->hInstance, template, taskconfig->hwndParent,
            taskdialog_proc, (LPARAM)&dialog_info);
    Free(template);

    if (button) *button = ret;
    if (radio_button) *radio_button = dialog_info.selected_radio_id;
    if (verification_flag_checked) *verification_flag_checked = dialog_info.verification_checked;

    return S_OK;
}

/***********************************************************************
 * TaskDialog [COMCTL32.@]
 */
HRESULT WINAPI TaskDialog(HWND owner, HINSTANCE hinst, const WCHAR *title, const WCHAR *main_instruction,
    const WCHAR *content, TASKDIALOG_COMMON_BUTTON_FLAGS common_buttons, const WCHAR *icon, int *button)
{
    TASKDIALOGCONFIG taskconfig;

    TRACE("%p, %p, %s, %s, %s, %#x, %s, %p\n", owner, hinst, debugstr_w(title), debugstr_w(main_instruction),
        debugstr_w(content), common_buttons, debugstr_w(icon), button);

    memset(&taskconfig, 0, sizeof(taskconfig));
    taskconfig.cbSize = sizeof(taskconfig);
    taskconfig.hwndParent = owner;
    taskconfig.hInstance = hinst;
    taskconfig.dwCommonButtons = common_buttons;
    taskconfig.pszWindowTitle = title;
    taskconfig.u.pszMainIcon = icon;
    taskconfig.pszMainInstruction = main_instruction;
    taskconfig.pszContent = content;
    return TaskDialogIndirect(&taskconfig, button, NULL, NULL);
}
