/////////////////////////////////////////////////////////////////////////////
// Name:        src/msw/msgdlg.cpp
// Purpose:     wxMessageDialog
// Author:      Julian Smart
// Created:     04/01/98
// Copyright:   (c) Julian Smart
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"


#if wxUSE_MSGDLG

#ifndef WX_PRECOMP
    #include "wx/msgdlg.h"
    #include "wx/app.h"
    #include "wx/intl.h"
    #include "wx/utils.h"
    #include "wx/msw/private.h"
#endif

#include "wx/ptr_scpd.h"
#include "wx/dynlib.h"
#include "wx/msw/private/button.h"
#include "wx/msw/private/darkmode.h"
#include "wx/msw/private/metrics.h"
#include "wx/msw/private/msgdlg.h"
#include "wx/msw/private/taskdlg.h"
#include "wx/modalhook.h"
#include "wx/fontutil.h"
#include "wx/textbuf.h"
#include "wx/display.h"
#include "wx/translation.h"

// Interestingly, this symbol currently seems to be absent from Platform SDK
// headers but it is documented at MSDN.
#ifndef TDF_SIZE_TO_CONTENT
    #define TDF_SIZE_TO_CONTENT 0x1000000
#endif

using namespace wxMSWMessageDialog;

wxIMPLEMENT_CLASS(wxMessageDialog, wxDialog);

// the order in this array is the one in which buttons appear in the
// message box
const wxMessageDialog::ButtonAccessors wxMessageDialog::ms_buttons[] =
{
    { IDYES,    &wxMessageDialog::GetYesLabel    },
    { IDNO,     &wxMessageDialog::GetNoLabel     },
    { IDOK,     &wxMessageDialog::GetOKLabel     },
    { IDCANCEL, &wxMessageDialog::GetCancelLabel },
};

namespace
{

// Different threads could potentially show message boxes at the same time, so
// remember the window showing it in a thread-specific variable.
thread_local wxMessageDialog* gs_currentDialog = nullptr;

/*
    All this code is used for adjusting the message box layout when we mess
    with its contents. It's rather complicated because we try hard to avoid
    assuming much about the standard layout details and so, instead of just
    laying out everything ourselves (which would have been so much simpler!)
    we try to only modify the existing controls positions by offsetting them
    from their default ones in the hope that this will continue to work with
    the future Windows versions.
 */

// convert the given RECT from screen to client coordinates in place
void ScreenRectToClient(HWND hwnd, RECT& rc)
{
    // map from desktop (i.e. screen) coordinates to ones of this window
    wxMapWindowPoints(HWND_DESKTOP, hwnd, &rc);
}

// set window position to the given rect
inline void SetWindowRect(HWND hwnd, const RECT& rc)
{
    ::MoveWindow(hwnd,
                 rc.left, rc.top,
                 rc.right - rc.left, rc.bottom - rc.top,
                 FALSE);
}

// set window position expressed in screen coordinates, whether the window is
// child or top level
void MoveWindowToScreenRect(HWND hwnd, RECT rc)
{
    ScreenRectToClient(::GetParent(hwnd), rc);

    SetWindowRect(hwnd, rc);
}

} // anonymous namespace

/* static */
WXLRESULT wxCALLBACK
wxMessageDialog::HookFunction(int code, WXWPARAM wParam, WXLPARAM lParam)
{
    // Get the thread-local instance of wxMessageDialog
    wxMessageDialog *  const wnd = gs_currentDialog;
    wxCHECK_MSG( wnd, false, "No valid wxMessageDialog?" );

    const HHOOK hhook = (HHOOK)wnd->m_hook;
    const LRESULT rc = ::CallNextHookEx(hhook, code, wParam, lParam);

    if ( code == HCBT_ACTIVATE )
    {
        // we won't need this hook any longer
        ::UnhookWindowsHookEx(hhook);
        wnd->m_hook = nullptr;
        gs_currentDialog = nullptr;

        TempHWNDSetter set(wnd, (WXHWND)wParam);

        // replace the static text with an edit control if the message box is
        // too big to fit the display
        wnd->ReplaceStaticWithEdit();

#ifdef wxHAS_ANY_BUTTON
        // update the labels if necessary: we need to do it before centering
        // the dialog as this can change its size
        if ( wnd->HasCustomLabels() )
            wnd->AdjustButtonLabels();
#endif // wxHAS_ANY_BUTTON

        // centre the message box on its parent if requested
        if ( wnd->GetMessageDialogStyle() & wxCENTER )
            wnd->Center(); // center on parent
        //else: default behaviour, center on screen
    }

    return rc;
}

void wxMessageDialog::ReplaceStaticWithEdit()
{
    // check if the message box fits the display
    const wxRect rectDisplay = wxDisplay(this).GetClientArea();

    if ( rectDisplay.Contains(GetRect()) )
    {
        // nothing to do
        return;
    }


    // find the static control to replace: normally there are two of them, the
    // icon and the text itself so search for all of them and ignore the icon
    // ones
    HWND hwndStatic = ::FindWindowEx(GetHwnd(), nullptr, wxT("STATIC"), nullptr);
    if ( ::GetWindowLong(hwndStatic, GWL_STYLE) & SS_ICON )
        hwndStatic = ::FindWindowEx(GetHwnd(), hwndStatic, wxT("STATIC"), nullptr);

    if ( !hwndStatic )
    {
        wxLogDebug("Failed to find the static text control in message box.");
        return;
    }

    // set the right font for GetCharHeight() call below
    wxWindowBase::SetFont(GetMessageFont());

    // put the new edit control at the same place
    RECT rc = wxGetWindowRect(hwndStatic);
    ScreenRectToClient(GetHwnd(), rc);

    // but make it less tall so that the message box fits on the screen: we try
    // to make the message box take no more than 7/8 of the screen to leave
    // some space above and below it
    const int hText = (7*rectDisplay.height)/8 -
                      (
                         2*wxGetSystemMetrics(SM_CYFIXEDFRAME, this) +
                         wxGetSystemMetrics(SM_CYCAPTION, this) +
                         5*GetCharHeight() // buttons + margins
                      );
    const int dh = (rc.bottom - rc.top) - hText; // vertical space we save
    rc.bottom -= dh;

    // and it also must be wider as it needs a vertical scrollbar (in order
    // to preserve the word wrap, otherwise the number of lines would change
    // and we want the control to look as similar as possible to the original)
    //
    // NB: you would have thought that 2*SM_CXEDGE would be enough but it
    //     isn't, somehow, and the text control breaks lines differently from
    //     the static one so fudge by adding some extra space
    const int dw = wxGetSystemMetrics(SM_CXVSCROLL, this) +
                        4*wxGetSystemMetrics(SM_CXEDGE, this);
    rc.right += dw;


    // chop of the trailing new line(s) from the message box text, they are
    // ignored by the static control but result in extra lines and hence extra
    // scrollbar position in the edit one
    wxString text(wxGetWindowText(hwndStatic));
    for ( wxString::reverse_iterator i = text.rbegin(); i != text.rend(); ++i )
    {
        if ( *i != '\n' )
        {
            // found last non-newline char, remove anything after it if
            // necessary and stop in any case
            if ( i != text.rbegin() )
                text.erase(i.base() + 1, text.end());
            break;
        }
    }

    // do create the new control
    HWND hwndEdit = ::CreateWindow
                      (
                        wxT("EDIT"),
                        wxTextBuffer::Translate(text).t_str(),
                        WS_CHILD | WS_VSCROLL | WS_VISIBLE |
                        ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL,
                        rc.left, rc.top,
                        rc.right - rc.left, rc.bottom - rc.top,
                        GetHwnd(),
                        nullptr,
                        wxGetInstance(),
                        nullptr
                      );

    if ( !hwndEdit )
    {
        wxLogDebug("Creation of replacement edit control failed in message box");
        return;
    }

    // copy the font from the original control
    LRESULT hfont = ::SendMessage(hwndStatic, WM_GETFONT, 0, 0);
    ::SendMessage(hwndEdit, WM_SETFONT, hfont, 0);

    // and get rid of it
    ::DestroyWindow(hwndStatic);


    // shrink and centre the message box vertically and widen it box to account
    // for the extra scrollbar
    RECT rcBox = wxGetWindowRect(GetHwnd());
    const int hMsgBox = rcBox.bottom - rcBox.top - dh;
    rcBox.top = (rectDisplay.height - hMsgBox)/2;
    rcBox.bottom = rcBox.top + hMsgBox + (rectDisplay.height - hMsgBox)%2;
    rcBox.left -= dw/2;
    rcBox.right += dw - dw/2;
    SetWindowRect(GetHwnd(), rcBox);

    // and adjust all the buttons positions
    for ( unsigned n = 0; n < WXSIZEOF(ms_buttons); n++ )
    {
        const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
        if ( !hwndBtn )
            continue;   // it's ok, not all buttons are always present

        rc = wxGetWindowRect(hwndBtn);
        rc.top -= dh;
        rc.bottom -= dh;
        rc.left += dw/2;
        rc.right += dw/2;
        MoveWindowToScreenRect(hwndBtn, rc);
    }
}

#ifdef wxHAS_ANY_BUTTON
void wxMessageDialog::AdjustButtonLabels()
{
    // changing the button labels is the easy part but we also need to ensure
    // that the buttons are big enough for the label strings and increase their
    // size (and maybe the size of the message box itself) if they are not

    // TODO-RTL: check whether this works correctly in RTL

    // we want to use this font in GetTextExtent() calls below but we don't
    // want to send WM_SETFONT to the message box, who knows how is it going to
    // react to it (right now it doesn't seem to do anything but what if this
    // changes)
    wxWindowBase::SetFont(GetMessageFont());

    // first iteration: find the widest button and update the buttons labels
    int wBtnOld = 0,            // current buttons width
        wBtnNew = 0;            // required new buttons width
    RECT rcBtn;                 // stores the button height and y positions
    unsigned numButtons = 0;    // total number of buttons in the message box
    unsigned n;
    for ( n = 0; n < WXSIZEOF(ms_buttons); n++ )
    {
        const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
        if ( !hwndBtn )
            continue;   // it's ok, not all buttons are always present

        numButtons++;

        const wxString label = (this->*ms_buttons[n].getter)();
        const wxSize sizeLabel = wxWindowBase::GetTextExtent(label);

        // check if the button is big enough for this label
        const RECT rc = wxGetWindowRect(hwndBtn);
        if ( !wBtnOld )
        {
            // initialize wBtnOld using the first button width, all the other
            // ones should have the same one
            wBtnOld = rc.right - rc.left;

            rcBtn = rc; // remember for use below when we reposition the buttons
        }
        else
        {
            wxASSERT_MSG( wBtnOld == rc.right - rc.left,
                          "all buttons are supposed to be of same width" );
        }

        const int widthNeeded = wxMSWButton::GetFittingSize(this, sizeLabel).x;
        if ( widthNeeded > wBtnNew )
            wBtnNew = widthNeeded;

        ::SetWindowText(hwndBtn, label.t_str());
    }

    if ( wBtnNew <= wBtnOld )
    {
        // all buttons fit, nothing else to do
        return;
    }

    // resize the message box to be wider if needed
    const int wBoxOld = wxGetClientRect(GetHwnd()).right;

    const int CHAR_WIDTH_IN_PIXELS = GetCharWidth();
    const int MARGIN_OUTER = 2*CHAR_WIDTH_IN_PIXELS;  // margin between box and buttons
    const int MARGIN_INNER = CHAR_WIDTH_IN_PIXELS;    // margin between buttons

    RECT rcBox = wxGetWindowRect(GetHwnd());

    const int wAllButtons = numButtons*(wBtnNew + MARGIN_INNER) - MARGIN_INNER;
    int wBoxNew = 2*MARGIN_OUTER + wAllButtons;
    if ( wBoxNew > wBoxOld )
    {
        const int dw = wBoxNew - wBoxOld;
        rcBox.left -= dw/2;
        rcBox.right += dw - dw/2;

        SetWindowRect(GetHwnd(), rcBox);

        // surprisingly, we don't need to resize the static text control, it
        // seems to adjust itself to the new size, at least under Windows 2003
        // (TODO: test if this happens on older Windows versions)
    }
    else // the current width is big enough
    {
        wBoxNew = wBoxOld;
    }


    // finally position all buttons

    // notice that we have to take into account the difference between window
    // and client width
    rcBtn.left = (rcBox.left + rcBox.right - wxGetClientRect(GetHwnd()).right +
                  wBoxNew - wAllButtons) / 2;
    rcBtn.right = rcBtn.left + wBtnNew;

    for ( n = 0; n < WXSIZEOF(ms_buttons); n++ )
    {
        const HWND hwndBtn = ::GetDlgItem(GetHwnd(), ms_buttons[n].id);
        if ( !hwndBtn )
            continue;

        MoveWindowToScreenRect(hwndBtn, rcBtn);

        rcBtn.left += wBtnNew + MARGIN_INNER;
        rcBtn.right += wBtnNew + MARGIN_INNER;
    }
}
#endif // wxHAS_ANY_BUTTON

/* static */
wxFont wxMessageDialog::GetMessageFont()
{
    const wxWindow* win = wxApp::GetMainTopWindow();
    const wxNativeFontInfo
        info(wxMSWImpl::GetNonClientMetrics(win).lfMessageFont, win);

    return info;
}

int wxMessageDialog::ShowMessageBox()
{
    if ( wxTheApp && !wxTheApp->GetTopWindow() )
    {
        // when the message box is shown from wxApp::OnInit() (i.e. before the
        // message loop is entered), this must be done or the next message box
        // will never be shown - just try putting 2 calls to wxMessageBox() in
        // OnInit() to see it
        while ( wxTheApp->Pending() )
            wxTheApp->Dispatch();
    }

    // use the top level window as parent if none specified
    m_parent = GetParentForModalDialog();
    HWND hWnd = m_parent ? GetHwndOf(m_parent) : nullptr;

#if wxUSE_INTL
    // native message box always uses the current user locale but the program
    // may be using a different one and in this case we need to manually
    // translate the default button labels (if they're non default we have no
    // way to translate them and so we must assume they were already
    // translated) to avoid mismatch between the language of the message box
    // text and its buttons
    wxTranslations* currentTranslations = wxTranslations::Get();
    if ( currentTranslations )
    {
        if ( m_dialogStyle & wxYES_NO &&
                (GetCustomYesLabel().empty() && GetCustomNoLabel().empty()) )

        {
            // use the strings with mnemonics here as the native message box
            // does
            SetYesNoLabels(_("&Yes"), _("&No"));
        }

        // we may or not have the Ok/Cancel buttons but either we do have them
        // or we already made the labels custom because we called
        // SetYesNoLabels() above so doing this does no harm -- and is
        // necessary in wxYES_NO | wxCANCEL case
        //
        // note that we don't use mnemonics here for consistency with the
        // native message box (which probably doesn't use them because
        // Enter/Esc keys can be already used to dismiss the message box
        // using keyboard)
        if ( GetCustomOKLabel().empty() && GetCustomCancelLabel().empty() )
            SetOKCancelLabels(_("OK"), _("Cancel"));
    }
#endif // wxUSE_INTL

    // translate wx style in MSW
    unsigned int msStyle;
    const long wxStyle = GetMessageDialogStyle();
    if ( wxStyle & wxYES_NO )
    {
        if ( wxStyle & wxCANCEL )
            msStyle = MB_YESNOCANCEL;
        else
            msStyle = MB_YESNO;

        if ( wxStyle & wxNO_DEFAULT )
            msStyle |= MB_DEFBUTTON2;
        else if ( wxStyle & wxCANCEL_DEFAULT )
            msStyle |= MB_DEFBUTTON3;
    }
    else // without Yes/No we're going to have an OK button
    {
        if ( wxStyle & wxCANCEL )
        {
            msStyle = MB_OKCANCEL;

            if ( wxStyle & wxCANCEL_DEFAULT )
                msStyle |= MB_DEFBUTTON2;
        }
        else // just "OK"
        {
            msStyle = MB_OK;
        }
    }

    if ( wxStyle & wxHELP )
    {
        msStyle |= MB_HELP;
    }

    // set the icon style
    switch ( GetEffectiveIcon() )
    {
        case wxICON_ERROR:
            msStyle |= MB_ICONHAND;
            break;

        case wxICON_WARNING:
            msStyle |= MB_ICONEXCLAMATION;
            break;

        case wxICON_QUESTION:
            msStyle |= MB_ICONQUESTION;
            break;

        case wxICON_INFORMATION:
            msStyle |= MB_ICONINFORMATION;
            break;
    }

    if ( wxStyle & wxSTAY_ON_TOP )
        msStyle |= MB_TOPMOST;

    if ( wxApp::MSWGetDefaultLayout(m_parent) == wxLayout_RightToLeft )
        msStyle |= MB_RTLREADING | MB_RIGHT;

    if (hWnd)
        msStyle |= MB_APPLMODAL;
    else
        msStyle |= MB_TASKMODAL;

    // install the hook in any case as we don't know in advance if the message
    // box is not going to be too big (requiring the replacement of the static
    // control with an edit one)
    const DWORD tid = ::GetCurrentThreadId();
    m_hook = ::SetWindowsHookEx(WH_CBT,
                                &wxMessageDialog::HookFunction, nullptr, tid);
    gs_currentDialog = this;

    // do show the dialog
    const int msAns = MessageBox
                      (
                        hWnd,
                        GetFullMessage().t_str(),
                        m_caption.t_str(),
                        msStyle
                      );

    return MSWTranslateReturnCode(msAns);
}

int wxMessageDialog::ShowModal()
{
    WX_HOOK_MODAL_DIALOG();

    wxWindowDisabler disableOthers(this, GetParentForModalDialog());

    if ( TaskDialogIndirect_t taskDialogIndirect = GetTaskDialogIndirectFunc() )
    {
        WinStruct<TASKDIALOGCONFIG> tdc;
        wxMSWTaskDialogConfig wxTdc( *this );
        wxTdc.MSWCommonTaskDialogInit( tdc );

        int msAns;
        HRESULT hr = taskDialogIndirect( &tdc, &msAns, nullptr, nullptr );
        if ( FAILED(hr) )
        {
            wxLogApiError( "TaskDialogIndirect", hr );
            return wxID_CANCEL;
        }

        // In case only an "OK" button was specified we actually created a
        // "Cancel" button (see comment in MSWCommonTaskDialogInit). This
        // results in msAns being IDCANCEL while we want IDOK (just like
        // how the native MessageBox function does with only an "OK" button).
        if ( (msAns == IDCANCEL)
            && !(GetMessageDialogStyle() & (wxYES_NO|wxCANCEL)) )
        {
            msAns = IDOK;
        }

        return MSWTranslateReturnCode( msAns );
    }

    return ShowMessageBox();
}

long wxMessageDialog::GetEffectiveIcon() const
{
    // only use the auth needed icon if available, otherwise fallback to the default logic
    if ( (m_dialogStyle & wxICON_AUTH_NEEDED) &&
        wxMSWMessageDialog::HasNativeTaskDialog() )
    {
        return wxICON_AUTH_NEEDED;
    }

    return wxMessageDialogBase::GetEffectiveIcon();
}

void wxMessageDialog::DoCentre(int dir)
{
    // Task dialog is always centered on its parent window and trying to center
    // it manually doesn't work because its HWND is not created yet so don't
    // even try as this would only result in (debug) error messages.
    if ( HasNativeTaskDialog() )
        return;

    wxMessageDialogBase::DoCentre(dir);
}

// ----------------------------------------------------------------------------
// Helpers of the wxMSWMessageDialog namespace
// ----------------------------------------------------------------------------

namespace
{

HRESULT CALLBACK
wxTaskDialogCallback(HWND hwnd,
                     UINT msg,
                     WPARAM wParam,
                     LPARAM WXUNUSED(lParam),
                     LONG_PTR refData)
{
    const auto* const
        config = reinterpret_cast<const TASKDIALOGCONFIG*>(refData);

    switch ( msg )
    {
        case TDN_CREATED:
            // The dialog is fully constructed now, so we can enable dark mode
            // for it if necessary (doing it on TDN_DIALOG_CONSTRUCTED wouldn't
            // work because all dialog elements wouldn't be constructed yet).
            wxMSWDarkMode::AllowForTaskDialog(hwnd, config);
            break;

        case TDN_EXPANDO_BUTTON_CLICKED:
            // Store the expanded state as a window property so the subclassed
            // TaskPage panel can read it during WM_PAINT without a UIA call.
            SetProp(hwnd, L"IsExpanded",
                reinterpret_cast<HANDLE>(static_cast<ULONG_PTR>(wParam)));
            break;

        case TDN_DESTROYED:
            // Clean up all resources allocated by AllowForTaskDialog().
            wxMSWDarkMode::RemoveFromTaskDialog(hwnd);
            break;
    }

    return S_OK;
}

} // anonymous namespace

wxMSWTaskDialogConfig::wxMSWTaskDialogConfig(const wxMessageDialogBase& dlg)
                     : buttons(new TASKDIALOG_BUTTON[MAX_BUTTONS])
{
    parent = dlg.GetParentForModalDialog();
    caption = dlg.GetCaption();
    message = dlg.GetMessage();
    extendedMessage = dlg.GetExtendedMessage();

    // Before wxMessageDialog added support for extended message it was common
    // practice to have long multiline texts in the message box with the first
    // line playing the role of the main message and the rest of the extended
    // one. Try to detect such usage automatically here by synthesizing the
    // extended message on our own if it wasn't given.
    if ( extendedMessage.empty() )
    {
        // Check if there is a blank separating line after the first line (this
        // is not the same as searching for "\n\n" as we want the automatically
        // recognized main message be single line to avoid embarrassing false
        // positives).
        const size_t posNL = message.find('\n');
        if ( posNL != wxString::npos &&
                posNL < message.length() - 1 &&
                    message[posNL + 1 ] == '\n' )
        {
            extendedMessage.assign(message, posNL + 2, wxString::npos);
            message.erase(posNL);
        }
    }

    iconId = dlg.GetEffectiveIcon();
    style = dlg.GetMessageDialogStyle();
    useCustomLabels = dlg.HasCustomLabels();
    btnYesLabel = dlg.GetYesLabel();
    btnNoLabel = dlg.GetNoLabel();
    btnOKLabel = dlg.GetOKLabel();
    btnCancelLabel = dlg.GetCancelLabel();
    btnHelpLabel = dlg.GetHelpLabel();
}

void wxMSWTaskDialogConfig::MSWCommonTaskDialogInit(TASKDIALOGCONFIG &tdc)
{
    // Use TDF_SIZE_TO_CONTENT to try to prevent Windows from truncating or
    // ellipsizing the message text. This doesn't always work as Windows will
    // still do it if the message contains too long "words" (i.e. runs of the
    // text without spaces) but at least it ensures that the message text is
    // fully shown for reasonably-sized words whereas without it using almost
    // any file system path in a message box would result in truncation.
    tdc.dwFlags = TDF_EXPAND_FOOTER_AREA |
                  TDF_SIZE_TO_CONTENT;
    tdc.hInstance = wxGetInstance();
    tdc.pszWindowTitle = caption.t_str();

    tdc.hwndParent = parent ? GetHwndOf(parent) : nullptr;

    // Don't use this flag if our parent window is minimized because this
    // results in the task dialog being shown in the top left corner of the
    // screen, which is completely unexpected, so prefer the default behaviour
    // of centering the dialog on the screen in this case.
    if ( parent && !::IsIconic(tdc.hwndParent) )
        tdc.dwFlags |= TDF_POSITION_RELATIVE_TO_WINDOW;

    if ( wxApp::MSWGetDefaultLayout(parent) == wxLayout_RightToLeft )
        tdc.dwFlags |= TDF_RTL_LAYOUT;

    // If we have both the main and extended messages, just use them as
    // intended. However if only one message is given we normally use it as the
    // content and not as the main instruction because the latter is supposed
    // to stand out compared to the former and doesn't look good if there is
    // nothing for it to contrast with. Finally, notice that the extended
    // message we use here might be automatically extracted from the main
    // message in our ctor, see comment there.
    if ( !extendedMessage.empty() )
    {
        tdc.pszMainInstruction = message.t_str();
        tdc.pszContent = extendedMessage.t_str();
    }
    else
    {
        tdc.pszContent = message.t_str();
    }

    // set an icon to be used, if possible
    switch ( iconId )
    {
        case wxICON_ERROR:
            tdc.pszMainIcon = TD_ERROR_ICON;
            break;

        case wxICON_WARNING:
            tdc.pszMainIcon = TD_WARNING_ICON;
            break;

        case wxICON_INFORMATION:
            tdc.pszMainIcon = TD_INFORMATION_ICON;
            break;

        case wxICON_AUTH_NEEDED:
            tdc.pszMainIcon = TD_SHIELD_ICON;
            break;
    }

    // custom label button array that can hold all buttons in use
    tdc.pButtons = buttons.get();

    if ( style & wxYES_NO )
    {
        AddTaskDialogButton(tdc, IDYES, TDCBF_YES_BUTTON, btnYesLabel);
        AddTaskDialogButton(tdc, IDNO,  TDCBF_NO_BUTTON,  btnNoLabel);

        if (style & wxCANCEL)
            AddTaskDialogButton(tdc, IDCANCEL,
                                TDCBF_CANCEL_BUTTON, btnCancelLabel);

        if ( style & wxNO_DEFAULT )
            tdc.nDefaultButton = IDNO;
        else if ( style & wxCANCEL_DEFAULT )
            tdc.nDefaultButton = IDCANCEL;
    }
    else // without Yes/No we're going to have an OK button
    {
        if ( style & wxCANCEL )
        {
            AddTaskDialogButton(tdc, IDOK, TDCBF_OK_BUTTON, btnOKLabel);
            AddTaskDialogButton(tdc, IDCANCEL,
                                TDCBF_CANCEL_BUTTON, btnCancelLabel);

            if ( style & wxCANCEL_DEFAULT )
                tdc.nDefaultButton = IDCANCEL;
        }
        else // Only "OK"
        {
            // We actually create a "Cancel" button instead because we want to
            // allow closing the dialog box with Escape (and also Alt-F4 or
            // clicking the close button in the title bar) which wouldn't work
            // without a Cancel button.
            if ( !useCustomLabels )
            {
                useCustomLabels = true;
                btnOKLabel = _("OK");
            }

            AddTaskDialogButton(tdc, IDCANCEL, TDCBF_CANCEL_BUTTON, btnOKLabel);
        }
    }

    if ( style & wxHELP )
    {
        // There is no support for "Help" button in the task dialog, it can
        // only show "Retry" or "Close" ones.
        useCustomLabels = true;

        AddTaskDialogButton(tdc, IDHELP, 0 /* not used */, btnHelpLabel);
    }

    tdc.pfCallback = wxTaskDialogCallback;
    tdc.lpCallbackData = (LONG_PTR) &tdc;
}

void wxMSWTaskDialogConfig::AddTaskDialogButton(TASKDIALOGCONFIG &tdc,
                                                int btnCustomId,
                                                int btnCommonId,
                                                const wxString& customLabel)
{
    if ( useCustomLabels )
    {
        // use custom buttons to implement custom labels
        TASKDIALOG_BUTTON &tdBtn = buttons[tdc.cButtons];

        tdBtn.nButtonID = btnCustomId;
        tdBtn.pszButtonText = customLabel.t_str();
        tdc.cButtons++;

        // We should never have more than 4 buttons currently as this is the
        // maximal number of buttons supported by the message dialog.
        wxASSERT_MSG( tdc.cButtons <= MAX_BUTTONS, wxT("Too many buttons") );
    }
    else
    {
        tdc.dwCommonButtons |= btnCommonId;
    }
}

// Task dialog can be used from different threads (and wxProgressDialog always
// uses it from another thread in fact) so protect access to the static
// variable below with a critical section.
wxCRIT_SECT_DECLARE(gs_csTaskDialogIndirect);

TaskDialogIndirect_t wxMSWMessageDialog::GetTaskDialogIndirectFunc()
{
    // Initialize the function pointer to an invalid value different from nullptr
    // to avoid reloading comctl32.dll and trying to resolve it every time
    // we're called if task dialog is not available (notice that this may
    // happen even under Vista+ if we don't use comctl32.dll v6).
    static const TaskDialogIndirect_t
        INVALID_TASKDIALOG_FUNC = reinterpret_cast<TaskDialogIndirect_t>(-1);
    static TaskDialogIndirect_t s_TaskDialogIndirect = INVALID_TASKDIALOG_FUNC;

    wxCRIT_SECT_LOCKER(lock, gs_csTaskDialogIndirect);

    if ( s_TaskDialogIndirect == INVALID_TASKDIALOG_FUNC )
    {
        wxLoadedDLL dllComCtl32("comctl32.dll");
        wxDL_INIT_FUNC(s_, TaskDialogIndirect, dllComCtl32);
    }

    return s_TaskDialogIndirect;
}

bool wxMSWMessageDialog::HasNativeTaskDialog()
{
    return wxMSWMessageDialog::GetTaskDialogIndirectFunc() != nullptr;
}

int wxMSWMessageDialog::MSWTranslateReturnCode(int msAns)
{
    int ans;
    switch (msAns)
    {
        default:
            wxFAIL_MSG(wxT("unexpected return code"));
            wxFALLTHROUGH;

        case IDCANCEL:
            ans = wxID_CANCEL;
            break;
        case IDOK:
            ans = wxID_OK;
            break;
        case IDYES:
            ans = wxID_YES;
            break;
        case IDNO:
            ans = wxID_NO;
            break;
        case IDHELP:
            ans = wxID_HELP;
            break;
    }

    return ans;
}

#endif // wxUSE_MSGDLG
