#pragma once
#include <drogon/exports.h>
#include <trantor/net/Certificate.h>
#include <trantor/net/callbacks.h>
#include <trantor/net/AsyncStream.h>
#include <drogon/DrClassMap.h>
#include <drogon/Cookie.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpTypes.h>
#include <drogon/HttpViewData.h>
#include <drogon/utils/Utilities.h>
#include <json/json.h>
#include <memory>
#include <string>
#include <string_view>
namespace drogon
{
class HttpResponse;
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
template <typename T>
T fromResponse(const HttpResponse &)
{
    LOG_ERROR
        << "You must specialize the fromResponse template for the type of "
        << DrClassMap::demangle(typeid(T).name());
    exit(1);
}
template <typename T>
HttpResponsePtr toResponse(T &&)
{
    LOG_ERROR << "You must specialize the toResponse template for the type of "
              << DrClassMap::demangle(typeid(T).name());
    exit(1);
}
template <>
HttpResponsePtr toResponse<const Json::Value &>(const Json::Value &pJson);
template <>
HttpResponsePtr toResponse(Json::Value &&pJson);
template <>
inline HttpResponsePtr toResponse<Json::Value &>(Json::Value &pJson)
{
    return toResponse((const Json::Value &)pJson);
}
class DROGON_EXPORT ResponseStream
{
  public:
    explicit ResponseStream(trantor::AsyncStreamPtr asyncStream)
        : asyncStream_(std::move(asyncStream))
    {
    }
    ~ResponseStream()
    {
        close();
    }
    bool send(const std::string &data)
    {
        if (!asyncStream_)
        {
            return false;
        }
        std::ostringstream oss;
        oss << std::hex << data.length() << "\r\n";
        oss << data << "\r\n";
        return asyncStream_->send(oss.str());
    }
    void close()
    {
        if (asyncStream_)
        {
            static std::string closeStream{"0\r\n\r\n"};
            asyncStream_->send(closeStream);
            asyncStream_->close();
            asyncStream_.reset();
        }
    }
  private:
    trantor::AsyncStreamPtr asyncStream_;
};
using ResponseStreamPtr = std::unique_ptr<ResponseStream>;
class DROGON_EXPORT HttpResponse
{
  public:
    template <typename T>
    operator T() const
    {
        return fromResponse<T>(*this);
    }
    template <typename T>
    T as() const
    {
        return fromResponse<T>(*this);
    }
    virtual HttpStatusCode statusCode() const = 0;
    HttpStatusCode getStatusCode() const
    {
        return statusCode();
    }
    virtual void setStatusCode(HttpStatusCode code) = 0;
    void setCustomStatusCode(int code,
                             std::string_view message = std::string_view{})
    {
        setCustomStatusCode(code, message.data(), message.length());
    }
    virtual void setAllowCompression(bool allow) = 0;
    virtual bool allowCompression() const = 0;
    virtual const trantor::Date &creationDate() const = 0;
    const trantor::Date &getCreationDate() const
    {
        return creationDate();
    }
    virtual void setVersion(const Version v) = 0;
    virtual void setCloseConnection(bool on) = 0;
    virtual bool ifCloseConnection() const = 0;
    virtual void setContentTypeCode(ContentType type) = 0;
    void setContentTypeString(const std::string_view &typeString)
    {
        setContentTypeString(typeString.data(), typeString.size());
    }
    void setContentTypeCodeAndCustomString(ContentType type,
                                           const std::string_view &typeString)
    {
        setContentTypeCodeAndCustomString(type,
                                          typeString.data(),
                                          typeString.length());
    }
    template <int N>
    void setContentTypeCodeAndCustomString(ContentType type,
                                           const char (&typeString)[N])
    {
        assert(N > 0);
        setContentTypeCodeAndCustomString(type, typeString, N - 1);
    }
    virtual ContentType contentType() const = 0;
    ContentType getContentType() const
    {
        return contentType();
    }
    virtual const std::string &getHeader(std::string key) const = 0;
    virtual void removeHeader(std::string key) = 0;
    virtual const SafeStringMap<std::string> &headers() const = 0;
    const SafeStringMap<std::string> &getHeaders() const
    {
        return headers();
    }
    virtual void addHeader(std::string field, const std::string &value) = 0;
    virtual void addHeader(std::string field, std::string &&value) = 0;
    virtual void addCookie(const std::string &key,
                           const std::string &value) = 0;
    virtual void addCookie(const Cookie &cookie) = 0;
    virtual void addCookie(Cookie &&cookie) = 0;
    virtual const Cookie &getCookie(const std::string &key) const = 0;
    virtual const SafeStringMap<Cookie> &cookies() const = 0;
    const SafeStringMap<Cookie> &getCookies() const
    {
        return cookies();
    }
    virtual void removeCookie(const std::string &key) = 0;
    virtual void setBody(const std::string &body) = 0;
    virtual void setBody(std::string &&body) = 0;
    template <int N>
    void setBody(const char (&body)[N])
    {
        assert(strnlen(body, N) == N - 1);
        setBody(body, N - 1);
    }
    std::string_view body() const
    {
        return std::string_view{getBodyData(), getBodyLength()};
    }
    std::string_view getBody() const
    {
        return body();
    }
    virtual const char *versionString() const = 0;
    const char *getVersionString() const
    {
        return versionString();
    }
    virtual Version version() const = 0;
    Version getVersion() const
    {
        return version();
    }
    virtual void clear() = 0;
    virtual void setExpiredTime(ssize_t expiredTime) = 0;
    virtual ssize_t expiredTime() const = 0;
    ssize_t getExpiredTime() const
    {
        return expiredTime();
    }
    virtual const std::shared_ptr<Json::Value> &jsonObject() const = 0;
    const std::shared_ptr<Json::Value> &getJsonObject() const
    {
        return jsonObject();
    }
    virtual const std::string &getJsonError() const = 0;
    virtual void setPassThrough(bool flag) = 0;
    virtual const trantor::CertificatePtr &peerCertificate() const = 0;
    const trantor::CertificatePtr &getPeerCertificate() const
    {
        return peerCertificate();
    }
    static HttpResponsePtr newHttpResponse();
    static HttpResponsePtr newHttpResponse(HttpStatusCode code,
                                           ContentType type);
    static HttpResponsePtr newNotFoundResponse(
        const HttpRequestPtr &req = HttpRequestPtr());
    static HttpResponsePtr newHttpJsonResponse(const Json::Value &data);
    static HttpResponsePtr newHttpJsonResponse(Json::Value &&data);
    static HttpResponsePtr newHttpViewResponse(
        const std::string &viewName,
        const HttpViewData &data = HttpViewData(),
        const HttpRequestPtr &req = HttpRequestPtr());
    static HttpResponsePtr newRedirectionResponse(
        const std::string &location,
        HttpStatusCode status = k302Found);
    static HttpResponsePtr newFileResponse(
        const std::string &fullPath,
        const std::string &attachmentFileName = "",
        ContentType type = CT_NONE,
        const std::string &typeString = "",
        const HttpRequestPtr &req = HttpRequestPtr());
    static HttpResponsePtr newFileResponse(
        const std::string &fullPath,
        size_t offset,
        size_t length,
        bool setContentRange = true,
        const std::string &attachmentFileName = "",
        ContentType type = CT_NONE,
        const std::string &typeString = "",
        const HttpRequestPtr &req = HttpRequestPtr());
    static HttpResponsePtr newFileResponse(
        const unsigned char *pBuffer,
        size_t bufferLength,
        const std::string &attachmentFileName = "",
        ContentType type = CT_NONE,
        const std::string &typeString = "");
    static HttpResponsePtr newStreamResponse(
        const std::function<std::size_t(char *, std::size_t)> &callback,
        const std::string &attachmentFileName = "",
        ContentType type = CT_NONE,
        const std::string &typeString = "",
        const HttpRequestPtr &req = HttpRequestPtr());
    static HttpResponsePtr newAsyncStreamResponse(
        const std::function<void(ResponseStreamPtr)> &callback,
        bool disableKickoffTimeout = false);
    template <typename T>
    static HttpResponsePtr newCustomHttpResponse(T &&obj)
    {
        return toResponse(std::forward<T>(obj));
    }
    virtual const std::string &sendfileName() const = 0;
    using SendfileRange = std::pair<size_t, size_t>;  
    virtual const SendfileRange &sendfileRange() const = 0;
    virtual const std::function<std::size_t(char *, std::size_t)> &
    streamCallback() const = 0;
    virtual const std::function<void(ResponseStreamPtr)> &asyncStreamCallback()
        const = 0;
    virtual std::string contentTypeString() const = 0;
    virtual ~HttpResponse()
    {
    }
  private:
    virtual void setBody(const char *body, size_t len) = 0;
    virtual const char *getBodyData() const = 0;
    virtual size_t getBodyLength() const = 0;
    virtual void setContentTypeCodeAndCustomString(ContentType type,
                                                   const char *typeString,
                                                   size_t typeStringLength) = 0;
    virtual void setContentTypeString(const char *typeString,
                                      size_t typeStringLength) = 0;
    virtual void setCustomStatusCode(int code,
                                     const char *message,
                                     size_t messageLength) = 0;
};
template <>
inline HttpResponsePtr toResponse<const Json::Value &>(const Json::Value &pJson)
{
    return HttpResponse::newHttpJsonResponse(pJson);
}
template <>
inline HttpResponsePtr toResponse(Json::Value &&pJson)
{
    return HttpResponse::newHttpJsonResponse(std::move(pJson));
}
template <>
inline std::shared_ptr<Json::Value> fromResponse(const HttpResponse &resp)
{
    return resp.getJsonObject();
}
}  