/*
    This file is part of the KDE project.

    SPDX-FileCopyrightText: 2018 Stefano Crocco <stefano.crocco@alice.it>

    SPDX-License-Identifier: LGPL-2.1-or-later
*/

#ifndef WEBENGINE_KIOHANDLER_H
#define WEBENGINE_KIOHANDLER_H

#include <QWebEngineUrlSchemeHandler>
#include <QWebEngineUrlRequestJob>
#include <QPointer>
#include <QMimeType>

namespace KIO {
  class StoredTransferJob;  
};

namespace WebEngine {

/**
 * @brief Class which allows QWebEngine to access URLs provided by KIO
 * 
 * The class assumes that the data can be obtained from KIO using `KIO::storedGet` and
 * passes it to `QWebEngineUrlRequestJob::reply`. The mime type is determined using 
 * `QMimeDatabase::mimeTypeForData`.
 * 
 * This class provides a virtual function, processSlaveOutput() which allows to further process KIO output before
 * using it as reply. Derived classes can use it to change the data, its mimetype and to set 
 * an error code and error message.
 * 
 * @internal
 * 
 * Since requestStarted() can be called multiple times, requests are stored in a list and 
 * processed one by one. This is hidden to derived classes, which only work with the current request.
 */
class KIOHandler : public QWebEngineUrlSchemeHandler
{
    Q_OBJECT

public:
    /**
     * Constructor
     *
     * @param parent the parent object
     */
    KIOHandler(QObject* parent);
    
    /**
     * @brief Override of `QWebEngineUrlSchemeHandler::requestStarted`
     * 
     * It adds the new request to the list and start processing it if there aren't
     * other queued requests
     *
     * @param  req: the request job
     */
    void requestStarted(QWebEngineUrlRequestJob* req) override;

protected:

    using MaybeJobError = std::optional<QWebEngineUrlRequestJob::Error>;
    
    /**
    * @brief Creates and returns the html embedder object
    *
    * This is the object used to replace local URLs with their content as `data` URLs.
    *
    * The embedder is only created on the first use of this function. When this happens, its
    * WebEnginePartHtmlEmbedder::finished() signal is connected to the embedderFinished(). If you
    * don't want this, either disconnect the signal or reimplement embedderFinished() so that it
    * does nothing, then connect WebEnginePartHtmlEmbedder::finished() with the appropriate slot
    * yourself
    *
    * @return the html embedder
    */

    /**
     * @brief The request object
     * 
     * @return The request object
     */
    inline QPointer<QWebEngineUrlRequestJob> request() const {return m_currentRequest;}
    
    /**
     * @brief The error code to pass to `QWebEngineUrlRequestJob::fail`
     * 
     * In the base class implementation, this can be either `QWebEngineUrlRequestJob::RequestFailed`
     * or std::nullopt if the request succeeded
     * 
     * @return the error code to pass to `QWebEngineUrlRequestJob::fail`
     */
    inline MaybeJobError error() const {return m_error;}
    
    /**
     * @brief Sets the error code passed to `QWebEngineUrlRequestJob::fail`
     * 
     * Passing `std::nullopt` here will mark the request as successful.
     *
     * This function can be called by derived class in their implementation of processSlaveOutput().
     *
     * Note that there may be situations where the slave produces an error but the URL request should
     * be considered successful. This happens if, despite the failure, there's something to show the user
     * (for example, when the slave produced an error message which is more informative than the generic
     * message QtWebEngine would have produced). In this case, pass `std::nullopt` here.
     * 
     * @see isSuccessful()
     * 
     * @param error: the new error code. Pass `std::nullopt` to clear any previously set error code
     * and treat the request as successful
     */
    inline void setError(MaybeJobError error) {m_error = error;}
    
    /**
     * @brief Whether an error has occurred or not
     * 
     * @return `true` if error() is `std::nullopt` and false otherwise
     */
    inline bool isSuccessful() const {return !m_error.has_value();}
    
    /**
     * @brief The error message
     * 
     * Currently, this is only used for debugging purpose
     * 
     * @return The error message given by `KJob::errorString` (or set by derived classes using setErrorMessage())
     *  or an empty string if KIO produced no error.
     */
    inline QString errorMessage() const {return m_errorMessage;}
    
    /**
     * @brief Changes the error message
     * 
     * This function can be called by derived classes from their implementation of
     * processSlaveOutput().
     * 
     * @param message the new error message. If error() is not `QWebEngineUrlRequestJob::NoError`, this
     *  should not be empty
     */
    inline void setErrorMessage(const QString &message) {m_errorMessage = message;}
    
    /**
     * @brief The data to pass to `QWebEngineUrlRequestJob::reply`
     * 
     * This is the data returned by the ioslave, but can be changed in processSlaveOutput() using setData()
     * 
     * @return The data to pass to QWebEngineUrlRequestJob::reply
     */
    inline QByteArray data() const {return m_data;}
    
    /**
     * @brief Changes the data to pass to `QWebEngineUrlRequestJob::reply`
     * 
     * This function can be used by derived classes in their implementation of processSlaveOutput(). The base 
     * class implementation calls it for (X)HTML code to embed the content of local files in the code itself.
     * 
     * @param data: the new data
     */
    inline void setData(const QByteArray& data) {m_data = data;}
    
    /**
     * @brief The mime type of the data
     * 
     * This value (actually, it's `name`) will be passed to QWebEngineUrlRequestJob::reply
     * @return The mime type of the data
     */
    inline QMimeType mimeType() const {return m_mimeType;}
    
    /**
     * @brief Changes the mime type of the data
     * 
     * This function can be used by derived classes in their implementation of processSlaveOutput()
     * if the mime type according to `QMimeDatabase::mimeTypeForData` is not correct or if they change
     * the data so that its mimetype changes from what the ioslave returned.
     */
    inline void setMimeType(const QMimeType &mime) {m_mimeType = mime;}
    
    /**
     * @brief Processes the output from the ioslave and replies to the request
     * 
     * In the base class implementation, if the data is (X)HTML code (according to mimetype()), the
     * htmlEmbedder() is used to embed the content of local files in the code. In response to 
     * WebEnginePartHtmlEmbedder::finished(), the ready() signal is emitted. For other mimetypes, the
     * ready() signal is emitted directly from here.
     * 
     * Derived classes can reimplement this function to do their own processing, of the data produced
     * by the ioslave. At the beginning of this function, data(), mimetype(), and errorMessage() are 
     * those produced by the ioslave, while error is `QWebEngineUrlRequestJob::NoError` if the ioslave 
     * was successful andd `QWebEngineUrlRequestJob::RequestFailed` if there were errors. Derived classes can
     * change these values using setData(), setMimeType(), setError() and setErrorMessage().
     * 
     * It is important that, after having changed the values as desired, the ready() signal is emitted.
     */
    virtual void processSlaveOutput();
    
signals:
    
    /**
    * @brief Signal emitted when processing of data produced by the ioslave has finished
    * 
    * In response to this signal, sendReply() is called, sending the response to the original request
    */
    void ready();
    
private slots:
    
    /**
     * Slot called when the ioslave finishes
     * 
     * It reads the data, mimetype and error state from the job and stores it.
     * 
     * @param job: the finished ioslave job
     */
    
    void kioJobFinished(KIO::StoredTransferJob *job);
    
    /**
     * @brief Sends a reply to the `QWebEngineUrlRequestJob`
     * 
     * The reply is sent using the values returned by data(), mimeType(), error() and errorMessage().
     * 
     * If error() is something else from `QWebEngineUrlRequestJob::NoError`, this function calls
     * `QWebEngineUrlRequestJob::fail` with the corresponding error code, otherwise it calls 
     * `QWebEngineUrlRequestJob::reply()` passing data() and mimetype() as arguments.
     * 
     * This slot is called in response to the ready() signal
     */
    void sendReply();
    
private:
    
    /**
     * @brief Starts processing the next `QWebEngineUrlRequestJob` in queue
     * 
     * @internal
     * This function removes the first (valid) request from #m_queuedRequests,
     * stores it in #m_currentRequest and starts a `KIO::storedGet` for its URL
     */
    void processNextRequest();

    /**
     * @brief Creates a reply from the error string of the given job
     *
     * The reply is stored in m_data
     *
     * This function does nothing if the job didn't report an error or if the error string is empty
     * @param job the job
     */
    void createDataFromErrorString(KIO::StoredTransferJob *job);
    
    using RequestJobPointer = QPointer<QWebEngineUrlRequestJob>;
    
    /**
     * @brief A list of requests to be processed
     */
    QList<RequestJobPointer> m_queuedRequests;
    
    /**
     * @brief The request currently being processed
     */
    RequestJobPointer m_currentRequest;
    
    /**
     * @brief The error code to use for `QWebEngineUrlRequestJob::fail` for the current request
     *
     * A value of `std::nullopt` means no error occurred.
     * 
     * This is valid only after the call to kioJobFinished()
     */
    MaybeJobError m_error;
    
    /**
     * @brief The error message produced by KIO for the current request
     * 
     * Currently, this is only used for debugging purposes
     * 
     * This is valid only after the call to kioJobFinished()
     */
    QString m_errorMessage;
    
    /**
     * @brief The data produced by KIO for the current request
     * 
     * This is valid only after the call to kioJobFinished()
     */ 
    QByteArray m_data;
    
    /**
     * @brief The mime type of the data produced by KIO for the current request
     * 
     * This is valid only after the call to kioJobFinished()
     */ 
    QMimeType m_mimeType;
    
    /**
     * @brief The redirect URL produced by KIO for the current request, if any.
     *
     * This is valid only after the call to kioJobFinished()
     */
    QUrl m_redirectUrl;

};
}

#endif // WEBENGINE_KIOHANDLER_H
