// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under    *
// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0           *
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************

#include "popup_dlg.h"
#include <wx/app.h>
#include <wx/display.h>
#include <wx+/std_button_layout.h>
#include <wx+/font_size.h>
#include <wx+/image_resources.h>
#include "popup_dlg_generated.h"


using namespace zen;

namespace
{
void setAsStandard(wxButton& btn)
{
    btn.SetDefault();
    btn.SetFocus();
}


void setBestInitialSize(wxTextCtrl& ctrl, const wxString& text, wxSize maxSize)
{
    const int scrollbarWidth = 30;
    if (maxSize.x <= scrollbarWidth) //implicitly checks for non-zero, too!
        return;
    maxSize.x -= scrollbarWidth;

    int bestWidth = 0;
    int rowCount  = 0;
    int rowHeight = 0;

    auto evalLineExtent = [&](const wxSize& sz) -> bool //return true when done
    {
        if (sz.x > bestWidth)
            bestWidth = std::min(maxSize.x, sz.x);

        rowCount += (sz.x + maxSize.x - 1) / maxSize.x; //integer round up: consider line-wraps!
        rowHeight = std::max(rowHeight, sz.y); //all rows *should* have same height

        return rowCount * rowHeight >= maxSize.y;
    };

    for (auto it = text.begin();;)
    {
        auto itEnd = std::find(it, text.end(), L'\n');
        wxString line(it, itEnd);
        if (line.empty())
            line = L" "; //GetTextExtent() returns (0, 0) for empty strings!

        wxSize sz = ctrl.GetTextExtent(line); //exactly gives row height, but does *not* consider newlines
        if (evalLineExtent(sz))
            break;

        if (itEnd == text.end())
            break;
        it = itEnd + 1;
    }

    const int rowGap = 0;
    const wxSize bestSize(bestWidth + scrollbarWidth, std::min(rowCount * (rowHeight + rowGap), maxSize.y));
    ctrl.SetMinSize(bestSize); //alas, SetMinClientSize() is just not working!
}
}


class zen::StandardPopupDialog : public PopupDialogGenerated
{
public:
    StandardPopupDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg) :
        PopupDialogGenerated(parent),
        checkBoxValue_(cfg.checkBoxValue)
    {


        wxBitmap iconTmp;
        wxString titleTmp;
        switch (type)
        {
            case DialogInfoType::INFO:
                //"Information" is meaningless as caption text!
                //confirmation doesn't use info icon
                //iconTmp = getResourceImage(L"msg_info");
                break;
            case DialogInfoType::WARNING:
                iconTmp  = getResourceImage(L"msg_warning");
                titleTmp = _("Warning");
                break;
            case DialogInfoType::ERROR2:
                iconTmp  = getResourceImage(L"msg_error");
                titleTmp = _("Error");
                break;
        }
        if (cfg.icon.IsOk())
            iconTmp = cfg.icon;

        if (!cfg.title.empty())
            titleTmp = cfg.title;
        //-----------------------------------------------
        m_bitmapMsgType->SetBitmap(iconTmp);

        if (titleTmp.empty())
            SetTitle(wxTheApp->GetAppDisplayName());
        else
        {
            if (parent && parent->IsShownOnScreen())
                SetTitle(titleTmp);
            else
                SetTitle(wxTheApp->GetAppDisplayName() + L" - " + titleTmp);
        }

        int maxWidth  = 500;
        int maxHeight = 400; //try to determine better value based on actual display resolution:

        if (parent)
        {
            const int disPos = wxDisplay::GetFromWindow(parent); //window must be visible
            if (disPos != wxNOT_FOUND)
                maxHeight = wxDisplay(disPos).GetClientArea().GetHeight() *  2 / 3;
        }

        assert(!cfg.textMain.empty() || !cfg.textDetail.empty());
        if (!cfg.textMain.empty())
        {
            setMainInstructionFont(*m_staticTextMain);
            m_staticTextMain->SetLabel(cfg.textMain);
            m_staticTextMain->Wrap(maxWidth); //call *after* SetLabel()
        }
        else
            m_staticTextMain->Hide();

        if (!cfg.textDetail.empty())
        {
            const wxString& text = L"\n" + trimCpy(cfg.textDetail) + L"\n"; //add empty top/bottom lines *instead* of using border space!
            setBestInitialSize(*m_textCtrlTextDetail, text, wxSize(maxWidth, maxHeight));
            m_textCtrlTextDetail->ChangeValue(text);
        }
        else
            m_textCtrlTextDetail->Hide();

        if (checkBoxValue_)
        {
            assert(contains(cfg.checkBoxLabel, L"&"));
            m_checkBoxCustom->SetLabel(cfg.checkBoxLabel);
            m_checkBoxCustom->SetValue(*checkBoxValue_);
        }
        else
            m_checkBoxCustom->Hide();

        Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(StandardPopupDialog::OnKeyPressed), nullptr, this); //dialog-specific local key events
    }

private:
    void OnClose (wxCloseEvent&   event) override { EndModal(static_cast<int>(ConfirmationButton3::CANCEL)); }
    void OnCancel(wxCommandEvent& event) override { EndModal(static_cast<int>(ConfirmationButton3::CANCEL)); }

    void OnKeyPressed(wxKeyEvent& event)
    {
        switch (event.GetKeyCode())
        {
            case WXK_RETURN:
            case WXK_NUMPAD_ENTER:
            {
                wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED);
                OnButtonAffirmative(dummy);
                return;
            }

            case WXK_ESCAPE: //handle case where cancel button is hidden!
                EndModal(static_cast<int>(ConfirmationButton3::CANCEL));
                return;
        }
        event.Skip();
    }

    void OnButtonAffirmative(wxCommandEvent& event) override
    {
        if (checkBoxValue_)
            *checkBoxValue_ = m_checkBoxCustom->GetValue();
        EndModal(static_cast<int>(ConfirmationButton3::DO_IT));
    }

    void OnButtonNegative(wxCommandEvent& event) override
    {
        if (checkBoxValue_)
            *checkBoxValue_ = m_checkBoxCustom->GetValue();
        EndModal(static_cast<int>(ConfirmationButton3::DONT_DO_IT));
    }

    bool* checkBoxValue_;
};


namespace
{
class NotificationDialog : public StandardPopupDialog
{
public:
    NotificationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg) :
        StandardPopupDialog(parent, type, cfg)
    {
        m_buttonAffirmative->SetLabel(_("Close")); //UX Guide: use "Close" for errors, warnings and windows in which users can't make changes (no ampersand!)
        m_buttonNegative->Hide();
        m_buttonCancel->Hide();

        //set std order after button visibility was set
        setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonAffirmative));
        setAsStandard(*m_buttonAffirmative);
        GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
        Center(); //needs to be re-applied after a dialog size change!
    }
};


class ConfirmationDialog : public StandardPopupDialog
{
public:
    ConfirmationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg, const wxString& labelDoIt) :
        StandardPopupDialog(parent, type, cfg)
    {
        assert(contains(labelDoIt, L"&"));
        m_buttonAffirmative->SetLabel(labelDoIt);
        m_buttonNegative->Hide();

        //set std order after button visibility was set
        setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonAffirmative).setCancel(m_buttonCancel));
        setAsStandard(*m_buttonAffirmative);
        GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
        Center(); //needs to be re-applied after a dialog size change!
    }
};
}

class zen::ConfirmationDialog3 : public StandardPopupDialog
{
public:
    ConfirmationDialog3(wxWindow* parent, DialogInfoType type, const PopupDialogCfg3& cfg, const wxString& labelDoIt, const wxString& labelDontDoIt) :
        StandardPopupDialog(parent, type, cfg.pdCfg_),
        buttonToDisableWhenChecked_(cfg.buttonToDisableWhenChecked_)
    {
        assert(contains(labelDoIt,     L"&"));
        assert(contains(labelDontDoIt, L"&"));
        m_buttonAffirmative->SetLabel(labelDoIt);
        m_buttonNegative   ->SetLabel(labelDontDoIt);

        //m_buttonAffirmative->SetId(wxID_IGNORE); -> setting id after button creation breaks "mouse snap to" functionality
        //m_buttonNegative   ->SetId(wxID_RETRY);  -> also wxWidgets docs seem to hide some info: "Normally, the identifier should be provided on creation and should not be modified subsequently."

        updateGui();

        //set std order after button visibility was set
        setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonAffirmative).setNegative(m_buttonNegative).setCancel(m_buttonCancel));
        setAsStandard(*m_buttonAffirmative);
        GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
        Center(); //needs to be re-applied after a dialog size change!
    }

private:
    void OnCheckBoxClick(wxCommandEvent& event) override { updateGui(); event.Skip(); }

    void updateGui()
    {
        switch (buttonToDisableWhenChecked_)
        {
            case ConfirmationButton3::DO_IT:
                m_buttonAffirmative->Enable(!m_checkBoxCustom->GetValue());
                break;
            case ConfirmationButton3::DONT_DO_IT:
                m_buttonNegative->Enable(!m_checkBoxCustom->GetValue());
                break;
            case ConfirmationButton3::CANCEL:
                break;
        }
    }

    const ConfirmationButton3 buttonToDisableWhenChecked_;
};

//########################################################################################

void zen::showNotificationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg)
{
    NotificationDialog dlg(parent, type, cfg);
    dlg.ShowModal();
}


ConfirmationButton zen::showConfirmationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg, const wxString& labelDoIt)
{
    ConfirmationDialog dlg(parent, type, cfg, labelDoIt);
    return static_cast<ConfirmationButton>(dlg.ShowModal());
}


ConfirmationButton3 zen::showConfirmationDialog3(wxWindow* parent, DialogInfoType type, const PopupDialogCfg3& cfg, const wxString& labelDoIt, const wxString& labelDontDoIt)
{
    ConfirmationDialog3 dlg(parent, type, cfg, labelDoIt, labelDontDoIt);
    return static_cast<ConfirmationButton3>(dlg.ShowModal());
}
