/*
 * Copyright (c) 2018-2019 TOYOTA MOTOR CORPORATION
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "httpclient.h"

#include <QDebug>
#include <QFile>
#include <QHash>
#include <QHttpMultiPart>
#include <QHttpPart>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrlQuery>

class HttpClientPrivate {
 public:
  HttpClientPrivate(const QString& url);

  QString url;
  QUrlQuery params;
  QHash<QString, QString> headers;
  QNetworkAccessManager* manager;

  bool debug;

  enum HttpMethod { GET, POST, PUT, DELETE };

  static QNetworkAccessManager* getManager(HttpClientPrivate* d,
                                           bool* internal);

  static QNetworkRequest createRequest(HttpClientPrivate* d, HttpMethod method);

  static void get(HttpClientPrivate* d,
                  HttpMethod method,
                  std::function<void(const QString&)> successHandler,
                  std::function<void(const QString&)> errorHandler,
                  const char* encoding);

  static QString readReply(QNetworkReply* reply,
                           const char* encoding = "UTF-8");

  static void handleFinish(bool debug,
                           const QString& successMessage,
                           const QString& errorMessage,
                           std::function<void(const QString&)> successHandler,
                           std::function<void(const QString&)> errorHandler,
                           QNetworkReply* reply,
                           QNetworkAccessManager* manager);
};

HttpClientPrivate::HttpClientPrivate(const QString& url)
    : url(url), manager(NULL), debug(false) {}

HttpClient::HttpClient(const QString& url) : d(new HttpClientPrivate(url)) {}

HttpClient::~HttpClient() {
  delete d;
}

HttpClient& HttpClient::manager(QNetworkAccessManager* manager) {
  d->manager = manager;
  return *this;
}

HttpClient& HttpClient::debug(bool debug) {
  d->debug = debug;
  return *this;
}

HttpClient& HttpClient::param(const QString& name, const QString& value) {
  d->params.addQueryItem(name, value);
  return *this;
}

HttpClient& HttpClient::header(const QString& header, const QString& value) {
  d->headers[header] = value;
  return *this;
}

void HttpClient::get(std::function<void(const QString&)> successHandler,
                     std::function<void(const QString&)> errorHandler,
                     const char* encoding) {
  HttpClientPrivate::get(d, HttpClientPrivate::GET, successHandler,
                         errorHandler, encoding);
}

void HttpClient::download(
    const QString& savePath,
    std::function<void(const QString&)> successHandler,
    std::function<void(const QString&)> errorHandler,
    std::function<void(const qint64, const qint64)> progressHandler) {
  bool debug = d->debug;
  QFile* file = new QFile(savePath);

  if (file->open(QIODevice::WriteOnly)) {
    download(
        [=](const QByteArray& data) { file->write(data); },
        [=](const QString&) {
          file->flush();
          file->close();
          file->deleteLater();

          if (debug) {
            qDebug().noquote()
                << QString("download finished, save to: %1").arg(savePath);
          }

          if (NULL != successHandler) {
            successHandler(
                QString("download finished, save to: %1").arg(savePath));
          }
        },
        errorHandler, progressHandler);
  } else {
    if (debug) {
      qDebug().noquote() << QString("open file error: %1").arg(savePath);
    }

    if (NULL != errorHandler) {
      errorHandler(QString("open file error: %1").arg(savePath));
    }
  }
}

void HttpClient::download(
    std::function<void(const QByteArray&)> readyRead,
    std::function<void(const QString&)> successHandler,
    std::function<void(const QString&)> errorHandler,
    std::function<void(const qint64, const qint64)> progressHandler) {
  bool debug = d->debug;
  bool internal;

  QNetworkAccessManager* manager = HttpClientPrivate::getManager(d, &internal);
  QNetworkRequest request =
      HttpClientPrivate::createRequest(d, HttpClientPrivate::GET);
  QNetworkReply* reply = manager->get(request);

  QObject::connect(reply, &QNetworkReply::readyRead,
                   [=] { readyRead(reply->readAll()); });

  QObject::connect(reply, &QNetworkReply::finished, [=] {
    QString successMessage = "download finished";
    QString errorMessage = reply->errorString();
    HttpClientPrivate::handleFinish(debug, successMessage, errorMessage,
                                    successHandler, errorHandler, reply,
                                    internal ? manager : NULL);
  });

  QObject::connect(reply, &QNetworkReply::downloadProgress,
                   [=](qint64 bytesReceived, qint64 bytesTotal) {
                     if (NULL != progressHandler) {
                       progressHandler(bytesReceived, bytesTotal);
                     }
                   });
}

void HttpClientPrivate::get(HttpClientPrivate* d,
                            HttpMethod method,
                            std::function<void(const QString&)> successHandler,
                            std::function<void(const QString&)> errorHandler,
                            const char* encoding) {
  bool internal;

  QNetworkAccessManager* manager = HttpClientPrivate::getManager(d, &internal);
  QNetworkRequest request =
      HttpClientPrivate::createRequest(d, HttpClientPrivate::GET);
  QNetworkReply* reply = NULL;

  switch (method) {
    case HttpClientPrivate::GET:
      reply = manager->get(request);
      break;

    default:
      break;
  }

  QObject::connect(reply, &QNetworkReply::finished, [=] {
    QString successMessage = HttpClientPrivate::readReply(reply, encoding);
    QString errorMessage = reply->errorString();
    HttpClientPrivate::handleFinish(d->debug, successMessage, errorMessage,
                                    successHandler, errorHandler, reply,
                                    internal ? manager : NULL);
  });
}

QNetworkAccessManager* HttpClientPrivate::getManager(HttpClientPrivate* d,
                                                     bool* internal) {
  *internal = d->manager == NULL;
  return *internal ? new QNetworkAccessManager() : d->manager;
}

QNetworkRequest HttpClientPrivate::createRequest(HttpClientPrivate* d,
                                                 HttpMethod method) {
  if (!d->params.isEmpty()) {
    d->url += "?" + d->params.toString(QUrl::FullyEncoded);
  }

  if (d->debug) {
    qDebug().noquote() << "url:" << d->url;

    QList<QPair<QString, QString> > paramItems = d->params.queryItems();
    for (int i = 0; i < paramItems.size(); ++i) {
      QString name = paramItems.at(i).first;
      QString value = paramItems.at(i).second;
      if (0 == i) {
        qDebug().noquote() << QString("params: %1=%2").arg(name).arg(value);
      } else {
        qDebug().noquote() << QString("     %1=%2").arg(name).arg(value);
      }
    }
  }

  QNetworkRequest request(QUrl(d->url));
  QHashIterator<QString, QString> iter(d->headers);
  while (iter.hasNext()) {
    iter.next();
    request.setRawHeader(iter.key().toUtf8(), iter.value().toUtf8());
  }

  return request;
}

QString HttpClientPrivate::readReply(QNetworkReply* reply,
                                     const char* encoding) {
  QTextStream in(reply);
  QString result;
  in.setCodec(encoding);

  while (!in.atEnd()) {
    result += in.readLine();
  }

  return result;
}

void HttpClientPrivate::handleFinish(
    bool debug,
    const QString& successMessage,
    const QString& errorMessage,
    std::function<void(const QString&)> successHandler,
    std::function<void(const QString&)> errorHandler,
    QNetworkReply* reply,
    QNetworkAccessManager* manager) {
  if (reply->error() == QNetworkReply::NoError) {
    if (debug) {
      qDebug().noquote()
          << QString("request successed: %1").arg(successMessage);
    }

    if (NULL != successHandler) {
      successHandler(successMessage);
    }
  } else {
    if (debug) {
      qDebug().noquote() << QString("request failed: %1").arg(errorMessage);
    }

    if (NULL != errorHandler) {
      errorHandler(errorMessage);
    }
  }

  reply->deleteLater();
  if (NULL != manager) {
    manager->deleteLater();
  }
}