#include "wx/wxprec.h"

#ifndef WX_PRECOMP
#include "wx/utils.h"
#include "wx/frame.h"
#include "wx/button.h"
#include "wx/stattext.h"
#include "wx/sizer.h"
#include "wx/event.h"
#include "wx/gauge.h"
#include "wx/intl.h"
#include "wx/dcclient.h"
#include "wx/timer.h"
#include "wx/settings.h"
#include "wx/app.h"
#endif

#include "../GUI.hpp"
#include "../GUI_App.hpp"
#include "../I18N.hpp"
#include "ProgressDialog.hpp"
#include "wx/evtloop.h"
#include "Label.hpp"

// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------

#define LAYOUT_MARGIN 12

static const int wxID_SKIP = 32000;

namespace Slic3r { namespace GUI {
void ProgressDialog::Init()
{
    // we may disappear at any moment, let the others know about it
    SetExtraStyle(GetExtraStyle() | wxWS_EX_TRANSIENT);

    // Initialize all our members that we always use (even when we don't
    // create a valid window in this class).

    m_pdStyle   = 0;
    m_parentTop = NULL;

    m_gauge   = NULL;
    m_msg     = NULL;
    m_elapsed = m_estimated = m_remaining = NULL;

    m_state   = Uncancelable;
    m_maximum = 0;

    m_timeStart = wxGetCurrentTime();
    m_timeStop  = (unsigned long) -1;
    m_break     = 0;

    m_skip = false;

    m_btnAbort = NULL;
    m_btnSkip  = NULL;

    m_display_estimated = m_last_timeupdate = m_ctdelay = 0;

    m_delay = 3;

    m_winDisabler   = NULL;
    m_tempEventLoop = NULL;

    SetWindowStyle(wxDEFAULT_DIALOG_STYLE);
}

ProgressDialog::ProgressDialog() : wxDialog() { Init(); }

ProgressDialog::ProgressDialog(const wxString &title, const wxString &message, int maximum, wxWindow *parent, int style, bool adaptive) : wxDialog()
{
    m_adaptive = adaptive;
    Init();
    Create(title, message, maximum, parent, style);
    Bind(wxEVT_PAINT, &ProgressDialog::OnPaint, this);
    Bind(wxEVT_CLOSE_WINDOW, &ProgressDialog::OnClose, this);
}

void ProgressDialog::OnPaint(wxPaintEvent &evt) {}

void ProgressDialog::SetTopParent(wxWindow *parent)
{
    m_parent    = parent;
    m_parentTop = parent;
}

wxString ProgressDialog::FormatString(wxString title)
{
    if (!m_adaptive) {
        auto current_width = 0;
        m_mode             = 0;
        for (int i = 0; i < title.length(); i++) {
            current_width = m_msg->GetTextExtent(title.SubString(0, i)).x;
            if (current_width > PROGRESSDIALOG_GAUGE_SIZE.x) {
                m_mode = 1;
                // title.insert(i - 1, "\n");
                break;
            }
        }

        // set mode
        if (m_mode == 0) {
            m_simplebook->SetSelection(0);
            m_msg->SetLabel(title);
        } else {
            m_simplebook->SetSelection(1);
            m_msg_2line->SetLabel(title);
        }
    } else {
        m_msg->SetLabel(title);
        m_msg->SetMaxSize(wxSize(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x + 5, -1));
        m_msg->SetMinSize(wxSize(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x  + 5, -1));
        m_msg->Wrap(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x  + 5);
        m_msg->Layout();
        m_msg->Fit();

        m_msg_scrolledWindow->SetSize(wxSize(FromDIP(m_msg->GetSize().x + 5), FromDIP(150)));
        m_msg_scrolledWindow->SetMinSize(wxSize(FromDIP(m_msg->GetSize().x + 5), FromDIP(150)));
        m_msg_scrolledWindow->SetMaxSize(wxSize(FromDIP(m_msg->GetSize().x + 5), FromDIP(150)));
       // m_msg_scrolledWindow->Layout();
        m_msg_scrolledWindow->Fit();
    }

    
    //Fit();
    return title;
}

bool ProgressDialog::Create(const wxString &title, const wxString &message, int maximum, wxWindow *parent, int style)
{
    SetFont(wxGetApp().normal_font());
    SetTopParent(parent);

    m_pdStyle = style;

    if (!wxDialog::Create(m_parentTop, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, GetWindowStyle())) return false;
    SetBackgroundColour(PROGRESSDIALOG_DEF_BK);

    /* SetSize(DESIGN_RESOUTION_PROGRESS_SIZE);
     SetMinSize(DESIGN_RESOUTION_PROGRESS_SIZE);
     SetMaxSize(DESIGN_RESOUTION_PROGRESS_SIZE);*/

    SetMaximum(maximum);
    EnsureActiveEventLoopExists();

#if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
    if (!HasPDFlag(wxPD_CAN_ABORT)) { EnableCloseButton(false); }
#endif // wxMSW

    m_state = HasPDFlag(wxPD_CAN_ABORT) ? Continue : Uncancelable;

    wxBoxSizer *m_sizer_main = new wxBoxSizer(wxVERTICAL);

    m_top_line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
    m_top_line->SetBackgroundColour(wxColour(166, 169, 170));

    m_sizer_main->Add(m_top_line, 0, wxEXPAND, 0);
    m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(24));

    if (!m_adaptive) {
        m_simplebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, PROGRESSDIALOG_SIMPLEBOOK_SIZE, 0);

        m_panel_1line = new wxPanel(m_simplebook, wxID_ANY, wxDefaultPosition, PROGRESSDIALOG_SIMPLEBOOK_SIZE, 0);
        m_panel_1line->SetMaxSize(PROGRESSDIALOG_SIMPLEBOOK_SIZE);
        m_panel_1line->SetBackgroundColour(PROGRESSDIALOG_DEF_BK);
        m_simplebook->AddPage(m_panel_1line, wxEmptyString, false);

        m_panel_2line = new wxPanel(m_simplebook, wxID_ANY, wxDefaultPosition, PROGRESSDIALOG_SIMPLEBOOK_SIZE, 0);
        m_panel_2line->SetMaxSize(PROGRESSDIALOG_SIMPLEBOOK_SIZE);
        m_panel_2line->SetBackgroundColour(PROGRESSDIALOG_DEF_BK);
        m_simplebook->AddPage(m_panel_2line, wxEmptyString, false);

        wxBoxSizer *sizer_1line = new wxBoxSizer(wxHORIZONTAL);
        m_msg                   = new wxStaticText(m_panel_1line, wxID_ANY, wxEmptyString, wxDefaultPosition, PROGRESSDIALOG_SIMPLEBOOK_SIZE, 0);
        m_msg->Wrap(-1);
        m_msg->SetFont(::Label::Body_13);
        m_msg->SetForegroundColour(PROGRESSDIALOG_GREY_700);
        sizer_1line->Add(m_msg, 0, wxALIGN_CENTER, 0);
        m_panel_1line->SetSizer(sizer_1line);
        m_panel_1line->Layout();
        sizer_1line->Fit(m_panel_1line);

        wxBoxSizer *sizer_2line = new wxBoxSizer(wxVERTICAL);
        m_msg_2line             = new wxStaticText(m_panel_2line, wxID_ANY, wxEmptyString, wxDefaultPosition, PROGRESSDIALOG_SIMPLEBOOK_SIZE, 0);
        m_msg_2line->Wrap(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x);
        m_msg_2line->SetFont(::Label::Body_13);
        m_msg_2line->SetForegroundColour(PROGRESSDIALOG_GREY_700);
        m_msg_2line->SetMaxSize(PROGRESSDIALOG_SIMPLEBOOK_SIZE);
        sizer_2line->Add(m_msg_2line, 1, wxALL, 0);
        m_panel_2line->SetSizer(sizer_2line);
        m_panel_2line->Layout();
        sizer_2line->Fit(m_panel_2line);

        m_sizer_main->Add(m_simplebook, 1, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(28));
    } else {
        m_msg_scrolledWindow = new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL );
        m_msg_scrolledWindow->SetScrollRate(0,5);
        wxBoxSizer* m_msg_sizer= new wxBoxSizer(wxVERTICAL);

        m_msg = new wxStaticText(m_msg_scrolledWindow, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x, -1), 0);
        m_msg->Wrap(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x);
        m_msg->SetFont(::Label::Body_13);
        m_msg->SetForegroundColour(PROGRESSDIALOG_GREY_700);

        m_msg_sizer->Add(m_msg, 0, wxEXPAND | wxALL, 0);
        m_msg_scrolledWindow->SetSizer(m_msg_sizer);
        m_msg_scrolledWindow->Layout();
        m_msg_sizer->Fit(m_msg_scrolledWindow);
        m_sizer_main->Add(m_msg_scrolledWindow, 0, wxEXPAND | wxALL, FromDIP(28));
    }


    m_sizer_main->Add(0, 0, 0, wxEXPAND | wxTOP, 0);

    int gauge_style = wxGA_HORIZONTAL;
    if (style & wxPD_SMOOTH) gauge_style |= wxGA_SMOOTH;
    gauge_style |= wxGA_PROGRESS;

#ifdef __WXMSW__
    maximum /= m_factor;
#endif

    m_gauge = new wxGauge(this, wxID_ANY, maximum, wxDefaultPosition, PROGRESSDIALOG_GAUGE_SIZE, gauge_style);
    m_gauge->SetValue(0);
    m_sizer_main->Add(m_gauge, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(28));

#ifdef __WXMSW__
    //m_block_left = new wxWindow(m_gauge, wxID_ANY, wxPoint(0, 0), wxSize(FromDIP(2), PROGRESSDIALOG_GAUGE_SIZE.y * 2));
    //m_block_left->SetBackgroundColour(PROGRESSDIALOG_DEF_BK);
    //m_block_right = new wxWindow(m_gauge, wxID_ANY, wxPoint(PROGRESSDIALOG_GAUGE_SIZE.x - 2, 0), wxSize(FromDIP(2), PROGRESSDIALOG_GAUGE_SIZE.y * 2));
    //m_block_right->SetBackgroundColour(PROGRESSDIALOG_DEF_BK);
#endif
    wxBoxSizer *m_sizer_bottom = new wxBoxSizer(wxHORIZONTAL);

    m_sizer_bottom->Add(0, 0, 1, wxEXPAND, 0);

    if (HasPDFlag(wxPD_CAN_ABORT)) {
        m_button_cancel = new Button(this, _L("Cancel"));
        m_button_cancel->SetTextColor(PROGRESSDIALOG_GREY_700);
        m_button_cancel->SetMinSize(PROGRESSDIALOG_CANCEL_BUTTON_SIZE);
        m_button_cancel->SetCornerRadius(PROGRESSDIALOG_CANCEL_BUTTON_SIZE.y / 2);
        m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) {
            if (m_state == Finished) {
                event.Skip();
            } else {
                m_state = Canceled;
                DisableAbort();
                m_button_cancel->Enable(false);
                DisableSkip();
                m_timeStop = wxGetCurrentTime();
            }
        });
        m_sizer_bottom->Add(m_button_cancel, 0, wxALL, 0);
    }

    m_sizer_main->Add(0, 0, 0, wxEXPAND | wxTOP, FromDIP(16));
    m_sizer_main->Add(m_sizer_bottom, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(28));
    m_sizer_main->Add(0, 0, 0, wxEXPAND | wxTOP, FromDIP(10));

    wxGetApp().UpdateDlgDarkUI(this);

    SetSizer(m_sizer_main);
    Layout();
    Fit();
    Centre(wxCENTER_FRAME | wxBOTH);
    DisableOtherWindows();
    FormatString(message);

    Show();
    Enable();
    if (m_elapsed) { SetTimeLabel(0, m_elapsed); }

    Update();
    return true;

    //
    //    sizerTop = new wxBoxSizer(wxVERTICAL);
    //    auto def_size_width = wxMin(wxGetClientDisplayRect().width / 3, 305);
    //
    //    wxStaticLine *m_staticline2 = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxSize(def_size_width, 1), wxLI_HORIZONTAL);
    //    sizerTop->Add(m_staticline2, 0, wxEXPAND, 0);
    //    auto m_block_top = new wxWindow(this, wxID_ANY, wxDefaultPosition, wxSize(def_size_width, 20));
    //    m_block_top->SetBackgroundColour(wxColor(DESIGN_RESOUTION_DEF_BK_COLOR));
    //    sizerTop->Add(m_block_top, 0, wxEXPAND);
    //
    //    m_message_area = new wxWindow(this, wxID_ANY, wxDefaultPosition, wxSize(def_size_width, 38));
    //    m_message_area->SetBackgroundColour(DESIGN_RESOUTION_DEF_BK_COLOR);
    //    sizerTop->Add(m_message_area, 0, wxLEFT | wxRIGHT | wxBottom | wxEXPAND, 2 * LAYOUT_MARGIN);
    //
    //    m_msg = new wxStaticText(m_message_area, wxID_ANY, wxString(""), wxPoint(0, 10), wxSize(def_size_width, 20), wxST_ELLIPSIZE_END);
    //    m_msg->SetForegroundColour(wxColour(107, 107, 107));
    //    m_msg->SetBackgroundColour(DESIGN_RESOUTION_DEF_BK_COLOR);
    //    m_msg->SetMinSize(wxSize(def_size_width, 20));
    //    m_msg->SetMaxSize(wxSize(def_size_width, 20));
    //
    //    m_msg_2line = new wxStaticText(m_message_area, wxID_ANY, wxString(""), wxPoint(0, 0), wxSize(def_size_width, 40), wxST_ELLIPSIZE_END);
    //    m_msg_2line->SetForegroundColour(wxColour(107, 107, 107));
    //    m_msg_2line->SetBackgroundColour(DESIGN_RESOUTION_DEF_BK_COLOR);
    //    m_msg_2line->SetMinSize(wxSize(def_size_width, 40));
    //    m_msg_2line->SetMaxSize(wxSize(def_size_width, 40));
    //
    //    auto block_left = new wxWindow(m_gauge, -1, wxPoint(0, 0), wxSize(2, m_gauge->GetSize().GetHeight()));
    //    block_left->SetBackgroundColour(wxColor(255, 255, 255));
    //
    //    auto block_right = new wxWindow(m_gauge, -1, wxPoint(m_gauge->GetSize().GetWidth() - 2, 0), wxSize(2, m_gauge->GetSize().GetHeight()));
    //    block_right->SetBackgroundColour(wxColor(255, 255, 255));
    //
    //    sizerTop->Add(m_gauge, 0, wxLEFT | wxRIGHT | wxBottom, 2 * LAYOUT_MARGIN);
    //    m_gauge->SetValue(0);
    //
    //    m_elapsed = m_estimated = m_remaining = NULL;
    //    size_t nTimeLabels                    = 0;
    //
    //    wxSizer *const sizerLabels = new wxFlexGridSizer(2);
    //
    //    if (style & wxPD_ELAPSED_TIME) {
    //        nTimeLabels++;
    //
    //        m_elapsed = CreateLabel(GetElapsedLabel(), sizerLabels);
    //    }
    //
    //    if (style & wxPD_ESTIMATED_TIME) {
    //        nTimeLabels++;
    //
    //        m_estimated = CreateLabel(GetEstimatedLabel(), sizerLabels);
    //    }
    //
    //    if (style & wxPD_REMAINING_TIME) {
    //        nTimeLabels++;
    //
    //        m_remaining = CreateLabel(GetRemainingLabel(), sizerLabels);
    //    }
    //    sizerTop->Add(sizerLabels, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP | wxBottom, LAYOUT_MARGIN);
    //
    //    wxStdDialogButtonSizer *buttonSizer = wxDialog::CreateStdDialogButtonSizer(0);
    //
    //    const int borderFlags = wxALL;
    //
    //    std::string icon_path = (boost::format("%1%/images/common_dialog_confirm.png") % resources_dir()).str();
    //    /*m_btnAbort            = new wxBitmapButton(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(52, 24), wxBU_AUTODRAW | wxBORDER_NONE);
    //    m_btnAbort->SetBitmap(wxBitmap(icon_path, wxBITMAP_TYPE_ANY));
    //    m_btnAbort->SetBitmapDisabled(wxBitmap(icon_path, wxBITMAP_TYPE_ANY));
    //    m_btnAbort->SetBitmapPressed(wxBitmap(icon_path, wxBITMAP_TYPE_ANY));
    //    m_btnAbort->SetBitmapFocus(wxBitmap(icon_path, wxBITMAP_TYPE_ANY));
    //    m_btnAbort->SetBitmapCurrent(wxBitmap(icon_path, wxBITMAP_TYPE_ANY));*/
    //
    //     m_btnAbort = new wxButton(this, wxID_CANCEL, wxString(""), wxDefaultPosition, wxSize(52,24));
    //
    //     wxStaticBitmap *m_bitmatAbort = new wxStaticBitmap(m_btnAbort, wxID_ANY, wxBitmap(icon_path, wxBITMAP_TYPE_ANY), wxDefaultPosition, wxSize(52, 24), 0);
    //     wxStaticText *textAbort = new wxStaticText(m_btnAbort, wxID_ANY, _T("Cancel"), wxPoint(5, 3), wxSize(42, 19));
    //     textAbort->SetBa
    //     ckgroundColour(wxColor(0, 150, 136));
    //     textAbort->SetForegroundColour(DESIGN_RESOUTION_DEF_BK_COLOR);
    //
    //     textAbort->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) {
    //        if (m_state == Finished) {
    //            event.Skip();
    //        } else {
    //            m_state = Canceled;
    //            //DisableAbort();
    //            DisableSkip();
    //            m_timeStop = wxGetCurrentTime();
    //        }
    //    });
    //
    //     wxSizerFlags sizerFlags = wxSizerFlags().Border(borderFlags, LAYOUT_MARGIN);
    //    wxWindow *m_button_sizer = new wxWindow(this, wxID_ANY, wxDefaultPosition, wxSize(def_size_width, 26));
    //    m_button_sizer->SetBackgroundColour(DESIGN_RESOUTION_DEF_BK_COLOR);
    //
    //    if (HasPDFlag(wxPD_CAN_SKIP)) {
    //        m_btnSkip = new wxButton(this, wxID_SKIP, wxGetTranslation("&Skip"));
    //        m_btnSkip->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ProgressDialog::OnSkip, this);
    //        buttonSizer->SetNegativeButton(m_btnSkip);
    //    }
    //
    //    if (HasPDFlag(wxPD_CAN_ABORT)) {
    //        /*m_btnAbort = new wxButton(this, wxID_CANCEL);
    //        buttonSizer->SetCancelButton(m_btnAbort);
    //        m_btnAbort->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ProgressDialog::OnCancel, this);*/
    //
    //        m_button_calcel = new Button(m_button_sizer, _T("Cancel"));
    //        m_button_calcel->SetTextColor(wxColour(107, 107, 107));
    //        m_button_calcel->SetSize(60, 24);
    //        m_button_calcel->SetPosition(wxPoint(m_button_sizer->GetSize().GetWidth() - m_button_calcel->GetSize().GetWidth(), 0));
    //
    //        m_button_calcel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) {
    //            if (m_state == Finished) {
    //                event.Skip();
    //            } else {
    //                m_state = Canceled;
    //                 DisableAbort();
    //                m_button_calcel->Enable(false);
    //                DisableSkip();
    //                m_timeStop = wxGetCurrentTime();
    //            }
    //        });
    //    }
    //
    //    if (!HasPDFlag(wxPD_CAN_SKIP | wxPD_CAN_ABORT)) buttonSizer->AddSpacer(LAYOUT_MARGIN);
    //
    //    sizerTop->Add(m_button_sizer, 1, wxEXPAND, 0);
    //
    //    auto m_block_bottom = new wxWindow(this, wxID_ANY, wxDefaultPosition, wxSize(def_size_width, 15));
    //    m_block_bottom->SetBackgroundColour(DESIGN_RESOUTION_DEF_BK_COLOR);
    //    sizerTop->Add(m_block_bottom, 0, wxEXPAND);
    //
    //    SetSizerAndFit(sizerTop);
    //    FormatString(message);
    //
    //    Centre(wxCENTER_FRAME | wxBOTH);
    //
    //    DisableOtherWindows();
    //
    //    Show();
    //    Enable();
    //    if (m_elapsed) { SetTimeLabel(0, m_elapsed); }
    //
    //    Update();
    //    return true;
}

void ProgressDialog::UpdateTimeEstimates(int value, unsigned long &elapsedTime, unsigned long &estimatedTime, unsigned long &remainingTime)
{
    unsigned long elapsed = wxGetCurrentTime() - m_timeStart;
    if (value != 0 && (m_last_timeupdate < elapsed || value == m_maximum)) {
        m_last_timeupdate       = elapsed;
        unsigned long estimated = m_break + (unsigned long) (((double) (elapsed - m_break) * m_maximum) / ((double) value));
        if (estimated > m_display_estimated && m_ctdelay >= 0) {
            ++m_ctdelay;
        } else if (estimated < m_display_estimated && m_ctdelay <= 0) {
            --m_ctdelay;
        } else {
            m_ctdelay = 0;
        }
        if (m_ctdelay >= m_delay             // enough confirmations for a higher value
            || m_ctdelay <= (m_delay * -1)   // enough confirmations for a lower value
            || value == m_maximum            // to stay consistent
            || elapsed > m_display_estimated // to stay consistent
            || (elapsed > 0 && elapsed < 4)  // additional updates in the beginning
        ) {
            m_display_estimated = estimated;
            m_ctdelay           = 0;
        }
    }

    if (value != 0) {
        long display_remaining = m_display_estimated - elapsed;
        if (display_remaining < 0) { display_remaining = 0; }

        estimatedTime = m_display_estimated;
        remainingTime = display_remaining;
    }

    elapsedTime = elapsed;
}

// static
wxString ProgressDialog::GetFormattedTime(unsigned long timeInSec)
{
    wxString timeAsHMS;

    if (timeInSec == (unsigned long) -1) {
        timeAsHMS = wxGetTranslation("Unknown");
    } else {
        unsigned hours   = timeInSec / 3600;
        unsigned minutes = (timeInSec % 3600) / 60;
        unsigned seconds = timeInSec % 60;
        timeAsHMS.Printf("%u:%02u:%02u", hours, minutes, seconds);
    }

    return timeAsHMS;
}

void ProgressDialog::EnsureActiveEventLoopExists()
{
    if (!wxEventLoopBase::GetActive()) {
        m_tempEventLoop = new wxEventLoop;
        wxEventLoop::SetActive(m_tempEventLoop);
    }
}

wxStaticText *ProgressDialog::CreateLabel(const wxString &text, wxSizer *sizer)
{
    wxStaticText *label = new wxStaticText(this, wxID_ANY, text);
    wxStaticText *value = new wxStaticText(this, wxID_ANY, wxGetTranslation("unknown"));

    // select placement most native or nice on target GUI
#if defined(__WXMSW__) || defined(__WXMAC__) || defined(__WXGTK20__)
    // value and time centered in one row
    sizer->Add(label, 1, wxALIGN_RIGHT | wxTOP | wxRIGHT, LAYOUT_MARGIN);
    sizer->Add(value, 1, wxALIGN_LEFT | wxTOP, LAYOUT_MARGIN);
#else
    // value and time to the right in one row
    sizer->Add(label);
    sizer->Add(value, 0, wxLEFT, LAYOUT_MARGIN);
#endif

    return value;
}

// ----------------------------------------------------------------------------
// ProgressDialog operations
// ----------------------------------------------------------------------------

bool ProgressDialog::Update(int value, const wxString &newmsg, bool *skip)
{
    if (!DoBeforeUpdate(skip)) return false;

    wxCHECK_MSG(m_gauge, false, "dialog should be fully created");

#ifdef __WXMSW__
    value /= m_factor;
#endif // __WXMSW__

    wxASSERT_MSG(value <= m_maximum, wxT("invalid progress value"));
    m_gauge->SetValue(value);

    UpdateMessage(newmsg);

    if ((m_elapsed || m_remaining || m_estimated) && (value != 0)) {
        unsigned long elapsed;
        unsigned long display_remaining;

        UpdateTimeEstimates(value, elapsed, m_display_estimated, display_remaining);

        SetTimeLabel(elapsed, m_elapsed);
        SetTimeLabel(m_display_estimated, m_estimated);
        SetTimeLabel(display_remaining, m_remaining);
    }

    if (value == m_maximum) {
        if (m_state == Finished) {
            // ignore multiple calls to Update(m_maximum): it may sometimes be
            // troublesome to ensure that Update() is not called twice with the
            // same value (e.g. because of the rounding errors) and if we don't
            // return now we're going to generate asserts below
            return true;
        }

        // so that we return true below and that out [Cancel] handler knew what
        // to do
        m_state = Finished;
        if (!HasPDFlag(wxPD_AUTO_HIDE)) {
            EnableClose();
            DisableSkip();
#if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
            EnableCloseButton();
#endif // __WXMSW__

            if (newmsg.empty()) {
                // also provide the finishing message if the application didn't
                m_msg->SetLabel(wxGetTranslation("Done."));
            }

            // allow the window to repaint:
            // NOTE: since we yield only for UI events with this call, there
            //       should be no side-effects
            wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);

            // NOTE: this call results in a new event loop being created
            //       and to a call to ProcessPendingEvents() (which may generate
            //       unwanted re-entrancies).
            (void) ShowModal();
        } else // auto hide
        {
            // reenable other windows before hiding this one because otherwise
            // Windows wouldn't give the focus back to the window which had
            // been previously focused because it would still be disabled
            ReenableOtherWindows();

            Hide();
        }
    } else // not at maximum yet
    {
        DoAfterUpdate();
    }

    // update the display in case yielding above didn't do it
    Update();

    return m_state != Canceled;
}

bool ProgressDialog::Pulse(const wxString &newmsg, bool *skip)
{
    if (!DoBeforeUpdate(skip)) return false;

    wxCHECK_MSG(m_gauge, false, "dialog should be fully created");

    // show a bit of progress
    m_gauge->Pulse();

    UpdateMessage(newmsg);

    if (m_elapsed || m_remaining || m_estimated) {
        unsigned long elapsed = wxGetCurrentTime() - m_timeStart;

        SetTimeLabel(elapsed, m_elapsed);
        SetTimeLabel((unsigned long) -1, m_estimated);
        SetTimeLabel((unsigned long) -1, m_remaining);
    }

    DoAfterUpdate();

    return m_state != Canceled;
}

bool ProgressDialog::WasCanceled() const { return m_state == Canceled; }

bool ProgressDialog::DoBeforeUpdate(bool *skip)
{
    // we have to yield because not only we want to update the display but
    // also to process the clicks on the cancel and skip buttons
    // NOTE: using YieldFor() this call shouldn't give re-entrancy problems
    //       for event handlers not interested to UI/user-input events.
    wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);

    Update();

    if (m_skip && skip && !*skip) {
        *skip  = true;
        m_skip = false;
        EnableSkip();
    }

    return m_state != Canceled;
}

void ProgressDialog::DoAfterUpdate()
{
    // allow the window to repaint:
    // NOTE: since we yield only for UI events with this call, there
    //       should be no side-effects
    wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
}

void ProgressDialog::Resume()
{
    m_state   = Continue;
    m_ctdelay = m_delay; // force an update of the elapsed/estimated/remaining time
    m_break += wxGetCurrentTime() - m_timeStop;

    EnableAbort();
    m_button_cancel->Enable(true);
    EnableSkip();
    m_skip = false;
}

bool ProgressDialog::Show(bool show)
{
    // reenable other windows before hiding this one because otherwise
    // Windows wouldn't give the focus back to the window which had
    // been previously focused because it would still be disabled
    if (!show) ReenableOtherWindows();
    return wxDialog::Show(show);
}

int ProgressDialog::GetValue() const
{
    wxCHECK_MSG(m_gauge, -1, "dialog should be fully created");

    return m_gauge->GetValue();
}

int ProgressDialog::GetRange() const { return m_maximum; }

wxString ProgressDialog::GetMessage() const { return m_msg->GetLabel(); }

void ProgressDialog::SetRange(int maximum)
{
    wxCHECK_RET(m_gauge, "dialog should be fully created");

    wxCHECK_RET(maximum > 0, "Invalid range");

    m_gauge->SetRange(maximum);

    SetMaximum(maximum);
}

void ProgressDialog::SetMaximum(int maximum)
{
    m_maximum = maximum;

#if defined(__WXMSW__)
    // we can't have values > 65,536 in the progress control under Windows, so
    // scale everything down
    m_factor = m_maximum / 65536 + 1;
    m_maximum /= m_factor;
#endif // __WXMSW__
}

bool ProgressDialog::WasCancelled() const { return HasPDFlag(wxPD_CAN_ABORT) && m_state == Canceled; }

bool ProgressDialog::WasSkipped() const { return HasPDFlag(wxPD_CAN_SKIP) && m_skip; }

// static
void ProgressDialog::SetTimeLabel(unsigned long val, wxStaticText *label)
{
    if (label) {
        wxString s;

        if (val != (unsigned long) -1) {
            s = GetFormattedTime(val);
        } else {
            s = wxGetTranslation("Unknown");
        }

        if (s != label->GetLabel()) label->SetLabel(s);
    }
}

// ----------------------------------------------------------------------------
// event handlers
// ----------------------------------------------------------------------------

void ProgressDialog::OnCancel(wxCommandEvent &event)
{
    if (m_state == Finished) {
        // this means that the count down is already finished and we're being
        // shown as a modal dialog - so just let the default handler do the job
        event.Skip();
    } else {
        // request to cancel was received, the next time Update() is called we
        // will handle it
        m_state = Canceled;

        // update the buttons state immediately so that the user knows that the
        // request has been noticed
        DisableAbort();
        DisableSkip();

        // save the time when the dialog was stopped
        m_timeStop = wxGetCurrentTime();
    }
}

void ProgressDialog::OnSkip(wxCommandEvent &WXUNUSED(event))
{
    DisableSkip();
    m_skip = true;
}

void ProgressDialog::OnClose(wxCloseEvent &event)
{
    if (m_state == Uncancelable) {
        // can't close this dialog
        event.Veto();
    } else if (m_state == Finished) {
        // let the default handler close the window as we already terminated
        event.Skip();
    } else {
        // next Update() will notice it
        m_state = Canceled;
        DisableAbort();
        DisableSkip();

        m_timeStop = wxGetCurrentTime();
    }
}

// ----------------------------------------------------------------------------
// destruction
// ----------------------------------------------------------------------------

ProgressDialog::~ProgressDialog()
{
    // normally this should have been already done, but just in case
    ReenableOtherWindows();

    if (m_tempEventLoop) {
        // If another event loop has been installed as active during the life
        // time of this object, we shouldn't deactivate it, but we also can't
        // delete our m_tempEventLoop in this case because it risks leaving the
        // new event loop with a dangling pointer, which it will set back as
        // the active loop when it exits, resulting in a crash. So we have no
        // choice but to just leak this pointer then, which is, of course, bad
        // and usually easily avoidable by just destroying the progress dialog
        // sooner, so warn the programmer about it.
        wxCHECK_RET(wxEventLoopBase::GetActive() == m_tempEventLoop, "current event loop must not be changed during "
                                                                     "ProgressDialog lifetime");

        wxEventLoopBase::SetActive(NULL);
        delete m_tempEventLoop;
    }
}

void ProgressDialog::DoSetSize(int x, int y, int width, int height, int sizeFlags /*= wxSIZE_AUTO*/)
{
    if (m_button_cancel != nullptr) { m_button_cancel->SetMinSize(PROGRESSDIALOG_CANCEL_BUTTON_SIZE); }

#ifdef __WXMSW__
    //if (m_block_left != nullptr && m_block_right != nullptr) {
    //    m_block_left->SetPosition(wxPoint(0, 0));
    //    m_block_right->SetPosition(wxPoint(PROGRESSDIALOG_GAUGE_SIZE.x - 2, 0));
    //}
#endif
    wxWindow::DoSetSize(x, y, width, height, sizeFlags);
}

void ProgressDialog::DisableOtherWindows()
{
    if (HasPDFlag(wxPD_APP_MODAL)) {
#if defined(__WXOSX__)
        if (m_parentTop) m_parentTop->Disable();
        m_winDisabler = NULL;
#else
        m_winDisabler = new wxWindowDisabler(this);
#endif
    } else {
        if (m_parentTop) m_parentTop->Disable();
        m_winDisabler = NULL;
    }

}

void ProgressDialog::ReenableOtherWindows()
{
    if (HasPDFlag(wxPD_APP_MODAL)) {
#if defined(__WXOSX__)
        if (m_parentTop) m_parentTop->Enable();
#else
        wxDELETE(m_winDisabler);
#endif
    } else {
        if (m_parentTop) m_parentTop->Enable();
    }
}

// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------

void ProgressDialog::EnableSkip(bool enable)
{
    if (HasPDFlag(wxPD_CAN_SKIP)) {
        if (m_btnSkip) m_btnSkip->Enable(enable);
    }
}

void ProgressDialog::EnableAbort(bool enable)
{
    if (HasPDFlag(wxPD_CAN_ABORT)) {
        if (m_btnAbort) m_btnAbort->Enable(enable);
    }
}

void ProgressDialog::EnableClose()
{
    if (HasPDFlag(wxPD_CAN_ABORT)) {
        if (m_btnAbort) {
            m_btnAbort->Enable();
            m_btnAbort->SetLabel(wxGetTranslation("Close"));
        }
    }
}

void ProgressDialog::UpdateMessage(const wxString &newmsg)
{
    if (!newmsg.empty() && newmsg != m_msg->GetLabel()) {
        const wxSize sizeOld = m_msg->GetSize();

        FormatString(newmsg);
        Fit();
        // m_msg->SetLabel(newmsg);

        if (m_msg->GetSize().x > sizeOld.x) {
            // Resize the dialog to fit its new, longer contents instead of
            // just truncating it.
            Fit();
        }

        // allow the window to repaint:
        // NOTE: since we yield only for UI events with this call, there
        //       should be no side-effects
        wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
    }
}
}} // namespace Slic3r::GUI
