///////////////////////////////////////////////////////////////////////////////
// Name:        wx/private/webrequest.h
// Purpose:     wxWebRequest implementation classes
// Author:      Vadim Zeitlin
// Created:     2020-12-26
// Copyright:   (c) 2020 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

#ifndef _WX_PRIVATE_WEBREQUEST_H_
#define _WX_PRIVATE_WEBREQUEST_H_

#include "wx/ffile.h"

#include "wx/private/refcountermt.h"

#include <memory>
#include <unordered_map>

class WXDLLIMPEXP_FWD_BASE wxURI;

using wxWebRequestHeaderMap = std::unordered_map<wxString, wxString>;

// Trace mask used for the messages in wxWebRequest code.
#define wxTRACE_WEBREQUEST "webrequest"

// ----------------------------------------------------------------------------
// wxWebAuthChallengeImpl
// ----------------------------------------------------------------------------

class wxWebAuthChallengeImpl : public wxRefCounterMT
{
public:
    virtual ~wxWebAuthChallengeImpl() = default;

    wxWebAuthChallenge::Source GetSource() const { return m_source; }

    virtual void SetCredentials(const wxWebCredentials& cred) = 0;

protected:
    explicit wxWebAuthChallengeImpl(wxWebAuthChallenge::Source source)
        : m_source(source) { }

private:
    const wxWebAuthChallenge::Source m_source;

    wxDECLARE_NO_COPY_CLASS(wxWebAuthChallengeImpl);
};

// ----------------------------------------------------------------------------
// wxWebRequestImpl
// ----------------------------------------------------------------------------

class wxWebRequestImpl : public wxRefCounterMT
{
public:
    using Result = wxWebRequest::Result;

    // Return true if this is an async request, false if it's synchronous.
    bool IsAsync() const { return m_session != nullptr; }

    virtual ~wxWebRequestImpl() = default;

    void SetHeader(const wxString& name, const wxString& value)
    { m_headers[name] = value; }

    void SetMethod(const wxString& method) { m_method = method; }

    void SetData(const wxString& text, const wxString& contentType, const wxMBConv& conv = wxConvUTF8);

    bool SetData(std::unique_ptr<wxInputStream> dataStream, const wxString& contentType, wxFileOffset dataSize = wxInvalidOffset);

    void SetStorage(wxWebRequest::Storage storage) { m_storage = storage; }

    wxWebRequest::Storage GetStorage() const { return m_storage; }

    // This method is called to execute the request in a synchronous way.
    virtual Result Execute() = 0;

    // This method is called to start execution of an asynchronous request.
    //
    // Precondition for this method checked by caller: current state is idle.
    virtual void Start() = 0;

    // This method can be called to cancel execution of an asynchronous request.
    //
    // Precondition for this method checked by caller: not idle and not already
    // cancelled.
    void Cancel();

    virtual wxWebResponseImplPtr GetResponse() const = 0;

    virtual wxWebAuthChallengeImplPtr GetAuthChallenge() const = 0;

    int GetId() const { return m_id; }

    // This one is only valid for async requests.
    wxWebSession& GetSession() const { return *m_session; }

    // This one can be always called.
    wxWebSessionImpl& GetSessionImpl() const { return *m_sessionImpl; }

    wxWebRequest::State GetState() const { return m_state; }

    virtual wxFileOffset GetBytesSent() const = 0;

    virtual wxFileOffset GetBytesExpectedToSend() const = 0;

    virtual wxFileOffset GetBytesReceived() const;

    virtual wxFileOffset GetBytesExpectedToReceive() const;

    virtual wxWebRequestHandle GetNativeHandle() const = 0;

    void MakeInsecure(int flags) { m_securityFlags = flags; }

    int GetSecurityFlags() const { return m_securityFlags; }

    void SetState(wxWebRequest::State state, const wxString& failMsg = wxString());

    void ReportDataReceived(size_t sizeReceived);

    wxEvtHandler* GetHandler() const { return m_handler; }

protected:
    wxString m_method;
    wxWebRequest::Storage m_storage = wxWebRequest::Storage_Memory;
    wxWebRequestHeaderMap m_headers;
    wxFileOffset m_dataSize = 0;
    std::unique_ptr<wxInputStream> m_dataStream;
    int m_securityFlags = 0;

    // Ctor for async requests.
    wxWebRequestImpl(wxWebSession& session,
                     wxWebSessionImpl& sessionImpl,
                     wxEvtHandler* handler,
                     int id);

    // Ctor for sync requests.
    explicit wxWebRequestImpl(wxWebSessionImpl& sessionImpl);

    bool WasCancelled() const { return m_cancelled; }

    // Get the HTTP method to use: this will be m_method if it's non-empty,
    // POST is we have any data to send, and GET otherwise.
    //
    // Returned string is always in upper case.
    wxString GetHTTPMethod() const;

    // Get wxWebRequest::State and, optionally, error message corresponding to
    // the given response (response must be valid here).
    static Result GetResultFromHTTPStatus(const wxWebResponseImplPtr& response);

    // Call SetState() with either State_Failed or State_Completed appropriate
    // for the response status.
    void SetFinalStateFromStatus()
    {
        HandleResult(GetResultFromHTTPStatus(GetResponse()));
    }

    // Unconditionally call SetState() with the parameters corresponding to the
    // given result.
    void HandleResult(const Result& result)
    {
        SetState(result.state, result.error);
    }

    // Call SetState() if the result is an error (State_Failed) and return
    // false in this case, otherwise just return true.
    bool CheckResult(const Result& result)
    {
        if ( !result )
        {
            HandleResult(result);
            return false;
        }

        return true;
    }

private:
    // Called from public Cancel() at most once per object.
    virtual void DoCancel() = 0;

    // Called to notify about the state change in the main thread by SetState()
    // (which can itself be called from a different one).
    //
    // It also releases a reference added when switching to the active state by
    // SetState() when leaving it.
    void ProcessStateEvent(wxWebRequest::State state, const wxString& failMsg);

    // This is a shared pointer and not just a reference to ensure that the
    // session stays alive as long as there are any requests using it, as
    // allowing it to die first would result in a crash when destroying the
    // request later.
    wxWebSessionImplPtr m_sessionImpl;

    // These parameters are only valid for async requests.
    wxWebSession* const m_session;
    wxEvtHandler* const m_handler;
    const int m_id;
    wxWebRequest::State m_state = wxWebRequest::State_Idle;
    wxFileOffset m_bytesReceived = 0;
    wxCharBuffer m_dataText;

    // Initially false, set to true after the first call to Cancel().
    bool m_cancelled = false;

    wxDECLARE_NO_COPY_CLASS(wxWebRequestImpl);
};

// ----------------------------------------------------------------------------
// wxWebResponseImpl
// ----------------------------------------------------------------------------

class wxWebResponseImpl : public wxRefCounterMT
{
public:
    virtual ~wxWebResponseImpl();

    virtual wxFileOffset GetContentLength() const = 0;

    virtual wxString GetURL() const = 0;

    virtual wxString GetHeader(const wxString& name) const = 0;

    virtual std::vector<wxString> GetAllHeaderValues(const wxString& name) const = 0;

    virtual wxString GetMimeType() const;

    virtual wxString GetContentType() const;

    virtual int GetStatus() const = 0;

    virtual wxString GetStatusText() const = 0;

    virtual wxInputStream* GetStream() const;

    virtual wxString GetSuggestedFileName() const;

    wxString AsString() const;

    virtual wxString GetDataFile() const;

    // Open data file if necessary, i.e. if using wxWebRequest::Storage_File.
    //
    // Returns result with State_Failed if the file is needed but couldn't be
    // opened.
    wxNODISCARD wxWebRequest::Result InitFileStorage();

    void ReportDataReceived(size_t sizeReceived);

protected:
    wxWebRequestImpl& m_request;

    explicit wxWebResponseImpl(wxWebRequestImpl& request);

    void* GetDataBuffer(size_t sizeNeeded);

    // This function can optionally be called to preallocate the read buffer,
    // if the total amount of data to be downloaded is known in advance.
    void PreAllocBuffer(size_t sizeNeeded);

private:
    // Called by wxWebRequestImpl only.
    friend class wxWebRequestImpl;
    void Finalize();

    wxMemoryBuffer m_readBuffer;
    mutable wxFFile m_file;
    mutable std::unique_ptr<wxInputStream> m_stream;

    wxDECLARE_NO_COPY_CLASS(wxWebResponseImpl);
};

// ----------------------------------------------------------------------------
// wxWebSessionFactory
// ----------------------------------------------------------------------------

class wxWebSessionFactory
{
public:
    virtual wxWebSessionImpl* Create() = 0;
    virtual wxWebSessionImpl* CreateSync() = 0;

    virtual bool Initialize() { return true; }

    virtual ~wxWebSessionFactory() = default;
};

// ----------------------------------------------------------------------------
// wxWebSessionImpl
// ----------------------------------------------------------------------------

class wxWebSessionImpl : public wxRefCounterMT
{
public:
    // This session class can be used either synchronously or asynchronously,
    // but the mode must be chosen at the time of the object creation and
    // cannot be changed later.
    enum class Mode
    {
        Async,
        Sync
    };

    virtual ~wxWebSessionImpl();

    // Only one of these functions is actually implemented in async/sync
    // session implementation classes respectively. This is ugly, but allows to
    // add support for sync requests/sessions without completely rewriting
    // wxWebRequest code.

    virtual wxWebRequestImplPtr
    CreateRequest(wxWebSession& session,
                  wxEvtHandler* handler,
                  const wxString& url,
                  int id) = 0;

    virtual wxWebRequestImplPtr
    CreateRequestSync(wxWebSessionSync& session, const wxString& url) = 0;

    virtual wxVersionInfo GetLibraryVersionInfo() const = 0;

    bool SetBaseURL(const wxString& url);
    const wxURI* GetBaseURL() const;

    void AddCommonHeader(const wxString& name, const wxString& value)
        { m_headers[name] = value; }

    void SetTempDir(const wxString& dir) { m_tempDir = dir; }

    wxString GetTempDir() const;

    virtual bool SetProxy(const wxWebProxy& proxy)
        { m_proxy = proxy; return true; }
    const wxWebProxy& GetProxy() const { return m_proxy; }

    const wxWebRequestHeaderMap& GetHeaders() const { return m_headers; }

    virtual wxWebSessionHandle GetNativeHandle() const = 0;

    virtual bool EnablePersistentStorage(bool WXUNUSED(enable)) { return false; }

protected:
    explicit wxWebSessionImpl(Mode mode);

    bool IsAsync() const { return m_mode == Mode::Async; }

private:
    // Make it a friend to allow accessing our m_headers.
    friend class wxWebRequest;

    const Mode m_mode;

    std::unique_ptr<wxURI> m_baseURL;
    wxWebRequestHeaderMap m_headers;
    wxString m_tempDir;
    wxWebProxy m_proxy{wxWebProxy::Default()};


    wxDECLARE_NO_COPY_CLASS(wxWebSessionImpl);
};

#endif // _WX_PRIVATE_WEBREQUEST_H_
