/////////////////////////////////////////////////////////////////////////////
// Name:        samples/webrequest.cpp
// Purpose:     wxWebRequest Sample
// Author:      Tobias Taschner
// Created:     2018-10-15
// Copyright:   (c) 2018 wxWidgets development team
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

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

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include "wx/cmdline.h"
#include "wx/notebook.h"
#include "wx/artprov.h"
#include "wx/creddlg.h"
#include "wx/webrequest.h"
#include "wx/filedlg.h"
#include "wx/image.h"

#ifndef wxHAS_IMAGES_IN_RESOURCES
    #include "../sample.xpm"
#endif

#if !wxUSE_WEBREQUEST
#error "wxUSE_WEBREQUEST must be 1 for this sample."
#endif

class WebRequestFrame : public wxFrame
{
public:
    enum Pages
    {
        Page_Image,
        Page_Text,
        Page_Download,
        Page_Advanced
    };

    WebRequestFrame(const wxString& title, const wxString& url) :
        wxFrame(nullptr, wxID_ANY, title)
    {
        // set the frame icon
        SetIcon(wxICON(sample));

        Bind(wxEVT_CLOSE_WINDOW, &WebRequestFrame::OnClose, this);

        // Prepare UI controls
        wxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);

        mainSizer->Add(new wxStaticText(this, wxID_ANY, "Request URL:"),
            wxSizerFlags().Border());
        m_urlTextCtrl = new wxTextCtrl(this, wxID_ANY,
            url.empty() ? "https://www.wxwidgets.org/downloads/logos/blocks.png"
                        : url,
            wxDefaultPosition, wxDefaultSize,
            wxTE_PROCESS_ENTER);
        mainSizer->Add(m_urlTextCtrl,
            wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT));
        m_urlTextCtrl->Bind(wxEVT_TEXT_ENTER, &WebRequestFrame::OnStartButton, this);

        m_notebook = new wxNotebook(this, wxID_ANY);
        m_notebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &WebRequestFrame::OnNotebookPageChanged, this);

        // Image page
        wxPanel* imagePanel = new wxPanel(m_notebook);
        wxSizer* imageSizer = new wxBoxSizer(wxVERTICAL);

        m_imageStaticBitmap = new wxStaticBitmap(imagePanel,
            wxID_ANY, wxArtProvider::GetBitmap(wxART_MISSING_IMAGE));
        imageSizer->Add(m_imageStaticBitmap, wxSizerFlags(1).Expand());

        imagePanel->SetSizer(imageSizer);
        m_notebook->AddPage(imagePanel, "Image", true);

        // Text page
        wxPanel* textPanel = new wxPanel(m_notebook);
        wxSizer* textSizer = new wxBoxSizer(wxVERTICAL);

        m_postCheckBox = new wxCheckBox(textPanel, wxID_ANY, "Post request body");
        textSizer->Add(m_postCheckBox, wxSizerFlags().Border());
        m_postCheckBox->Bind(wxEVT_CHECKBOX, &WebRequestFrame::OnPostCheckBox, this);

        m_postRequestTextCtrl = new wxTextCtrl(textPanel, wxID_ANY,
            "app=WebRequestSample&version=1",
            wxDefaultPosition, wxSize(-1, FromDIP(60)), wxTE_MULTILINE);
        textSizer->Add(m_postRequestTextCtrl,
            wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT));

        textSizer->Add(new wxStaticText(textPanel, wxID_ANY, "Request body content type:"),
            wxSizerFlags().Border());
        m_postContentTypeTextCtrl = new wxTextCtrl(textPanel, wxID_ANY,
            "application/x-www-form-urlencoded");
        textSizer->Add(m_postContentTypeTextCtrl,
            wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT));

        textSizer->Add(new wxStaticText(textPanel, wxID_ANY, "Response body:"),
            wxSizerFlags().Border());
        m_textResponseTextCtrl = new wxTextCtrl(textPanel, wxID_ANY, "",
            wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
        m_textResponseTextCtrl->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
        textSizer->Add(m_textResponseTextCtrl,
            wxSizerFlags(1).Expand().Border(wxLEFT | wxRIGHT | wxBOTTOM));

        m_persistentStorageCheckBox = new wxCheckBox(textPanel, wxID_ANY, "Enable persistent storage");
        textSizer->Add(m_persistentStorageCheckBox, wxSizerFlags().Border());

        textPanel->SetSizer(textSizer);
        m_notebook->AddPage(textPanel, "Text");

        // Download page
        wxPanel* downloadPanel = new wxPanel(m_notebook);
        wxSizer* downloadSizer = new wxBoxSizer(wxVERTICAL);
        wxStaticText* downloadHeader = new wxStaticText(downloadPanel, wxID_ANY,
            "The URL will be downloaded to a file.\n"
            "Progress will be shown and you will be asked, where\n"
            "to save the file when the download completed.");
        downloadSizer->Add(downloadHeader, wxSizerFlags().Expand().Border());
        downloadSizer->AddStretchSpacer();
        m_downloadGauge = new wxGauge(downloadPanel, wxID_ANY, 100);
        downloadSizer->Add(m_downloadGauge, wxSizerFlags().Expand().Border());
        m_downloadStaticText = new wxStaticText(downloadPanel, wxID_ANY, "");
        downloadSizer->Add(m_downloadStaticText, wxSizerFlags().Expand().Border());

        downloadSizer->AddStretchSpacer();

        downloadPanel->SetSizer(downloadSizer);
        m_notebook->AddPage(downloadPanel, "Download");

        // Advanced page
        wxPanel* advancedPanel = new wxPanel(m_notebook);
        wxSizer* advSizer = new wxBoxSizer(wxVERTICAL);
        wxStaticText* advHeader = new wxStaticText(advancedPanel, wxID_ANY,
            "As an example of processing data while\n"
            "it's being received from the server, every\n"
            "zero byte in the response will be counted below.");
        advSizer->Add(advHeader, wxSizerFlags().Expand().Border());

        advSizer->AddStretchSpacer();
        m_advCountStaticText = new wxStaticText(advancedPanel, wxID_ANY, "0",
            wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL | wxST_NO_AUTORESIZE);
        m_advCountStaticText->SetFont(m_advCountStaticText->GetFont()
            .MakeBold().MakeLarger().MakeLarger());
        advSizer->Add(m_advCountStaticText, wxSizerFlags().Expand().Border());
        advSizer->AddStretchSpacer();

        advancedPanel->SetSizer(advSizer);

        m_notebook->AddPage(advancedPanel, "Advanced");

        mainSizer->Add(m_notebook, wxSizerFlags(1).Expand().Border());

        wxStdDialogButtonSizer* btnSizer = new wxStdDialogButtonSizer();
        m_cancelButton = new wxButton(this, wxID_CANCEL, "Cancel");
        m_cancelButton->Bind(wxEVT_BUTTON, &WebRequestFrame::OnCancelButton, this);
        m_cancelButton->Disable();
        btnSizer->AddButton(m_cancelButton);

        m_startButton = new wxButton(this, wxID_OK, "&Start Request");
        m_startButton->Bind(wxEVT_BUTTON, &WebRequestFrame::OnStartButton, this);
        btnSizer->AddButton(m_startButton);
        btnSizer->Realize();
        mainSizer->Add(btnSizer, wxSizerFlags().Expand().Border());

        wxCommandEvent evt;
        OnPostCheckBox(evt);

        SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
        SetSizer(mainSizer);

        SetSize(FromDIP(wxSize(540, 500)));

        CreateStatusBar();

        wxLogStatus(this, "%s", wxWebSession::GetDefault().GetLibraryVersionInfo().ToString());

        m_downloadProgressTimer.Bind(wxEVT_TIMER,
            &WebRequestFrame::OnProgressTimer, this);

        m_urlTextCtrl->SetFocus();
    }

    virtual ~WebRequestFrame()
    {
        // We have to block until the web request completes, but we need to
        // process events while doing it.
        Hide();

        while ( m_currentRequest.IsOk() )
        {
            wxYield();
        }
    }

    void OnStartButton(wxCommandEvent& WXUNUSED(evt))
    {
        wxLogStatus(this, "Started request...");

        if (m_notebook->GetSelection() == Page_Text && m_persistentStorageCheckBox->IsEnabled())
        {
            if (!wxWebSession::GetDefault().EnablePersistentStorage(m_persistentStorageCheckBox->IsChecked()) &&
                m_persistentStorageCheckBox->IsChecked())
                wxLogDebug("Persistent storage is not supported by the current backend");

            m_persistentStorageCheckBox->Disable();
        }

        // Create request for the specified URL from the default session
        m_currentRequest = wxWebSession::GetDefault().CreateRequest(this,
            m_urlTextCtrl->GetValue());

        // Bind event for state change
        Bind(wxEVT_WEBREQUEST_STATE, &WebRequestFrame::OnWebRequestState, this);

        // Prepare request based on selected action
        switch (m_notebook->GetSelection())
        {
            case Page_Image:
                // Reset static bitmap image
                m_imageStaticBitmap->SetBitmap(wxArtProvider::GetBitmap(wxART_MISSING_IMAGE));
                break;
            case Page_Text:
                // Reset response text control
                m_textResponseTextCtrl->Clear();

                // Set postdata if checked
                if ( m_postCheckBox->IsChecked() )
                {
                    m_currentRequest.SetData(m_postRequestTextCtrl->GetValue(),
                        m_postContentTypeTextCtrl->GetValue());
                }
                break;
            case Page_Download:
                m_currentRequest.SetStorage(wxWebRequest::Storage_File);
                m_downloadGauge->SetValue(0);
                m_downloadGauge->Pulse();
                m_downloadStaticText->SetLabel("");
                m_downloadProgressTimer.Start(500);
                SetStatusText("");
                break;
            case Page_Advanced:
                m_currentRequest.SetStorage(wxWebRequest::Storage_None);
                Bind(wxEVT_WEBREQUEST_DATA, &WebRequestFrame::OnRequestData, this);

                wxLogStatus(this, "Counting...");
                m_advCount = 0;
                m_advCountStaticText->SetLabel("0");
                break;
        }

        m_startButton->Disable();

        // Start the request (events will be sent on success or failure)
        m_currentRequest.Start();
    }

    void OnCancelButton(wxCommandEvent& WXUNUSED(evt))
    {
        if ( m_currentRequest.IsOk() )
            m_currentRequest.Cancel();
    }

    void OnWebRequestState(wxWebRequestEvent& evt)
    {
        m_startButton->Enable(evt.GetState() != wxWebRequest::State_Active);
        m_cancelButton->Enable(evt.GetState() == wxWebRequest::State_Active);

        bool stillActive = false;

        switch (evt.GetState())
        {
            case wxWebRequest::State_Completed:
                switch (m_notebook->GetSelection())
                {
                    case Page_Image:
                    {
                        wxImage img(*evt.GetResponse().GetStream());
                        m_imageStaticBitmap->SetBitmap(img);
                        m_notebook->GetPage(Page_Image)->Layout();
                        wxLogStatus(this, "Loaded %lld bytes image data", evt.GetResponse().GetContentLength());
                        break;
                    }
                    case Page_Text:
                        m_textResponseTextCtrl->SetValue(evt.GetResponse().AsString());
                        wxLogStatus(this, "Loaded %lld bytes text data (Status: %d %s)",
                            evt.GetResponse().GetContentLength(),
                            evt.GetResponse().GetStatus(),
                            evt.GetResponse().GetStatusText());
                        break;
                    case Page_Download:
                    {
                        m_downloadGauge->SetValue(100);
                        m_downloadStaticText->SetLabel("");

                        wxLogStatus(this, "Download completed");

                        // Ask the user where to save the file
                        wxFileDialog fileDlg(this, "Save download", "",
                            evt.GetResponse().GetSuggestedFileName(), "*.*",
                            wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
                        if ( fileDlg.ShowModal() == wxID_OK )
                        {
                            if ( !wxRenameFile(evt.GetDataFile(), fileDlg.GetPath()) )
                                wxLogError("Could not move file");
                        }

                        break;
                    }
                    case Page_Advanced:
                        UpdateAdvCount();
                        SetStatusText("");
                        break;
                }
                break;

            case wxWebRequest::State_Failed:
                wxLogError("Web Request failed: %s", evt.GetErrorDescription());
                SetStatusText("");
                break;

            case wxWebRequest::State_Cancelled:
                m_downloadGauge->SetValue(0);
                m_downloadStaticText->SetLabel("");
                wxLogStatus(this, "Cancelled");
                break;

            case wxWebRequest::State_Unauthorized:
                {
                    wxWebAuthChallenge
                        auth = m_currentRequest.GetAuthChallenge();
                    if ( !auth.IsOk() )
                    {
                        wxLogStatus("Unexpectedly missing auth challenge");
                        break;
                    }

                    wxCredentialEntryDialog dialog
                        (
                            this,
                            wxString::Format
                            (
                                "Please enter credentials for accessing\n"
                                "%s",
                                evt.GetResponse().GetURL()
                            ),
                            "wxWidgets web request sample",
                            m_credentials
                        );
                    if ( dialog.ShowModal() == wxID_OK )
                    {
                        m_credentials = dialog.GetCredentials();
                        auth.SetCredentials(m_credentials);
                        wxLogStatus("Trying to authenticate...");

                        stillActive = true;
                    }
                }
                break;

            case wxWebRequest::State_Active:
                stillActive = true;
                break;

            case wxWebRequest::State_Idle:
                // Nothing special to do for this state.
                break;
        }

        if ( !stillActive )
        {
            m_currentRequest = wxWebRequest();
            m_downloadProgressTimer.Stop();
        }
    }

    void OnRequestData(wxWebRequestEvent& evt)
    {
        // Count zero bytes in data buffer
        const char* p = (const char*) evt.GetDataBuffer();
        for ( size_t i = 0; i < evt.GetDataSize(); i++ )
        {
            if ( *p == 0 )
                m_advCount++;

            p++;
        }

        UpdateAdvCount();

        // Make sure the new text is immediately visible.
        m_advCountStaticText->Update();
    }

    void UpdateAdvCount()
    {
        m_advCountStaticText->SetLabel(wxString::Format("%lld", m_advCount));
    }

    void OnProgressTimer(wxTimerEvent& WXUNUSED(evt))
    {
        if ( !m_currentRequest.IsOk() || m_currentRequest.GetBytesExpectedToReceive() <= 0 )
            return;

        m_downloadGauge->SetValue((m_currentRequest.GetBytesReceived() * 100) /
            m_currentRequest.GetBytesExpectedToReceive());

        m_downloadStaticText->SetLabelText(wxString::Format("%lld/%lld",
            m_currentRequest.GetBytesReceived(), m_currentRequest.GetBytesExpectedToReceive()));
    }

    void OnPostCheckBox(wxCommandEvent& WXUNUSED(evt))
    {
        m_postContentTypeTextCtrl->Enable(m_postCheckBox->IsChecked());
        m_postRequestTextCtrl->Enable(m_postCheckBox->IsChecked());
        wxColour textBg = wxSystemSettings::GetColour(
            (m_postCheckBox->IsChecked()) ? wxSYS_COLOUR_WINDOW : wxSYS_COLOUR_BTNFACE);

        m_postContentTypeTextCtrl->SetBackgroundColour(textBg);
        m_postRequestTextCtrl->SetBackgroundColour(textBg);
    }

    void OnNotebookPageChanged(wxBookCtrlEvent& event)
    {
        wxString defaultURL;
        switch (event.GetSelection())
        {
        case Page_Image:
            defaultURL = "https://www.wxwidgets.org/downloads/logos/blocks.png";
            break;
        case Page_Text:
            defaultURL = "https://httpbin.org/post";
            break;
        case Page_Download:
            defaultURL = "https://github.com/wxWidgets/wxWidgets/releases/download/v3.1.1/wxWidgets-3.1.1.7z";
            break;
        case Page_Advanced:
            defaultURL = "https://httpbin.org/bytes/100000";
            break;
        }
        m_urlTextCtrl->SetValue(defaultURL);
    }

    void OnClose(wxCloseEvent& event)
    {
        if ( m_currentRequest.IsOk() )
        {
            if ( event.CanVeto() )
            {
                wxMessageDialog dialog
                    (
                        this,
                        "A web request is in progress, "
                        "closing the window will cancel it.",
                        "Please confirm",
                        wxYES_NO
                    );
                dialog.SetYesNoLabels("Cancel and close", "Don't close");

                if ( dialog.ShowModal() != wxID_YES )
                {
                    event.Veto();
                    return;
                }
            }

            m_currentRequest.Cancel();
        }

        event.Skip();
    }

private:
    wxNotebook* m_notebook;
    wxTextCtrl* m_urlTextCtrl;
    wxButton* m_startButton;
    wxButton* m_cancelButton;
    wxStaticBitmap* m_imageStaticBitmap;
    wxWebRequest m_currentRequest;

    wxCheckBox* m_postCheckBox;
    wxTextCtrl* m_postContentTypeTextCtrl;
    wxTextCtrl* m_postRequestTextCtrl;
    wxTextCtrl* m_textResponseTextCtrl;
    wxCheckBox* m_persistentStorageCheckBox;

    wxGauge* m_downloadGauge;
    wxStaticText* m_downloadStaticText;
    wxTimer m_downloadProgressTimer;

    wxStaticText* m_advCountStaticText;
    wxLongLong m_advCount;

    // Normally it would be a bad idea to permanently store credentials like
    // this, we should use wxSecretStore to load them as needed, but let's keep
    // things simple in this example.
    wxWebCredentials m_credentials;
};

class WebRequestApp : public wxApp
{
public:
    virtual bool OnInit() override
    {
        if ( !wxApp::OnInit() )
            return false;

        wxInitAllImageHandlers();

        // create the main application window
        WebRequestFrame *frame = new WebRequestFrame("wxWebRequest Sample App", m_url);
        frame->Show(true);

        return true;
    }

    virtual void OnInitCmdLine(wxCmdLineParser& parser) override
    {
        parser.AddParam("url", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);

        wxApp::OnInitCmdLine(parser);
    }

    virtual bool OnCmdLineParsed(wxCmdLineParser& parser) override
    {
        if ( !wxApp::OnCmdLineParsed(parser) )
            return false;

        if ( parser.GetParamCount() > 0 )
            m_url = parser.GetParam(0);

        return true;
    }

private:
    // URL specified on the command line, if any.
    wxString m_url;
};

wxIMPLEMENT_APP(WebRequestApp);
