/**
 *
 *  @file HttpClientImpl.h
 *  @author An Tao
 *
 *  Copyright 2018, An Tao.  All rights reserved.
 *  https://github.com/an-tao/drogon
 *  Use of this source code is governed by the MIT license
 *  that can be found in the License file.
 *
 *  Drogon
 *
 */

#pragma once

#include <drogon/Cookie.h>
#include <drogon/HttpClient.h>
#include <trantor/net/EventLoop.h>
#include <trantor/net/Resolver.h>
#include <trantor/net/TcpClient.h>
#include <cstddef>
#include <functional>
#include <future>
#include <list>
#include <mutex>
#include <queue>
#include <vector>
#include "impl_forwards.h"

namespace drogon
{
class HttpClientImpl final : public HttpClient,
                             public std::enable_shared_from_this<HttpClientImpl>
{
  public:
    HttpClientImpl(trantor::EventLoop *loop,
                   const trantor::InetAddress &addr,
                   bool useSSL = false,
                   bool useOldTLS = false,
                   bool validateCert = true);
    HttpClientImpl(trantor::EventLoop *loop,
                   const std::string &hostString,
                   bool useOldTLS = false,
                   bool validateCert = true);
    void sendRequest(const HttpRequestPtr &req,
                     const HttpReqCallback &callback,
                     double timeout = 0) override;
    void sendRequest(const HttpRequestPtr &req,
                     HttpReqCallback &&callback,
                     double timeout = 0) override;

    trantor::EventLoop *getLoop() override
    {
        return loop_;
    }

    void setPipeliningDepth(size_t depth) override
    {
        pipeliningDepth_ = depth;
    }

    ~HttpClientImpl();

    void enableCookies(bool flag = true) override
    {
        enableCookies_ = flag;
    }

    void addCookie(const std::string &key, const std::string &value) override
    {
        validCookies_.emplace_back(Cookie(key, value));
    }

    void addCookie(const Cookie &cookie) override
    {
        validCookies_.emplace_back(cookie);
    }

    size_t bytesSent() const override
    {
        return bytesSent_;
    }

    size_t bytesReceived() const override
    {
        return bytesReceived_;
    }

    void setUserAgent(const std::string &userAgent) override
    {
        userAgent_ = userAgent;
    }

    uint16_t port() const override
    {
        return serverAddr_.toPort();
    }

    std::string host() const override
    {
        if (domain_.empty())
            return serverAddr_.toIp();
        return domain_;
    }

    bool secure() const override
    {
        return useSSL_;
    }

    void setCertPath(const std::string &cert, const std::string &key) override;
    void addSSLConfigs(const std::vector<std::pair<std::string, std::string>>
                           &sslConfCmds) override;

    void setSockOptCallback(std::function<void(int)> cb) override
    {
        sockOptCallback_ = std::move(cb);
    }

    std::size_t requestsBufferSize() override
    {
        if (loop_->isInLoopThread())
        {
            return requestsBuffer_.size();
        }
        else
        {
            std::promise<std::size_t> bufferSize;
            loop_->queueInLoop(
                [&] { bufferSize.set_value(requestsBuffer_.size()); });
            return bufferSize.get_future().get();
        }
    }

  private:
    std::shared_ptr<trantor::TcpClient> tcpClientPtr_;
    trantor::EventLoop *loop_;
    trantor::InetAddress serverAddr_;
    bool useSSL_;
    bool validateCert_;
    void sendReq(const trantor::TcpConnectionPtr &connPtr,
                 const HttpRequestPtr &req);
    void sendRequestInLoop(const HttpRequestPtr &req,
                           HttpReqCallback &&callback);
    void sendRequestInLoop(const HttpRequestPtr &req,
                           HttpReqCallback &&callback,
                           double timeout);
    void handleCookies(const HttpResponseImplPtr &resp);
    void handleResponse(const HttpResponseImplPtr &resp,
                        std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
                        const trantor::TcpConnectionPtr &connPtr);
    void createTcpClient();
    std::queue<std::pair<HttpRequestPtr, HttpReqCallback>> pipeliningCallbacks_;
    std::list<std::pair<HttpRequestPtr, HttpReqCallback>> requestsBuffer_;
    void onRecvMessage(const trantor::TcpConnectionPtr &, trantor::MsgBuffer *);
    void onError(ReqResult result);
    std::string domain_;
    bool isDomainName_{true};  // true if domain_ is name
    size_t pipeliningDepth_{0};
    bool enableCookies_{false};
    std::vector<Cookie> validCookies_;
    size_t bytesSent_{0};
    size_t bytesReceived_{0};
    bool dns_{false};
    std::shared_ptr<trantor::Resolver> resolverPtr_;
    bool useOldTLS_{false};
    std::string userAgent_{"DrogonClient"};
    std::vector<std::pair<std::string, std::string>> sslConfCmds_;
    std::string clientCertPath_;
    std::string clientKeyPath_;
    std::function<void(int)> sockOptCallback_;
};

using HttpClientImplPtr = std::shared_ptr<HttpClientImpl>;
}  // namespace drogon
