/**********************************************************
 * Author(作者)     : Qt君
 * 微信公众号        : Qt君
 * Website(网站)    : qthub.com
 * QQ交流群         : 1039852727
 * Email(邮箱)      : 2088201923@qq.com
 * Support(技术支持&合作) :2088201923(QQ)
 * Source Code(源码): https://github.com/aeagean/QtNetworkService
 * LISCENSE(开源协议): MIT
 * Demo(演示):
 ==========================================================
   static AeaQt::HttpClient client;
   client.get("https://qthub.com")
         .onSuccess([](QString result) { qDebug()<<"success!"; })
         .onFailed([](QString error) { qDebug()<<"failed!"; })
         .exec();
 ==========================================================
**********************************************************/
#ifndef HTTPCLIENT_HPP
#define HTTPCLIENT_HPP

#include <QRegularExpression>
#include <QNetworkReply>
#include <QHttpMultiPart>
#include <QAuthenticator>

#include <QJsonObject>
#include <QJsonDocument>

#include <QBuffer>
#include <QMetaEnum>
#include <QUrlQuery>
#include <QFileInfo>

#include <QTimer>
#include <QEventLoop>
#include <QThread>
#include <QDebug>

#include <functional>

namespace AeaQt
{

class HttpClient;
class HttpRequest;
class HttpResponse;

class HttpClient : public QNetworkAccessManager
{
    Q_OBJECT
public:
    inline static HttpClient* instance();
    inline HttpClient(QObject* parent = nullptr);
    inline QString getVersion() const;

    inline HttpRequest head(const QString& url);
    inline HttpRequest get(const QString& url);
    inline HttpRequest post(const QString& url);
    inline HttpRequest put(const QString& url);

    inline HttpRequest send(const QString& url, Operation op = GetOperation);

    HttpClient* timeout(const int& second = 60)
    {
        timeoutSecond = second;
        return this;
    }
    int timeoutSecond = 60;

    HttpClient* header(const QString& key, const QVariant& value)
    {
        globalHeader.insert(key, value);
        return this;
    }
    QMap<QString, QVariant> globalHeader;

private:
#if (QT_VERSION < QT_VERSION_CHECK(5, 8, 0))
    inline QNetworkReply* sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb,
                                            const QByteArray& data);
    inline QNetworkReply* sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb,
                                            QHttpMultiPart* multiPart);
#endif
    friend class HttpRequest;
};

class HttpRequest
{
public:
    enum LogLevel
    {
        Off,
        Fatal,
        Error,
        Warn,
        Debug,
        Info,
        Trace,
        All
    };

    inline explicit HttpRequest(QNetworkAccessManager::Operation op, HttpClient* httpClient);
    inline virtual ~HttpRequest();

    inline HttpRequest& url(const QString& url);

    inline HttpRequest& header(const QString& key, const QVariant& value);
    inline HttpRequest& header(QNetworkRequest::KnownHeaders header, const QVariant& value);
    inline HttpRequest& headers(const QMap<QString, QVariant>& headers);
    inline HttpRequest& headers(const QMap<QNetworkRequest::KnownHeaders, QVariant>& headers);

    inline HttpRequest& queryParam(const QString& key, const QVariant& value);
    inline HttpRequest& queryParams(const QMap<QString, QVariant>& params);

    /* Mainly used for identification */
    inline HttpRequest& userAttribute(const QVariant& value);
    inline HttpRequest& attribute(QNetworkRequest::Attribute attribute, const QVariant& value);

    inline HttpRequest& body(const QByteArray& raw);
    inline HttpRequest& bodyWithRaw(const QByteArray& raw);

    inline HttpRequest& body(const QJsonObject& json);
    inline HttpRequest& bodyWithJson(const QJsonObject& json);

    inline HttpRequest& body(const QVariantMap& formUrlencodedMap);
    inline HttpRequest& bodyWithFormUrlencoded(const QString& key, const QVariant& value);
    inline HttpRequest& bodyWithFormUrlencoded(const QVariantMap& keyValueMap);

    inline HttpRequest& bodyWithFormData(const QString& key, const QVariant& value);
    inline HttpRequest& bodyWithFormData(const QVariantMap& keyValueMap);

    inline HttpRequest& body(QHttpMultiPart* multiPart);
    inline HttpRequest& bodyWithMultiPart(QHttpMultiPart* multiPart);

    // multi-params
    inline HttpRequest& body(const QString& key, const QString& file);
    inline HttpRequest& bodyWithFile(const QString& key, const QString& file);
    inline HttpRequest& bodyWithFile(const QMap<QString /*key*/, QString /*file*/>& fileMap);  // => QMap<key, file>;
    // like: { "key":
    // "/home/example/car.jpeg"
    // }

    inline HttpRequest& ignoreSslErrors(const QList<QSslError>& errors);
    inline HttpRequest& sslConfiguration(const QSslConfiguration& config);

    inline HttpRequest& priority(QNetworkRequest::Priority priority);
    inline HttpRequest& maximumRedirectsAllowed(int maxRedirectsAllowed);
    inline HttpRequest& originatingObject(QObject* object);
    inline HttpRequest& readBufferSize(qint64 size);

    // Authentication
    inline HttpRequest& autoAuthenticationRequired(const QAuthenticator& authenticator);
    inline HttpRequest& autoAuthenticationRequired(const QString& user, const QString& password);

    // 超过身份验证计数则触发失败并中断请求。
    // count >= 0 => count
    // count < 0 => infinite
    inline HttpRequest& authenticationRequiredCount(int count = 1);

    /**
   * @brief msec <= 0, disable timeout
   *        msec >  0, enable timeout
   */
    inline HttpRequest& timeout(const int& second = -1);
    inline HttpRequest& timeoutMs(const int& msec = -1);

    inline HttpRequest& download();
    inline HttpRequest& download(const QString& file);
    inline HttpRequest& enabledBreakpointDownload(bool enabled = true);

    inline HttpRequest& retry(int count);
    inline HttpRequest& repeat(int count);

    /**
   * @brief Block(or sync) current thread, entering an event loop.
   */
    inline HttpRequest& block(bool isBlock = true);
    inline HttpRequest& sync(bool isSync = true);

    inline HttpRequest& logLevel(LogLevel level = Warn);

    inline HttpResponse* exec();

    // onFinished == onSuccess
    inline HttpRequest& onSuccess(const QObject* receiver, const char* method);
    inline HttpRequest& onSuccess(std::function<void(QNetworkReply*)> lambda);
    inline HttpRequest& onSuccess(std::function<void(QVariantMap)> lambda);
    inline HttpRequest& onSuccess(std::function<void(QByteArray)> lambda);
    inline HttpRequest& onSuccess(std::function<void()> lambda);

    // onFinished == onSuccess
    inline HttpRequest& onFinished(const QObject* receiver, const char* method);
    inline HttpRequest& onFinished(std::function<void(QNetworkReply*)> lambda);
    inline HttpRequest& onFinished(std::function<void(QVariantMap)> lambda);
    inline HttpRequest& onFinished(std::function<void(QByteArray)> lambda);
    inline HttpRequest& onFinished(std::function<void()> lambda);

    // onError == onFailed
    inline HttpRequest& onError(const QObject* receiver, const char* method);
    inline HttpRequest& onError(std::function<void(QNetworkReply*)> lambda);
    inline HttpRequest& onError(std::function<void(QNetworkReply::NetworkError)> lambda);
    inline HttpRequest& onError(std::function<void(QByteArray)> lambda);
    inline HttpRequest& onError(std::function<void()> lambda);

    // onError == onFailed
    inline HttpRequest& onFailed(const QObject* receiver, const char* method);
    inline HttpRequest& onFailed(std::function<void(QNetworkReply*)> lambda);
    inline HttpRequest& onFailed(std::function<void(QNetworkReply::NetworkError)> lambda);
    inline HttpRequest& onFailed(std::function<void(QByteArray)> lambda);
    inline HttpRequest& onFailed(std::function<void()> lambda);

    inline HttpRequest& onReadyRead(const QObject* receiver, const char* method);
    inline HttpRequest& onReadyRead(std::function<void(QNetworkReply*)> lambda);

    inline HttpRequest& onHead(const QObject* receiver, const char* method);
    inline HttpRequest& onHead(std::function<void(QList<QNetworkReply::RawHeaderPair>)> lambda);
    inline HttpRequest& onHead(std::function<void(QMap<QString, QString>)> lambda);

    inline HttpRequest& onTimeout(const QObject* receiver, const char* method);
    inline HttpRequest& onTimeout(std::function<void(QNetworkReply*)> lambda);
    inline HttpRequest& onTimeout(std::function<void()> lambda);

    inline HttpRequest& onUploadProgress(const QObject* receiver, const char* method);
    inline HttpRequest& onUploadProgress(std::function<void(qint64, qint64)> lambda);

    inline HttpRequest& onDownloadProgress(const QObject* receiver, const char* method);
    inline HttpRequest& onDownloadProgress(std::function<void(qint64, qint64)> lambda);

    /// file download interface
    inline HttpRequest& onDownloadFileNameChanged(const QObject* receiver, const char* method);
    inline HttpRequest& onDownloadFileNameChanged(std::function<void(QString /*fileName*/)> lambda);

    inline HttpRequest& onDownloadFileProgress(const QObject* receiver, const char* method);
    inline HttpRequest& onDownloadFileProgress(std::function<void(qint64, qint64)> lambda);

    inline HttpRequest& onDownloadFileSuccess(const QObject* receiver, const char* method);
    inline HttpRequest& onDownloadFileSuccess(std::function<void()> lambda);
    inline HttpRequest& onDownloadFileSuccess(std::function<void(QString /*fileName*/)> lambda);

    inline HttpRequest& onDownloadFileFailed(const QObject* receiver, const char* method);
    inline HttpRequest& onDownloadFileFailed(std::function<void()> lambda);
    inline HttpRequest& onDownloadFileFailed(std::function<void(QString /*fileName*/)> lambda);
    /// file download interface

    inline HttpRequest& onRetried(const QObject* receiver, const char* method);
    inline HttpRequest& onRetried(std::function<void()> lambda);

    inline HttpRequest& onRepeated(const QObject* receiver, const char* method);
    inline HttpRequest& onRepeated(std::function<void()> lambda);

    inline HttpRequest& onAuthenticationRequired(const QObject* receiver, const char* method);
    inline HttpRequest& onAuthenticationRequired(std::function<void(QAuthenticator*)> lambda);

    inline HttpRequest& onAuthenticationRequireFailed(const QObject* receiver, const char* method);
    inline HttpRequest& onAuthenticationRequireFailed(std::function<void()> lambda);
    inline HttpRequest& onAuthenticationRequireFailed(std::function<void(QNetworkReply*)> lambda);

    // onResponse == onSuccess. note: DEPRECATED
    inline HttpRequest& onResponse(const QObject* receiver, const char* method);
    inline HttpRequest& onResponse(std::function<void(QNetworkReply*)> lambda);
    inline HttpRequest& onResponse(std::function<void(QVariantMap)> lambda);
    inline HttpRequest& onResponse(std::function<void(QByteArray)> lambda);

    enum HandleType
    {
        h_onFinished = 0,
        h_onError,
        h_onDownloadProgress,
        h_onUploadProgress,
        h_onTimeout,
        h_onReadyRead,
        h_onDownloadFileSuccess,
        h_onDownloadFileFailed,
        h_onEncrypted,
        h_onMetaDataChanged,
        h_onPreSharedKeyAuthenticationRequired,
        h_onRedirectAllowed,
        h_onRedirected,
        h_onSslErrors,
        h_onRetried,
        h_onRepeated,
        h_onAuthenticationRequired,
        h_onAuthenticationRequireFailed,
        h_onHead,
        h_onDownloadFileProgess,
        h_onDownloadFileNameChanged,
    };

    enum BodyType
    {
        None = 0,  // This request does not have a body.
        Raw,
        Raw_Json,               // application/json
        X_Www_Form_Urlencoded,  // x-www-form-urlencoded
        FileMap,                // multipart/form-data
        MultiPart,              // multipart/form-data
        FormData                // multipart/form-data
    };

protected:
    struct Downloader
    {
        bool isEnabled;
        QString fileName;
        bool enabledBreakpointDownload;
        bool isSupportBreakpointDownload;
        qint64 currentSize;
        qint64 totalSize;

        Downloader()
        {
            isEnabled = false;
            fileName = "";
            enabledBreakpointDownload = true;
            isSupportBreakpointDownload = false;
            totalSize = 0;
            currentSize = 0;
        }
    };

    QNetworkAccessManager::Operation m_op;
    HttpClient* m_httpClient = nullptr;
    QNetworkReply* m_reply = nullptr;
    QNetworkRequest m_request;

    QPair<BodyType, QVariant> m_body = qMakePair(BodyType::None, QByteArray());
    int m_timeoutMs = -1;  // ms
    bool m_isBlock = false;
    int m_retryCount = 0;
    bool m_enabledRetry = false;
    int m_repeatCount = 1;
    QAuthenticator m_authenticator;
    int m_authenticationRequiredCount = 1;

    qint64 m_readBufferSize = -1;
    QList<QSslError> m_ignoreSslErrors;
    Downloader m_downloader;
    QMap<HandleType, QList<QPair<QString, QVariant>>> m_handleMap;
    QVariantMap m_formUrlencodedMap;
    QVariantMap m_formDataMap;
    LogLevel m_logLevel = Warn;

protected:
    inline QString toString();

    inline HttpRequest& enabledRetry(bool isEnabled);
    inline HttpResponse* exec(const HttpRequest& httpRequest, HttpResponse* httpResponse = nullptr);

    friend class HttpResponse;
    friend class HttpDownloader;

private:
    inline HttpRequest() = delete;
    inline HttpRequest& onResponse(HandleType type, const QObject* receiver, const char* method);
    inline HttpRequest& onResponse(HandleType type, QVariant lambda);
    inline HttpRequest& onResponse(HandleType type, QString key, QVariant value);
};

class HttpResponse : public QObject
{
    Q_OBJECT
public:
    inline explicit HttpResponse(const HttpRequest& httpRequest, QObject* parent = nullptr);
    inline virtual ~HttpResponse();

    inline void setHttpRequest(const HttpRequest& httpRequest);
    inline QNetworkReply* reply()
    {
        return m_httpRequest.m_reply;
    }
    inline QString toString() const;
signals:
    void replyChanged(QNetworkReply* reply);
    void finished(QNetworkReply* reply);
    void finished();
    void finished(QByteArray result);
    void finished(QVariantMap resultMap);

    void downloadProgress(qint64, qint64);
    void uploadProgress(qint64, qint64);

    void error(QByteArray error);
    void error();
    void error(QNetworkReply::NetworkError error);
    void error(QNetworkReply* reply);

    void timeout();
    void timeout(QNetworkReply* reply);

    void readyRead(QNetworkReply* reply);

    void downloadFileNameChanged(QString fileName);

    void downloadFileFinished();
    void downloadFileFinished(QString file);

    void downloadFileError();
    void downloadFileError(QString errorString);

    void encrypted();
    void metaDataChanged();

    void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator* authenticator);
    void redirectAllowed();
    void redirected(QUrl url);
    void sslErrors(QList<QSslError> errors);

    void retried();
    void repeated();

    void authenticationRequired(QAuthenticator* authentication);
    void authenticationRequireFailed();
    void authenticationRequireFailed(QNetworkReply*);

    void head(QList<QNetworkReply::RawHeaderPair>);
    void head(QMap<QString, QString>);

    void downloadFileProgress(qint64 bytesReceived, qint64 bytesTotal);

private slots:
    inline void onFinished();
    inline void onError(QNetworkReply::NetworkError error);
    inline void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
    inline void onUploadProgress(qint64 bytesSent, qint64 bytesTotal);
    inline void onTimeout();
    inline void onReadyRead();
    inline void onReadOnceReplyHeader();

    inline void onEncrypted();
    inline void onMetaDataChanged();
    inline void onPreSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator* authenticator);
    inline void onRedirectAllowed();
    inline void onRedirected(const QUrl& url);
    inline void onSslErrors(const QList<QSslError>& errors);

    inline void onAuthenticationRequired(QNetworkReply* reply, QAuthenticator* authenticator);

    inline void onHandleHead();

private:
    HttpRequest m_httpRequest;
    QFile m_downloadFile;
    int m_retriesRemaining = 0;
    int m_authenticationCount = 0;
    bool m_isHandleHead = false;
};

inline QString lineIndent(const QString& source, const QString& indentString);
inline QString networkOperation2String(QNetworkAccessManager::Operation o);
inline QString networkHeader2String(const QNetworkRequest& request);
inline QString networkBody2String(const QPair<HttpRequest::BodyType, QVariant>& body);
inline QString networkReplyHeader2String(const QNetworkReply* reply);

class HttpDownloader : public QObject
{
    Q_OBJECT
    HttpRequest m_httpRequest;
    bool m_isSupportContinueDownload = false;
    QString m_fileName;
    qint64 m_contentLength = 0;
    QList<QNetworkReply::RawHeaderPair> m_headerForPair;
    QMap<QString, QString> m_headerForMap;
    HttpResponse* m_response = nullptr;

public:
    HttpDownloader(const HttpRequest& httpRequest, QObject* parent) : m_httpRequest(httpRequest), QObject(parent)
    {
        m_fileName = this->m_httpRequest.m_request.url().fileName();
        if (m_fileName.isEmpty())
        {
            m_fileName = this->m_httpRequest.m_request.url().host();
        }
    }

    virtual ~HttpDownloader()
    {
    }

    HttpResponse* exec()
    {
        HttpClient& client = *HttpClient::instance();
        client.get(m_httpRequest.m_request.url().toString())
            .timeout(30)
            .block(m_httpRequest.m_isBlock)
            .onHead(this, SLOT(onHead(QList<QNetworkReply::RawHeaderPair>)))
            .onHead(this, SLOT(onHead(QMap<QString, QString>)))
            .onReadyRead(this, SLOT(onReadyRead(QNetworkReply*)))
            .onFailed(this, SLOT(onResponse(QNetworkReply::NetworkError)))
            .exec();
        m_response = new HttpResponse(m_httpRequest, nullptr);
        return m_response;
    }

private slots:
    void onResponse(QNetworkReply::NetworkError)
    {
        HttpResponse* response = m_httpRequest.exec(m_httpRequest, m_response);
        this->setParent(response);
    }

    void onHead(QList<QNetworkReply::RawHeaderPair> headerForPair)
    {
        m_headerForPair = headerForPair;
    }

    void onHead(QMap<QString, QString> headerForMap)
    {
        m_headerForMap = headerForMap;
        for (auto each : m_headerForMap.toStdMap())
        {
            QString key = each.first;
            QString value = each.second;

            if (key.contains("Content-Disposition", Qt::CaseInsensitive))
            {
                QString dispositionHeader = value;
                // fixme rx

                QRegularExpression rx("attachment;\\s*filename=([\\S]+)");
                QRegularExpressionMatch match = rx.match(value);
                if (match.hasMatch())
                {
                    m_fileName = match.captured(1);
                }
            }

            if (key.contains("Content-Length", Qt::CaseInsensitive))
            {
                m_contentLength = value.toLongLong();
            }

            if (key.contains("Content-Range", Qt::CaseInsensitive) || key.contains("Accept-Ranges", Qt::CaseInsensitive))
            {
                m_isSupportContinueDownload = true;
            }
        }
    }

    void onReadyRead(QNetworkReply* reply)
    {
        HttpResponse* response = new HttpResponse(m_httpRequest, m_httpRequest.m_reply);
        if (m_httpRequest.m_downloader.fileName.isEmpty())
        {
            m_httpRequest.m_downloader.fileName = m_fileName;
            emit response->downloadFileNameChanged(m_fileName);
        }

        m_httpRequest.m_downloader.totalSize = m_contentLength;
        m_httpRequest.m_downloader.isSupportBreakpointDownload = m_isSupportContinueDownload;

        if (m_httpRequest.m_downloader.enabledBreakpointDownload && m_httpRequest.m_downloader.isSupportBreakpointDownload)
        {
            QFile file(m_httpRequest.m_downloader.fileName);
            if (file.exists() && file.open(QIODevice::ReadOnly))
            {
                m_httpRequest.m_downloader.currentSize = file.size();

                if (file.size() == m_contentLength)
                {
                    emit response->head(m_headerForPair);
                    emit response->head(m_headerForMap);

                    emit response->downloadFileProgress(m_contentLength, m_contentLength);

                    emit response->downloadFileFinished();
                    emit response->downloadFileFinished(m_httpRequest.m_downloader.fileName);

                    emit response->finished();
                    emit response->finished(QByteArray(""));
                    emit response->finished(QVariantMap{});
                    emit response->finished(nullptr);

                    response->deleteLater();
                }
                else if (file.size() > m_contentLength)
                {
                    file.close();
                    // Clear file content
                    file.open(QIODevice::Truncate);
                    file.close();
                }
                else
                {
                    m_httpRequest.m_request.setRawHeader("Range", QString("bytes=%1-").arg(file.size()).toUtf8());
                }
            }
        }

        response->deleteLater();
        reply->abort();
    }
};


class HttpResponseTimeout : public QObject
{
    Q_OBJECT

private:
    HttpResponse* _parent;
    int _timeout;
    Q_SLOT void handleTimeOut(){
        QTimer::singleShot(_timeout, _parent, SLOT(onTimeout()));
    }
public:
    HttpResponseTimeout(HttpResponse* parent, const int timeout = -1) : QObject(parent)
    {
        _timeout = timeout;
        _parent = parent;
        if (timeout > 0)
        {
            QMetaObject::invokeMethod(this, "handleTimeOut",Qt::QueuedConnection);
        }
        else
        {
            // do nothing
        }
    }
};

class HttpBlocker : public QEventLoop
{
    Q_OBJECT
public:
    HttpBlocker(QNetworkReply* reply, bool isBlock) : QEventLoop(reply)
    {
        if (isBlock)
        {
            connect(reply, SIGNAL(finished()), this, SLOT(quit()));
            this->exec();
        }
    }
};

#define _logger(l1, l2, str)                                                                                           \
do                                                                                                                   \
    {                                                                                                                    \
            if (l1 >= l2)                                                                                                      \
        {                                                                                                                  \
                if (l2 >= HttpRequest::Debug)                                                                                    \
            {                                                                                                                \
                    qDebug().noquote() << str;                                                                                     \
            }                                                                                                                \
                else if (l2 == HttpRequest::Warn)                                                                                \
            {                                                                                                                \
                    qWarning().noquote() << str;                                                                                   \
            }                                                                                                                \
                else if (l2 == HttpRequest::Error)                                                                               \
            {                                                                                                                \
                    qCritical().noquote() << str;                                                                                  \
            }                                                                                                                \
                else if (l2 == HttpRequest::Fatal)                                                                               \
            {                                                                                                                \
                    qFatal("%s\n", str);                                                                                           \
            }                                                                                                                \
        }                                                                                                                  \
    } while (0);

#define printTrace(level, str) _logger(level, HttpRequest::Trace, str)
#define printInfo(level, str) _logger(level, HttpRequest::Info, str)
#define printDebug(level, str) _logger(level, HttpRequest::Debug, str)
#define printWarn(level, str) _logger(level, HttpRequest::Warn, str)
#define printError(level, str) _logger(level, HttpRequest::Error, str)
#define printFatal(level, str) _logger(level, HttpRequest::Fatal, str)

HttpRequest::~HttpRequest()
{
}

HttpRequest::HttpRequest(QNetworkAccessManager::Operation op, HttpClient* httpClient)
{
    m_op = op;
    m_httpClient = httpClient;
}

HttpRequest& HttpRequest::url(const QString& url)
{
    m_request.setUrl(QUrl(url));
    return *this;
}

HttpRequest& HttpRequest::header(QNetworkRequest::KnownHeaders header, const QVariant& value)
{
    m_request.setHeader(header, value);
    return *this;
}

HttpRequest& HttpRequest::headers(const QMap<QNetworkRequest::KnownHeaders, QVariant>& headers)
{
    QMapIterator<QNetworkRequest::KnownHeaders, QVariant> iter(headers);
    while (iter.hasNext())
    {
        iter.next();
        header(iter.key(), iter.value());
    }

    return *this;
}

HttpRequest& HttpRequest::header(const QString& key, const QVariant& value)
{
    m_request.setRawHeader(QByteArray(key.toStdString().data()), QByteArray(value.toString().toStdString().data()));
    return *this;
}

HttpRequest& HttpRequest::headers(const QMap<QString, QVariant>& headers)
{
    QMapIterator<QString, QVariant> iter(headers);
    while (iter.hasNext())
    {
        iter.next();
        header(iter.key(), iter.value());
    }

    return *this;
}

HttpRequest& HttpRequest::body(const QVariantMap& keyValueMap)
{
    return bodyWithFormUrlencoded(keyValueMap);
}

HttpRequest& HttpRequest::bodyWithFormUrlencoded(const QString& key, const QVariant& value)
{
    QVariantMap map;
    map[key] = value;

    return bodyWithFormUrlencoded(map);
}

HttpRequest& HttpRequest::bodyWithFormUrlencoded(const QVariantMap& keyValueMap)
{
    // merge map
    for (auto each : keyValueMap.toStdMap())
    {
        m_formUrlencodedMap[each.first] = each.second;
    }

    QUrl url;
    QUrlQuery urlQuery(url);
    QMapIterator<QString, QVariant> i(m_formUrlencodedMap);
    while (i.hasNext())
    {
        i.next();
        urlQuery.addQueryItem(i.key(), i.value().toString());
    }

    url.setQuery(urlQuery);
    const QString& value = url.toString(QUrl::FullyEncoded).toUtf8().remove(0, 1);

    m_body = qMakePair(X_Www_Form_Urlencoded, value);
    m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");

    return *this;
}

HttpRequest& HttpRequest::bodyWithFormData(const QString& key, const QVariant& value)
{
    QVariantMap map;
    map[key] = value;

    return bodyWithFormData(map);
}

HttpRequest& HttpRequest::bodyWithFormData(const QVariantMap& keyValueMap)
{
    // merge map
    for (auto each : keyValueMap.toStdMap())
    {
        m_formDataMap[each.first] = each.second;
    }

    m_body = qMakePair(FormData, m_formDataMap);
    m_request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data");
    return *this;
}

HttpRequest& HttpRequest::body(const QJsonObject& json)
{
    return bodyWithJson(json);
}

HttpRequest& HttpRequest::bodyWithJson(const QJsonObject& json)
{
    const QByteArray& value = QJsonDocument(json).toJson();
    m_body = qMakePair(Raw_Json, value);
    m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    return *this;
}

HttpRequest& HttpRequest::body(const QByteArray& raw)
{
    return bodyWithRaw(raw);
}

HttpRequest& HttpRequest::bodyWithRaw(const QByteArray& raw)
{
    m_body = qMakePair(Raw, raw);
    return *this;
}

HttpRequest& HttpRequest::body(QHttpMultiPart* multiPart)
{
    return bodyWithMultiPart(multiPart);
}

HttpRequest& HttpRequest::bodyWithMultiPart(QHttpMultiPart* multiPart)
{
    m_body = qMakePair(MultiPart, QVariant::fromValue(multiPart));
    return *this;
}

HttpRequest& HttpRequest::download()
{
    return download("");
}

HttpRequest& HttpRequest::download(const QString& file)
{
    m_downloader.isEnabled = true;
    m_downloader.fileName = file;
    return *this;
}

HttpRequest& HttpRequest::enabledBreakpointDownload(bool enabled)
{
    m_downloader.enabledBreakpointDownload = enabled;
    return *this;
}

HttpRequest& HttpRequest::onDownloadFileProgress(const QObject* receiver, const char* method)
{
    return onResponse(h_onDownloadFileProgess, receiver, method);
}

HttpRequest& HttpRequest::onDownloadFileProgress(std::function<void(qint64, qint64)> lambda)
{
    return onResponse(h_onDownloadFileProgess, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::body(const QString& key, const QString& file)
{
    return bodyWithFile(key, file);
}

HttpRequest& HttpRequest::bodyWithFile(const QString& key, const QString& filePath)
{
    QMap<QString, QString> map;
    map[key] = filePath;

    return bodyWithFile(map);
}

HttpRequest& HttpRequest::bodyWithFile(const QMap<QString, QString>& fileMap)
{
    auto& body = m_body;
    auto map = body.second.value<QMap<QString, QString>>();
    for (auto each : fileMap.toStdMap())
    {
        map[each.first] = each.second;
    }

    body.first = BodyType::FileMap;
    body.second = QVariant::fromValue(map);
    return *this;
}

HttpRequest& HttpRequest::ignoreSslErrors(const QList<QSslError>& errors)
{
    m_ignoreSslErrors = errors;
    return *this;
}

HttpRequest& HttpRequest::sslConfiguration(const QSslConfiguration& config)
{
    m_request.setSslConfiguration(config);
    return *this;
}

HttpRequest& HttpRequest::priority(QNetworkRequest::Priority priority)
{
    m_request.setPriority(priority);
    return *this;
}

HttpRequest& HttpRequest::maximumRedirectsAllowed(int maxRedirectsAllowed)
{
    m_request.setMaximumRedirectsAllowed(maxRedirectsAllowed);
    return *this;
}

HttpRequest& HttpRequest::originatingObject(QObject* object)
{
    m_request.setOriginatingObject(object);
    return *this;
}

HttpRequest& HttpRequest::readBufferSize(qint64 size)
{
    m_readBufferSize = size;
    return *this;
}

HttpRequest& HttpRequest::autoAuthenticationRequired(const QAuthenticator& authenticator)
{
    m_authenticator = authenticator;
    return *this;
}

HttpRequest& HttpRequest::autoAuthenticationRequired(const QString& user, const QString& password)
{
    QAuthenticator a;
    a.setUser(user);
    a.setPassword(password);

    return autoAuthenticationRequired(a);
}

HttpRequest& HttpRequest::authenticationRequiredCount(int count)
{
    m_authenticationRequiredCount = count;
    return *this;
}

HttpRequest& HttpRequest::timeout(const int& second)
{
    return timeoutMs(second * 1000);
}
HttpRequest& HttpRequest::timeoutMs(const int& msec)
{
    m_timeoutMs = msec;
    return *this;
}

HttpRequest& HttpRequest::retry(int count)
{
    m_retryCount = count;
    return *this;
}

HttpRequest& HttpRequest::repeat(int count)
{
    m_repeatCount = count;
    return *this;
}

HttpRequest& HttpRequest::block(bool isBlock)
{
    m_isBlock = isBlock;
    return *this;
}
HttpRequest& HttpRequest::sync(bool isSync)
{
    return block(isSync);
}

HttpRequest& HttpRequest::logLevel(HttpRequest::LogLevel level)
{
    m_logLevel = level;
    return *this;
}

HttpRequest& HttpRequest::onFinished(const QObject* receiver, const char* method)
{
    return onResponse(h_onFinished, receiver, method);
}
HttpRequest& HttpRequest::onFinished(std::function<void(QNetworkReply*)> lambda)
{
    return onResponse(h_onFinished, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onFinished(std::function<void(QVariantMap)> lambda)
{
    return onResponse(h_onFinished, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onFinished(std::function<void(QByteArray)> lambda)
{
    return onResponse(h_onFinished, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onFinished(std::function<void()> lambda)
{
    return onResponse(h_onFinished, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onSuccess(const QObject* receiver, const char* method)
{
    return onFinished(receiver, method);
}
HttpRequest& HttpRequest::onSuccess(std::function<void(QNetworkReply*)> lambda)
{
    return onFinished(lambda);
}
HttpRequest& HttpRequest::onSuccess(std::function<void(QVariantMap)> lambda)
{
    return onFinished(lambda);
}
HttpRequest& HttpRequest::onSuccess(std::function<void(QByteArray)> lambda)
{
    return onFinished(lambda);
}
HttpRequest& HttpRequest::onSuccess(std::function<void()> lambda)
{
    return onFinished(lambda);
}

HttpRequest& HttpRequest::onFailed(const QObject* receiver, const char* method)
{
    return onError(receiver, method);
}
HttpRequest& HttpRequest::onFailed(std::function<void(QNetworkReply*)> lambda)
{
    return onError(lambda);
}
HttpRequest& HttpRequest::onFailed(std::function<void(QNetworkReply::NetworkError)> lambda)
{
    return onError(lambda);
}
HttpRequest& HttpRequest::onFailed(std::function<void(QByteArray)> lambda)
{
    return onError(lambda);
}
HttpRequest& HttpRequest::onFailed(std::function<void()> lambda)
{
    return onError(lambda);
}

HttpRequest& HttpRequest::onError(const QObject* receiver, const char* method)
{
    return onResponse(h_onError, receiver, method);
}
HttpRequest& HttpRequest::onError(std::function<void(QNetworkReply*)> lambda)
{
    return onResponse(h_onError, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onError(std::function<void(QNetworkReply::NetworkError)> lambda)
{
    return onResponse(h_onError, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onError(std::function<void(QByteArray)> lambda)
{
    return onResponse(h_onError, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onError(std::function<void()> lambda)
{
    return onResponse(h_onError, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onReadyRead(const QObject* receiver, const char* method)
{
    return onResponse(h_onReadyRead, receiver, method);
}
HttpRequest& HttpRequest::onReadyRead(std::function<void(QNetworkReply*)> lambda)
{
    return onResponse(h_onReadyRead, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onHead(const QObject* receiver, const char* method)
{
    return onResponse(h_onHead, receiver, method);
}
HttpRequest& HttpRequest::onHead(std::function<void(QList<QNetworkReply::RawHeaderPair>)> lambda)
{
    return onResponse(h_onHead, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onHead(std::function<void(QMap<QString, QString>)> lambda)
{
    return onResponse(h_onHead, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onDownloadProgress(const QObject* receiver, const char* method)
{
    return onResponse(h_onDownloadProgress, receiver, method);
}
HttpRequest& HttpRequest::onDownloadProgress(std::function<void(qint64, qint64)> lambda)
{
    return onResponse(h_onDownloadProgress, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onDownloadFileNameChanged(const QObject* receiver, const char* method)
{
    return onResponse(h_onDownloadFileNameChanged, receiver, method);
}
HttpRequest& HttpRequest::onDownloadFileNameChanged(std::function<void(QString)> lambda)
{
    return onResponse(h_onDownloadFileNameChanged, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onDownloadFileSuccess(const QObject* receiver, const char* method)
{
    return onResponse(h_onDownloadFileSuccess, receiver, method);
}
HttpRequest& HttpRequest::onDownloadFileSuccess(std::function<void()> lambda)
{
    return onResponse(h_onDownloadFileSuccess, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onDownloadFileSuccess(std::function<void(QString)> lambda)
{
    return onResponse(h_onDownloadFileSuccess, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onDownloadFileFailed(const QObject* receiver, const char* method)
{
    return onResponse(h_onDownloadFileFailed, receiver, method);
}
HttpRequest& HttpRequest::onDownloadFileFailed(std::function<void()> lambda)
{
    return onResponse(h_onDownloadFileFailed, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onDownloadFileFailed(std::function<void(QString)> lambda)
{
    return onResponse(h_onDownloadFileFailed, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onUploadProgress(const QObject* receiver, const char* method)
{
    return onResponse(h_onUploadProgress, receiver, method);
}
HttpRequest& HttpRequest::onUploadProgress(std::function<void(qint64, qint64)> lambda)
{
    return onResponse(h_onUploadProgress, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onTimeout(const QObject* receiver, const char* method)
{
    return onResponse(h_onTimeout, receiver, method);
}
HttpRequest& HttpRequest::onTimeout(std::function<void(QNetworkReply*)> lambda)
{
    return onResponse(h_onTimeout, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onTimeout(std::function<void()> lambda)
{
    return onResponse(h_onTimeout, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onRetried(const QObject* receiver, const char* method)
{
    return onResponse(h_onRetried, receiver, method);
}
HttpRequest& HttpRequest::onRetried(std::function<void()> lambda)
{
    return onResponse(h_onRetried, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onRepeated(const QObject* receiver, const char* method)
{
    return onResponse(h_onRepeated, receiver, method);
}
HttpRequest& HttpRequest::onRepeated(std::function<void()> lambda)
{
    return onResponse(h_onRepeated, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onAuthenticationRequired(const QObject* receiver, const char* method)
{
    return onResponse(h_onAuthenticationRequired, receiver, method);
}
HttpRequest& HttpRequest::onAuthenticationRequired(std::function<void(QAuthenticator*)> lambda)
{
    return onResponse(h_onAuthenticationRequired, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onAuthenticationRequireFailed(const QObject* receiver, const char* method)
{
    return onResponse(h_onAuthenticationRequireFailed, receiver, method);
}
HttpRequest& HttpRequest::onAuthenticationRequireFailed(std::function<void()> lambda)
{
    return onResponse(h_onAuthenticationRequireFailed, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onAuthenticationRequireFailed(std::function<void(QNetworkReply*)> lambda)
{
    return onResponse(h_onAuthenticationRequireFailed, QVariant::fromValue(lambda));
}

HttpRequest& HttpRequest::onResponse(const QObject* receiver, const char* method)
{
    return onResponse(h_onFinished, receiver, method);
}
HttpRequest& HttpRequest::onResponse(std::function<void(QNetworkReply*)> lambda)
{
    return onResponse(h_onFinished, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onResponse(std::function<void(QVariantMap)> lambda)
{
    return onResponse(h_onFinished, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onResponse(std::function<void(QByteArray)> lambda)
{
    return onResponse(h_onFinished, QVariant::fromValue(lambda));
}
HttpRequest& HttpRequest::onResponse(HandleType type, QVariant lambda)
{
    return onResponse(type, lambda.typeName(), lambda);
}
HttpRequest& HttpRequest::onResponse(HandleType type, const QObject* receiver, const char* method)
{
    return onResponse(type, method, QVariant::fromValue((QObject*)receiver));
}
HttpRequest& HttpRequest::onResponse(HandleType type, QString key, QVariant value)
{
    if (!m_handleMap.contains(type))
    {
        QList<QPair<QString, QVariant>> handleList;
        m_handleMap.insert(type, handleList);
    }

    auto handleList = m_handleMap[type];
    handleList.append({ key, value });

    m_handleMap.insert(type, handleList);
    return *this;
}

QString HttpRequest::toString()
{
    QString str =
        "General: \n"
        "    Request URL: %{url} \n"
        "    Request Method: %{method} \n"
        "Request Headers: \n"
        "%{requestHeaders} \n"
        "Request Body: \n"
        "%{requestBody}";

    str.replace("%{url}", m_request.url().toString());
    str.replace("%{method}", networkOperation2String(m_op));
    str.replace("%{requestHeaders}", lineIndent(networkHeader2String(m_request), "    "));
    str.replace("%{requestBody}", lineIndent(networkBody2String(m_body), "    "));

    return str;
}

HttpResponse* HttpRequest::exec(const HttpRequest& _httpRequest, HttpResponse* httpResponse)
{
    HttpRequest httpRequest = _httpRequest;

    QByteArray op = networkOperation2String(httpRequest.m_op).toUtf8();
    if (op.isEmpty())
    {
        QString str =
            QString("Url: [%1]; Method: [%2] not support!").arg(httpRequest.m_request.url().toString()).arg(QString(op));
        printError(httpRequest.m_logLevel, str.toStdString().c_str());
        return nullptr;
    }

    using BodyType = HttpRequest::BodyType;
    BodyType bodyType = httpRequest.m_body.first;
    QVariant body = httpRequest.m_body.second;
    QNetworkRequest request = httpRequest.m_request;
    HttpClient* httpClient = httpRequest.m_httpClient;

    if (bodyType == BodyType::MultiPart)
    {
        QHttpMultiPart* multiPart = body.value<QHttpMultiPart*>();
        QString contentType = QString("multipart/form-data;boundary=%1").arg(multiPart->boundary().data());

        request.setHeader(QNetworkRequest::ContentTypeHeader, contentType);

        httpRequest.m_reply = httpRequest.m_httpClient->sendCustomRequest(request, op, multiPart);
        multiPart->setParent(httpRequest.m_reply);
    }
    else if (bodyType == BodyType::FileMap)
    {
        QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
        QString contentType = QString("multipart/form-data;boundary=%1").arg(multiPart->boundary().data());
        request.setHeader(QNetworkRequest::ContentTypeHeader, contentType);

        const auto& fileMap = body.value<QMap<QString, QString>>();
        for (const auto& each : fileMap.toStdMap())
        {
            const QString& key = each.first;
            const QString& filePath = each.second;

            QFile* file = new QFile(filePath);
            file->open(QIODevice::ReadOnly);
            file->setParent(multiPart);

            // todo
            // part.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain"));

            // note: "form-data; name=\"%1\";filename=\"%2\"" != "form-data; name=\"%1\";filename=\"%2\";"
            QString dispositionHeader =
                QString("form-data; name=\"%1\";filename=\"%2\"").arg(key).arg(QFileInfo(filePath).fileName());
            QHttpPart part;
            part.setHeader(QNetworkRequest::ContentDispositionHeader, dispositionHeader);
            part.setBodyDevice(file);

            multiPart->append(part);
        }

        httpRequest.m_reply = httpClient->sendCustomRequest(request, op, multiPart);
        if (httpRequest.m_reply)
            multiPart->setParent(httpRequest.m_reply);
        else
            delete multiPart;
    }
    else if (bodyType == BodyType::FormData)
    {
        QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
        QString contentType = QString("multipart/form-data;boundary=%1").arg(multiPart->boundary().data());
        request.setHeader(QNetworkRequest::ContentTypeHeader, contentType);

        const auto& formDataMap = body.value<QMap<QString, QVariant>>();
        for (const auto& each : formDataMap.toStdMap())
        {
            const QString& key = each.first;
            const QString& value = each.second.toString();

            QString dispositionHeader = QString("form-data; name=\"%1\"").arg(key);

            QHttpPart part;
            part.setHeader(QNetworkRequest::ContentDispositionHeader, dispositionHeader);
            part.setBody(value.toUtf8());

            multiPart->append(part);
        }

        httpRequest.m_reply = httpClient->sendCustomRequest(request, op, multiPart);

        if (httpRequest.m_reply)
            multiPart->setParent(httpRequest.m_reply);
        else
            delete multiPart;
    }
    else
    {
        httpRequest.m_reply = httpClient->sendCustomRequest(request, op, body.toByteArray());
    }

    if (httpRequest.m_reply == nullptr)
    {
        // fixme: todo onError
        printError(httpRequest.m_logLevel, "Http reply invalid");
        Q_ASSERT(httpRequest.m_reply);
        return nullptr;
    }

    // fixme
    if (!httpRequest.m_ignoreSslErrors.isEmpty())
    {
        httpRequest.m_reply->ignoreSslErrors(httpRequest.m_ignoreSslErrors);
    }

    if (httpRequest.m_readBufferSize >= 0)
    {
        httpRequest.m_reply->setReadBufferSize(httpRequest.m_readBufferSize);
    }

    printDebug(httpRequest.m_logLevel, toString().toStdString().c_str());

    if (httpResponse)
    {
        httpResponse->setParent(httpRequest.m_reply);
        httpResponse->setHttpRequest(httpRequest);
        return httpResponse;
    }
    else
    {
        return new HttpResponse(httpRequest, httpRequest.m_reply);
    }
}

inline QDebug operator<<(QDebug debug, const QNetworkAccessManager::Operation& op)
{
    QDebugStateSaver saver(debug);
    debug.nospace();

    switch (op)
    {
    case QNetworkAccessManager::HeadOperation:
        return debug << "HeadOperation";
    case QNetworkAccessManager::GetOperation:
        return debug << "GetOperation";
    case QNetworkAccessManager::PostOperation:
        return debug << "PostOperation";
    case QNetworkAccessManager::PutOperation:
        return debug << "PutOperation";
    case QNetworkAccessManager::DeleteOperation:
        return debug << "DeleteOperation";
    case QNetworkAccessManager::CustomOperation:
        return debug << "CustomOperation";
    default:
        return debug << "UnknownOperation";
    }
}

inline QDebug operator<<(QDebug debug, const HttpRequest::HandleType& handleType)
{
    QDebugStateSaver saver(debug);
    debug.nospace();

    switch (handleType)
    {
    case HttpRequest::h_onFinished:
        return debug << "onFinished";
    case HttpRequest::h_onError:
        return debug << "onError";
    case HttpRequest::h_onDownloadProgress:
        return debug << "onDownloadProgress";
    case HttpRequest::h_onUploadProgress:
        return debug << "onUploadProgress";
        // todo: onUploadProgressSuccess and onUploadProgressFaied
    case HttpRequest::h_onDownloadFileSuccess:
        return debug << "onDownloadFileSuccess";
    case HttpRequest::h_onDownloadFileFailed:
        return debug << "onDownloadFileFailed";
    case HttpRequest::h_onTimeout:
        return debug << "onTimeout";
    case HttpRequest::h_onReadyRead:
        return debug << "onReadyRead";
    case HttpRequest::h_onEncrypted:
        return debug << "onEncrypted";
    case HttpRequest::h_onMetaDataChanged:
        return debug << "onMetaChanged";
    case HttpRequest::h_onPreSharedKeyAuthenticationRequired:
        return debug << "onPreSharedKeyAuthenticationRequired";
    case HttpRequest::h_onRedirectAllowed:
        return debug << "onRedirectAllowed";
    case HttpRequest::h_onRedirected:
        return debug << "onRedirected";
    case HttpRequest::h_onSslErrors:
        return debug << "onSslErrors";
    case HttpRequest::h_onRetried:
        return debug << "onRetried";
    case HttpRequest::h_onRepeated:
        return debug << "onRepeated";
    case HttpRequest::h_onAuthenticationRequired:
        return debug << "onAuthenticationRequired";
    case HttpRequest::h_onAuthenticationRequireFailed:
        return debug << "onAuthenticationRequireFailed";
    case HttpRequest::h_onHead:
        return debug << "onHead";
    case HttpRequest::h_onDownloadFileProgess:
        return debug << "onDownloadFileProgress";
    case HttpRequest::h_onDownloadFileNameChanged:
        return debug << "onDownloadFileNameChanged";
    default:
        return debug << "Unknow";
    }
}

static int extractCode(const char* member)
{
    /* extract code, ensure QMETHOD_CODE <= code <= QSIGNAL_CODE */
    return (((int)(*member) - '0') & 0x3);
}

static bool isMethod(const char* member)
{
    int ret = extractCode(member);
    return ret >= QMETHOD_CODE && ret <= QSIGNAL_CODE;
}

template <typename M, typename L, typename T>
bool httpResponseConnect(L sender, T senderSignal, const QString& lambdaString, const QVariant& lambda)
{
    if (lambdaString == QVariant::fromValue(M()).typeName())
    {
        return QObject::connect(sender, senderSignal, lambda.value<M>());
    }
    else if (isMethod(qPrintable(lambdaString)))
    {
        QString signal = QMetaMethod::fromSignal(senderSignal).methodSignature();
        signal.insert(0, "2");
        signal.replace("qlonglong", "qint64");

        const QObject* receiver = lambda.value<QObject*>();
        QString method =
            QMetaObject::normalizedSignature(qPrintable(lambdaString));  // remove 'const', like: const QString => QString

        if (QMetaObject::checkConnectArgs(qPrintable(signal), qPrintable(method)))
        {
            return QObject::connect(sender, qPrintable(signal), receiver, qPrintable(method));
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false;
    }
}

#define HTTP_RESPONSE_CONNECT_X(sender, senderSignal, lambdaString, lambda, ...)                                       \
httpResponseConnect<std::function<void(__VA_ARGS__)>>(                                                               \
                                                                                                                     sender, static_cast<void (HttpResponse::*)(__VA_ARGS__)>(&HttpResponse::senderSignal), lambdaString, lambda);

HttpResponse* HttpRequest::exec()
{
    if (this->m_downloader.isEnabled)
    {
        HttpDownloader* downloader = new HttpDownloader(*this, nullptr);
        HttpResponse* response = downloader->exec();
        downloader->setParent(response);
        return response;
    }
    else
    {
        return exec(*this);
    }
}

HttpRequest& HttpRequest::enabledRetry(bool isEnabled)
{
    m_enabledRetry = isEnabled;
    return *this;
}

HttpRequest& HttpRequest::queryParam(const QString& key, const QVariant& value)
{
    QUrl url(m_request.url());
    QUrlQuery urlQuery(url);

    urlQuery.addQueryItem(key, value.toString());
    url.setQuery(urlQuery);

    m_request.setUrl(url);

    return *this;
}

HttpRequest& HttpRequest::queryParams(const QMap<QString, QVariant>& params)
{
    QMapIterator<QString, QVariant> iter(params);
    while (iter.hasNext())
    {
        iter.next();
        queryParam(iter.key(), iter.value());
    }

    return *this;
}

HttpRequest& HttpRequest::userAttribute(const QVariant& value)
{
    m_request.setAttribute(QNetworkRequest::User, value);
    return *this;
}

HttpRequest& HttpRequest::attribute(QNetworkRequest::Attribute attribute, const QVariant& value)
{
    m_request.setAttribute(attribute, value);
    return *this;
}

HttpClient* HttpClient::instance()
{
    static HttpClient client;
    return &client;
}

HttpClient::HttpClient(QObject* parent) : QNetworkAccessManager(parent)
{
}

QString HttpClient::getVersion() const
{
    return "1.1.0";
}

HttpRequest HttpClient::head(const QString& url)
{
    return HttpRequest(QNetworkAccessManager::HeadOperation, this).headers(globalHeader).url(url).timeout(timeoutSecond);
}

HttpRequest HttpClient::get(const QString& url)
{
    return HttpRequest(QNetworkAccessManager::GetOperation, this).headers(globalHeader).url(url).timeout(timeoutSecond);
}

HttpRequest HttpClient::post(const QString& url)
{
    return HttpRequest(QNetworkAccessManager::PostOperation, this).headers(globalHeader).url(url).timeout(timeoutSecond);
}

HttpRequest HttpClient::put(const QString& url)
{
    return HttpRequest(QNetworkAccessManager::PutOperation, this).headers(globalHeader).url(url).timeout(timeoutSecond);
}

HttpRequest HttpClient::send(const QString& url, QNetworkAccessManager::Operation op)
{
    return HttpRequest(op, this).headers(globalHeader).url(url).timeout(timeoutSecond);
}

#if (QT_VERSION < QT_VERSION_CHECK(5, 8, 0))
QNetworkReply* HttpClient::sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb,
                                             const QByteArray& data)
{
    QBuffer* buffer = new QBuffer;
    buffer->setData(data);
    buffer->open(QIODevice::ReadOnly);
    QNetworkReply* reply = QNetworkAccessManager::sendCustomRequest(request, verb, buffer);
    buffer->setParent(reply);

    return reply;
}

QNetworkReply* HttpClient::sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb,
                                             QHttpMultiPart* multiPart)
{
    if (verb == "PUT")
    {
        return QNetworkAccessManager::put(request, multiPart);
    }
    else if (verb == "POST")
    {
        return QNetworkAccessManager::post(request, multiPart);
    }
    else
    {
        qWarning() << "not support " << verb << "multi part.";
        return nullptr;
    }
}
#endif

HttpResponse::HttpResponse(const HttpRequest& httpRequest, QObject* parent)
    : QObject(parent), m_httpRequest(httpRequest), m_retriesRemaining(httpRequest.m_retryCount)
{
    this->setHttpRequest(httpRequest);
}

HttpResponse::~HttpResponse()
{
}

void HttpResponse::setHttpRequest(const HttpRequest& httpRequest)
{
    if (httpRequest.m_timeoutMs > 0)
    {
        new HttpResponseTimeout(this, httpRequest.m_timeoutMs);
    }

    QNetworkReply* reply = httpRequest.m_reply;
    if (reply)
    {
        connect(reply, SIGNAL(finished()), this, SLOT(onFinished()));
        connect(reply, SIGNAL(finished()), this, SLOT(onHandleHead()));
        connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));

        connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64)));
        connect(reply, SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(onUploadProgress(qint64, qint64)));

        connect(reply, SIGNAL(readyRead()), this, SLOT(onReadOnceReplyHeader()));
        connect(reply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
        connect(reply, SIGNAL(readyRead()), this, SLOT(onHandleHead()));

        connect(reply, SIGNAL(encrypted()), this, SLOT(onEncrypted()));
        connect(reply, SIGNAL(metaDataChanged()), this, SLOT(onMetaDataChanged()));

        connect(reply, SIGNAL(redirected(QUrl)), this, SLOT(onRedirected(QUrl)));
        connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(onSslErrors(QList<QSslError>)));

#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
        connect(reply, SIGNAL(redirectAllowed()), this, SLOT(onRedirectAllowed()));
#endif

        connect(reply, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), this,
                SLOT(onPreSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)));

        connect(reply->manager(), SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), this,
                SLOT(onAuthenticationRequired(QNetworkReply*, QAuthenticator*)));

        // fixme: Too cumbersome
        for (auto each : httpRequest.m_handleMap.toStdMap())
        {
            const HttpRequest::HandleType& key = each.first;
            const QList<QPair<QString, QVariant>>& value = each.second;

            for (auto iter : value)
            {
                const QVariant& lambda = iter.second;
                const QString& lambdaString = iter.first;
                int ret = 0;

                if (key == HttpRequest::h_onFinished)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, finished, lambdaString, lambda, void);
                    ret += HTTP_RESPONSE_CONNECT_X(this, finished, lambdaString, lambda, QByteArray);
                    ret += HTTP_RESPONSE_CONNECT_X(this, finished, lambdaString, lambda, QVariantMap);
                    ret += HTTP_RESPONSE_CONNECT_X(this, finished, lambdaString, lambda, QNetworkReply*);
                }
                else if (key == HttpRequest::h_onDownloadProgress)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, downloadProgress, lambdaString, lambda, qint64, qint64);
                }
                else if (key == HttpRequest::h_onUploadProgress)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, uploadProgress, lambdaString, lambda, qint64, qint64);
                }
                else if (key == HttpRequest::h_onError)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, error, lambdaString, lambda, void);
                    ret += HTTP_RESPONSE_CONNECT_X(this, error, lambdaString, lambda, QByteArray);
                    ret += HTTP_RESPONSE_CONNECT_X(this, error, lambdaString, lambda, QNetworkReply*);
                    ret += HTTP_RESPONSE_CONNECT_X(this, error, lambdaString, lambda, QNetworkReply::NetworkError);
                }
                else if (key == HttpRequest::h_onTimeout)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, timeout, lambdaString, lambda, QNetworkReply*);
                    ret += HTTP_RESPONSE_CONNECT_X(this, timeout, lambdaString, lambda, void);
                }
                else if (key == HttpRequest::h_onReadyRead)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, readyRead, lambdaString, lambda, QNetworkReply*);
                }
                else if (key == HttpRequest::h_onDownloadFileSuccess)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileFinished, lambdaString, lambda, void);
                    ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileFinished, lambdaString, lambda, QString);
                }
                else if (key == HttpRequest::h_onDownloadFileFailed)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileError, lambdaString, lambda, void);
                    ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileError, lambdaString, lambda, QString);
                }
                else if (key == HttpRequest::h_onEncrypted)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, encrypted, lambdaString, lambda, void);
                }
                else if (key == HttpRequest::h_onMetaDataChanged)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, metaDataChanged, lambdaString, lambda, void);
                }
                else if (key == HttpRequest::h_onPreSharedKeyAuthenticationRequired)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, preSharedKeyAuthenticationRequired, lambdaString, lambda,
                                                   QSslPreSharedKeyAuthenticator*);
                }
                else if (key == HttpRequest::h_onRedirectAllowed)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, redirectAllowed, lambdaString, lambda, void);
                }
                else if (key == HttpRequest::h_onRedirected)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, redirected, lambdaString, lambda, QUrl);
                }
                else if (key == HttpRequest::h_onSslErrors)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, sslErrors, lambdaString, lambda, QList<QSslError>);
                }
                else if (key == HttpRequest::h_onRetried)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, retried, lambdaString, lambda, void);
                }
                else if (key == HttpRequest::h_onRepeated)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, repeated, lambdaString, lambda, void);
                }
                else if (key == HttpRequest::h_onAuthenticationRequired)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, authenticationRequired, lambdaString, lambda, QAuthenticator*);
                }
                else if (key == HttpRequest::h_onAuthenticationRequireFailed)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, authenticationRequireFailed, lambdaString, lambda, void);
                    ret += HTTP_RESPONSE_CONNECT_X(this, authenticationRequireFailed, lambdaString, lambda, QNetworkReply*);
                }
                else if (key == HttpRequest::h_onHead)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, head, lambdaString, lambda, QList<QNetworkReply::RawHeaderPair>);
                    ret += HTTP_RESPONSE_CONNECT_X(this, head, lambdaString, lambda, QMap<QString, QString>);
                }
                else if (key == HttpRequest::h_onDownloadFileProgess)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileProgress, lambdaString, lambda, qint64, qint64);
                }
                else if (key == HttpRequest::h_onDownloadFileNameChanged)
                {
                    ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileNameChanged, lambdaString, lambda, QString);
                }
                else
                {
                    printWarn(httpRequest.m_logLevel, QString("%1 unsupported").arg(key).toStdString().c_str());
                }

                if (ret == 0)
                {
                    QString method = lambdaString;
                    if (isMethod(qPrintable(method)))
                        method.remove(0, 1);

                    printWarn(httpRequest.m_logLevel,
                              QString("%1 method[%2] is invalid").arg(key).arg(method).toStdString().c_str());
                }
            }
        }
    }

    if (reply && httpRequest.m_isBlock)
    {
        new HttpBlocker(reply, httpRequest.m_isBlock);
    }

    HttpRequest oldRequest = m_httpRequest;
    m_httpRequest = httpRequest;

    if (oldRequest.m_reply != httpRequest.m_reply)
    {
        emit replyChanged(httpRequest.m_reply);
    }
}

QString HttpResponse::toString() const
{
    QString str =
        "General: \n"
        "    Request URL: %{url} \n"
        "    Request Method: %{method} \n"
        "    Request Status: %{status}(%{statusString}) \n"
        "Request Headers: \n"
        "%{requestHeaders} \n"
        "Response Headers: \n"
        "%{responseHeaders} \n"
        "Request Body: \n"
        "%{requestBody}";

    QNetworkReply* reply = this->m_httpRequest.m_reply;
    str.replace("%{url}", this->m_httpRequest.m_request.url().toString());
    str.replace("%{method}", networkOperation2String(m_httpRequest.m_op));
    str.replace("%{status}", QString::number(reply->error()));
    str.replace("%{statusString}", reply->errorString());
    str.replace("%{requestHeaders}", lineIndent(networkHeader2String(reply->request()), "    "));
    str.replace("%{responseHeaders}", lineIndent(networkReplyHeader2String(reply), "    "));
    str.replace("%{requestBody}", lineIndent(networkBody2String(m_httpRequest.m_body), "    "));
    return str;
}

void HttpResponse::onFinished()
{
    QNetworkReply* reply = m_httpRequest.m_reply;
    if (reply->error() != QNetworkReply::NoError)
    {
        return;
    }

    for (QObject* o : reply->children())
    {
        HttpResponse* response = qobject_cast<HttpResponse*>(o);
        if (response)
        {
            printDebug(m_httpRequest.m_logLevel, response->toString().toStdString().c_str());
        }
    }

    if (m_httpRequest.m_enabledRetry)
    {
        emit retried();
    }

    if (m_downloadFile.isOpen())
    {
        emit downloadFileFinished();
        emit downloadFileFinished(m_downloadFile.fileName());

        m_downloadFile.close();
    }

    bool isAutoDelete = true;
    if (this->receivers(SIGNAL(finished(QNetworkReply*))) > 0)
    {
        emit finished(reply);
        isAutoDelete = false;
    }

    if (this->receivers(SIGNAL(finished())) > 0 || this->receivers(SIGNAL(finished(QByteArray))) > 0 ||
        this->receivers(SIGNAL(finished(QVariantMap))) > 0)
    {
        QByteArray result = reply->readAll();
        emit finished();

        emit finished(result);

        QVariantMap resultMap = QJsonDocument::fromJson(result).object().toVariantMap();
        emit finished(resultMap);
    }

    if (--m_httpRequest.m_repeatCount > 0)
    {
        HttpRequest httpRequest = m_httpRequest;
        httpRequest.repeat(m_httpRequest.m_repeatCount).exec();
    }
    else
    {
        emit repeated();
    }

    if (isAutoDelete)
    {
        reply->deleteLater();
    }
}

void HttpResponse::onError(QNetworkReply::NetworkError error)
{
    QNetworkReply* reply = m_httpRequest.m_reply;

    printInfo(m_httpRequest.m_logLevel,
              QString("%1 error: %2").arg(reply->url().toString()).arg(error).toStdString().c_str());

    if (m_retriesRemaining-- > 0)
    {
        HttpRequest httpRequest = m_httpRequest;
        httpRequest.retry(m_retriesRemaining).enabledRetry(true).exec();
        reply->deleteLater();
        return;
    }

    if (m_httpRequest.m_enabledRetry)
    {
        emit retried();
    }

    const QMetaObject& metaObject = QNetworkReply::staticMetaObject;
    QMetaEnum metaEnum = metaObject.enumerator(metaObject.indexOfEnumerator("NetworkError"));
    QString errorString = reply->errorString().isEmpty() ? metaEnum.valueToKey(error) : reply->errorString();

    if (m_httpRequest.m_downloader.isEnabled)
    {
        QString error = QString("Url: %1 file: %2 error: %3")
                            .arg(m_httpRequest.m_request.url().toString())  // fixme
                            .arg(m_downloadFile.fileName())
                            .arg(errorString);

        emit downloadFileError();
        emit downloadFileError(error);

        m_downloadFile.close();
    }

    bool isAutoDelete = true;
    if (this->receivers(SIGNAL(error(QNetworkReply*))) > 0)
    {
        emit this->error(reply);
        isAutoDelete = false;
    }

    emit this->error();
    emit this->error(error);
    emit this->error(errorString.toLocal8Bit());

    if (--m_httpRequest.m_repeatCount > 0)
    {
        HttpRequest httpRequest = m_httpRequest;
        httpRequest.repeat(m_httpRequest.m_repeatCount).exec();
    }
    else
    {
        emit repeated();
    }

    if (isAutoDelete)
    {
        reply->deleteLater();
    }
}

void HttpResponse::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
    emit this->downloadProgress(bytesReceived, bytesTotal);
}

void HttpResponse::onUploadProgress(qint64 bytesSent, qint64 bytesTotal)
{
    emit this->uploadProgress(bytesSent, bytesTotal);
}

void HttpResponse::onTimeout()
{
    QNetworkReply* reply = m_httpRequest.m_reply;
    if (reply->isRunning())
    {
        reply->abort();

        bool isAutoDelete = true;
        if (this->receivers(SIGNAL(timeout(QNetworkReply*))) > 0)
        {
            emit this->timeout(reply);
            isAutoDelete = false;
        }

        if (this->receivers(SIGNAL(timeout())) > 0)
        {
            emit this->timeout();
        }

        if (isAutoDelete)
        {
            reply->deleteLater();
        }
    }
}

void HttpResponse::onReadyRead()
{
    QNetworkReply* reply = m_httpRequest.m_reply;
    if (m_httpRequest.m_downloader.isEnabled)
    {
        if (m_downloadFile.isOpen())
        {
            int size = m_downloadFile.write(reply->readAll());
            if (size == -1)
            {
                QString error = QString("Url: %1 %2 Write failed!")
                                    .arg(m_httpRequest.m_request.url().toString())
                                    .arg(m_downloadFile.fileName());
                emit downloadFileError();
                emit downloadFileError(error);
            }
            else
            {
                m_httpRequest.m_downloader.currentSize += size;
                emit downloadFileProgress(m_httpRequest.m_downloader.currentSize, m_httpRequest.m_downloader.totalSize);
            }
        }
        else
        {
            // do nothing
        }
    }
    else
    {
        // do nothing
    }

    emit readyRead(reply);
}

void HttpResponse::onReadOnceReplyHeader()
{
    if (!m_httpRequest.m_downloader.isEnabled)
        return;

    QNetworkReply* reply = m_httpRequest.m_reply;
    disconnect(reply, SIGNAL(readyRead()), this, SLOT(onReadOnceReplyHeader()));

    QString fileName = m_httpRequest.m_downloader.fileName;
    m_downloadFile.setFileName(fileName);

    QIODevice::OpenMode mode = QIODevice::WriteOnly;
    if (m_httpRequest.m_downloader.isSupportBreakpointDownload && m_httpRequest.m_downloader.enabledBreakpointDownload &&
        QFile::exists(fileName))
    {
        mode = QIODevice::Append;
    }

    if (!m_downloadFile.open(mode))
    {
        QString error =
            QString("Url: %1 %2 Non-Writable").arg(m_httpRequest.m_request.url().toString()).arg(m_downloadFile.fileName());
        emit downloadFileError();
        emit downloadFileError(error);
    }
    else
    {
        // todo startDownload
    }
}

void HttpResponse::onEncrypted()
{
    emit encrypted();
}

void HttpResponse::onMetaDataChanged()
{
    emit metaDataChanged();
}

void HttpResponse::onPreSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator* authenticator)
{
    emit preSharedKeyAuthenticationRequired(authenticator);
}

void HttpResponse::onRedirectAllowed()
{
    emit redirectAllowed();
}

void HttpResponse::onRedirected(const QUrl& url)
{
    emit redirected(url);
}

void HttpResponse::onSslErrors(const QList<QSslError>& errors)
{
    emit sslErrors(errors);
}

void HttpResponse::onAuthenticationRequired(QNetworkReply* reply, QAuthenticator* authenticator)
{
    if (this->reply() != reply)
    {
        return;
    }

    m_authenticationCount++;

    bool isAuthenticationSuccessed = (m_authenticationCount >= 2);
    if (isAuthenticationSuccessed)
    {
        emit authenticationRequireFailed();
        emit authenticationRequireFailed(this->reply());
    }

    if (m_httpRequest.m_authenticationRequiredCount >= 0 &&
        m_authenticationCount > m_httpRequest.m_authenticationRequiredCount)
    {
        return;
    }

    if (m_httpRequest.m_authenticator.isNull())
    {
        emit authenticationRequired(authenticator);
    }
    else
    {
        authenticator->setUser(m_httpRequest.m_authenticator.user());
        authenticator->setPassword(m_httpRequest.m_authenticator.password());
        // todo setOption....
    }
}

void HttpResponse::onHandleHead()
{
    if (m_isHandleHead)
    {
        return;
    }

    m_isHandleHead = true;

    QNetworkReply* reply = m_httpRequest.m_reply;
    if (this->receivers(SIGNAL(head(QList<QNetworkReply::RawHeaderPair>))) ||
        this->receivers(SIGNAL(head(QMap<QString, QString>))))
    {
        emit head(reply->rawHeaderPairs());
        QMap<QString, QString> map;
        foreach (auto each, reply->rawHeaderPairs())
        {
            map[each.first] = each.second;
        }

        emit head(map);
    }
}

inline QString lineIndent(const QString& source, const QString& indentString)
{
    QRegularExpression rx("^(.*)");
    QRegularExpression::PatternOptions patternOptions;
    patternOptions |= QRegularExpression::MultilineOption;
    rx.setPatternOptions(patternOptions);
    return QString(source).replace(rx, indentString + "\\1");
}

inline QString networkHeader2String(const QNetworkRequest& request)
{
    QString headerString;
    for (const QByteArray& each : request.rawHeaderList())
    {
        QByteArray value = request.rawHeader(each);
        headerString += QString("%1: %2\n").arg(QString(each)).arg(QString(value));
    }

    if (headerString.isEmpty())
    {
        return "null";
    }

    if (headerString.at(headerString.size() - 1) == '\n')
    {
        headerString.chop(1);
    }

    return headerString;
}

inline QString networkReplyHeader2String(const QNetworkReply* reply)
{
    QString headerString;
    for (const QByteArray& each : reply->rawHeaderList())
    {
        QByteArray value = reply->rawHeader(each);
        headerString += QString("%1: %2\n").arg(QString(each)).arg(QString(value));
    }

    if (headerString.isEmpty())
    {
        return "null";
    }

    if (headerString.at(headerString.size() - 1) == '\n')
    {
        headerString.chop(1);
    }

    return headerString;
}

inline QString networkBodyType2String(HttpRequest::BodyType t)
{
    if (t == HttpRequest::MultiPart)
    {
        return "MultiPart";
    }
    else if (t == HttpRequest::FileMap)
    {
        return "FileMap";
    }
    else if (t == HttpRequest::FormData)
    {
        return "FormData";
    }
    else if (t == HttpRequest::Raw)
    {
        return "Raw";
    }
    else if (t == HttpRequest::Raw_Json)
    {
        return "RawJson";
    }
    else if (t == HttpRequest::X_Www_Form_Urlencoded)
    {
        return "x_www_form_urlencoded";
    }
    else
    {
        return "None";
    }
}

inline QString networkBody2String(const QPair<HttpRequest::BodyType, QVariant>& body)
{
    QString bodyTypeString;
    bodyTypeString += "Type: " + networkBodyType2String(body.first) + "\n";
    bodyTypeString += "Data: \n";

    QString bodyDataString;
    if (body.first == HttpRequest::MultiPart)
    {
        QDebug d(&bodyDataString);
        d << body.second;
    }
    else if (body.first == HttpRequest::FileMap)
    {
        const auto& fileMap = body.second.value<QMap<QString, QString>>();
        for (const auto& each : fileMap.toStdMap())
        {
            const QString& key = each.first;
            const QString& filePath = each.second;
            bodyDataString += key + ": " + filePath + "\n";
        }
    }
    else if (body.first == HttpRequest::FormData)
    {
        const auto& formDataMap = body.second.value<QMap<QString, QVariant>>();
        for (const auto& each : formDataMap.toStdMap())
        {
            const QString& key = each.first;
            const QString& value = each.second.toString();
            bodyDataString += key + ": " + value + "\n";
        }
    }
    else if (body.first == HttpRequest::X_Www_Form_Urlencoded || body.first == HttpRequest::Raw ||
             body.first == HttpRequest::Raw_Json)
    {
        bodyDataString += body.second.toByteArray();
    }

    if (bodyDataString.isEmpty())
    {
        bodyDataString = "null";
    }

    bodyDataString = lineIndent(bodyDataString, "=>    ");

    QString bodyString = bodyTypeString + bodyDataString;
    if (bodyString.at(bodyString.size() - 1) == '\n')
    {
        bodyString.chop(1);
    }

    return bodyString;
}

inline QString networkOperation2String(QNetworkAccessManager::Operation o)
{
    static QMap<QNetworkAccessManager::Operation, QByteArray> verbMap = {
                                                                         { QNetworkAccessManager::HeadOperation, "HEAD" },
                                                                         { QNetworkAccessManager::GetOperation, "GET" },
                                                                         { QNetworkAccessManager::PostOperation, "POST" },
                                                                         { QNetworkAccessManager::PutOperation, "PUT" },
                                                                         };

    return verbMap.value(o, "");
}
}  // namespace AeaQt

#define HTTPRESPONSE_DECLARE_METATYPE(...) Q_DECLARE_METATYPE(std::function<void(__VA_ARGS__)>)

HTTPRESPONSE_DECLARE_METATYPE(void)
HTTPRESPONSE_DECLARE_METATYPE(QByteArray)
HTTPRESPONSE_DECLARE_METATYPE(QString)
HTTPRESPONSE_DECLARE_METATYPE(QVariantMap)
HTTPRESPONSE_DECLARE_METATYPE(QNetworkReply*)
HTTPRESPONSE_DECLARE_METATYPE(qint64, qint64)
HTTPRESPONSE_DECLARE_METATYPE(QNetworkReply::NetworkError)
HTTPRESPONSE_DECLARE_METATYPE(QSslPreSharedKeyAuthenticator*)
HTTPRESPONSE_DECLARE_METATYPE(QUrl)
HTTPRESPONSE_DECLARE_METATYPE(QList<QSslError>)
HTTPRESPONSE_DECLARE_METATYPE(QAuthenticator*)
HTTPRESPONSE_DECLARE_METATYPE(QList<QNetworkReply::RawHeaderPair>)
HTTPRESPONSE_DECLARE_METATYPE(QMap<QString, QString>)

#endif  // HTTPCLIENT_HPP
