#pragma once
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#endif
#include <drogon/exports.h>
#include <drogon/utils/HttpConstraint.h>
#include <drogon/CacheMap.h>
#include <drogon/DrObject.h>
#include <drogon/HttpBinder.h>
#include <drogon/HttpFilter.h>
#include <drogon/MultiPart.h>
#include <drogon/NotFound.h>
#include <drogon/drogon_callbacks.h>
#include <drogon/utils/Utilities.h>
#include <drogon/plugins/Plugin.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <drogon/orm/DbClient.h>
#include <drogon/orm/DbConfig.h>
#include <drogon/nosql/RedisClient.h>
#include <drogon/Cookie.h>
#include <trantor/net/Resolver.h>
#include <trantor/net/EventLoop.h>
#include <trantor/utils/NonCopyable.h>
#include <functional>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
#include <chrono>
#if defined(__APPLE__) && defined(__MACH__) && \
    (defined(__ENVIRONMENT_IPHONE_OS__) ||     \
     defined(__IPHONE_OS_VERSION_MIN_REQUIRED))
#define TARGET_OS_IOS 1
#else
#define TARGET_OS_IOS 0
#endif
namespace drogon
{
const char banner[] =
    "     _                             \n"
    "  __| |_ __ ___   __ _  ___  _ __  \n"
    " / _` | '__/ _ \\ / _` |/ _ \\| '_ \\ \n"
    "| (_| | | | (_) | (_| | (_) | | | |\n"
    " \\__,_|_|  \\___/ \\__, |\\___/|_| |_|\n"
    "                 |___/             \n";
DROGON_EXPORT std::string getVersion();
DROGON_EXPORT std::string getGitCommit();
class HttpControllerBase;
class HttpSimpleControllerBase;
class WebSocketControllerBase;
using ExceptionHandler =
    std::function<void(const std::exception &,
                       const HttpRequestPtr &,
                       std::function<void(const HttpResponsePtr &)> &&)>;
using DefaultHandler =
    std::function<void(const HttpRequestPtr &,
                       std::function<void(const HttpResponsePtr &)> &&)>;
using HttpHandlerInfo = std::tuple<std::string, HttpMethod, std::string>;
#ifdef __cpp_impl_coroutine
class HttpAppFramework;
namespace internal
{
struct [[nodiscard]] ForwardAwaiter
    : public CallbackAwaiter<drogon::HttpResponsePtr>
{
  public:
    ForwardAwaiter(drogon::HttpRequestPtr &&req,
                   std::string &&host,
                   double timeout,
                   HttpAppFramework &app)
        : req_(std::move(req)),
          host_(std::move(host)),
          timeout_(timeout),
          app_(app)
    {
    }
    void await_suspend(std::coroutine_handle<> handle) noexcept;
  private:
    drogon::HttpRequestPtr req_;
    std::string host_;
    double timeout_;
    HttpAppFramework &app_;
};
}  
#endif
class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
{
  public:
    virtual ~HttpAppFramework() = default;
    static HttpAppFramework &instance();
    virtual void run() = 0;
    virtual bool isRunning() = 0;
    virtual void quit() = 0;
    virtual trantor::EventLoop *getLoop() const = 0;
    virtual trantor::EventLoop *getIOLoop(size_t id) const = 0;
    virtual HttpAppFramework &setCustom404Page(const HttpResponsePtr &resp,
                                               bool set404 = true) = 0;
    virtual HttpAppFramework &setCustomErrorHandler(
        std::function<HttpResponsePtr(HttpStatusCode,
                                      const HttpRequestPtr &req)>
            &&resp_generator) = 0;
    HttpAppFramework &setCustomErrorHandler(
        std::function<HttpResponsePtr(HttpStatusCode)> &&resp_generator)
    {
        return setCustomErrorHandler(
            [cb = std::move(resp_generator)](HttpStatusCode code,
                                             const HttpRequestPtr &) {
                return cb(code);
            });
    }
    virtual const std::function<HttpResponsePtr(HttpStatusCode,
                                                const HttpRequestPtr &req)> &
    getCustomErrorHandler() const = 0;
    template <typename T>
    T *getPlugin()
    {
        static_assert(IsPlugin<T>::value,
                      "The Template parameter must be a subclass of "
                      "PluginBase");
        assert(isRunning());
        static auto pluginPtr =
            dynamic_cast<T *>(getPlugin(T::classTypeName()));
        return pluginPtr;
    }
    template <typename T>
    std::shared_ptr<T> getSharedPlugin()
    {
        static_assert(IsPlugin<T>::value,
                      "The Template parameter must be a subclass of "
                      "PluginBase");
        assert(isRunning());
        static auto pluginPtr =
            std::dynamic_pointer_cast<T>(getSharedPlugin(T::classTypeName()));
        return pluginPtr;
    }
    virtual PluginBase *getPlugin(const std::string &name) = 0;
    virtual std::shared_ptr<PluginBase> getSharedPlugin(
        const std::string &name) = 0;
    virtual HttpAppFramework &registerBeginningAdvice(
        const std::function<void()> &advice) = 0;
    virtual HttpAppFramework &registerNewConnectionAdvice(
        const std::function<bool(const trantor::InetAddress &,
                                 const trantor::InetAddress &)> &advice) = 0;
    virtual HttpAppFramework &registerHttpResponseCreationAdvice(
        const std::function<void(const HttpResponsePtr &)> &advice) = 0;
    virtual HttpAppFramework &registerSyncAdvice(
        const std::function<HttpResponsePtr(const HttpRequestPtr &)>
            &advice) = 0;
    virtual HttpAppFramework &registerPreRoutingAdvice(
        const std::function<void(const HttpRequestPtr &,
                                 AdviceCallback &&,
                                 AdviceChainCallback &&)> &advice) = 0;
    virtual HttpAppFramework &registerPreRoutingAdvice(
        const std::function<void(const HttpRequestPtr &)> &advice) = 0;
    virtual HttpAppFramework &registerPostRoutingAdvice(
        const std::function<void(const HttpRequestPtr &,
                                 AdviceCallback &&,
                                 AdviceChainCallback &&)> &advice) = 0;
    virtual HttpAppFramework &registerPostRoutingAdvice(
        const std::function<void(const HttpRequestPtr &)> &advice) = 0;
    virtual HttpAppFramework &registerPreHandlingAdvice(
        const std::function<void(const HttpRequestPtr &,
                                 AdviceCallback &&,
                                 AdviceChainCallback &&)> &advice) = 0;
    virtual HttpAppFramework &registerPreHandlingAdvice(
        const std::function<void(const HttpRequestPtr &)> &advice) = 0;
    virtual HttpAppFramework &registerPostHandlingAdvice(
        const std::function<void(const HttpRequestPtr &,
                                 const HttpResponsePtr &)> &advice) = 0;
    virtual HttpAppFramework &registerPreSendingAdvice(
        const std::function<void(const HttpRequestPtr &,
                                 const HttpResponsePtr &)> &advice) = 0;
    virtual HttpAppFramework &setupFileLogger() = 0;
    virtual HttpAppFramework &loadConfigFile(
        const std::string &fileName) noexcept(false) = 0;
    virtual HttpAppFramework &loadConfigJson(const Json::Value &data) noexcept(
        false) = 0;
    virtual HttpAppFramework &loadConfigJson(Json::Value &&data) noexcept(
        false) = 0;
    virtual HttpAppFramework &registerHttpSimpleController(
        const std::string &pathName,
        const std::string &ctrlName,
        const std::vector<internal::HttpConstraint> &constraints = {}) = 0;
    template <typename FUNCTION>
    HttpAppFramework &registerHandler(
        const std::string &pathPattern,
        FUNCTION &&function,
        const std::vector<internal::HttpConstraint> &constraints = {},
        const std::string &handlerName = "")
    {
        LOG_TRACE << "pathPattern:" << pathPattern;
        auto binder = std::make_shared<internal::HttpBinder<FUNCTION>>(
            std::forward<FUNCTION>(function));
        getLoop()->queueInLoop([binder]() { binder->createHandlerInstance(); });
        std::vector<HttpMethod> validMethods;
        std::vector<std::string> middlewares;
        for (auto const &constraint : constraints)
        {
            if (constraint.type() == internal::ConstraintType::HttpMiddleware)
            {
                middlewares.push_back(constraint.getMiddlewareName());
            }
            else if (constraint.type() == internal::ConstraintType::HttpMethod)
            {
                validMethods.push_back(constraint.getHttpMethod());
            }
            else
            {
                LOG_ERROR << "Invalid controller constraint type";
                exit(1);
            }
        }
        registerHttpController(
            pathPattern, binder, validMethods, middlewares, handlerName);
        return *this;
    }
    template <typename FUNCTION>
    HttpAppFramework &registerHandlerViaRegex(
        const std::string &regExp,
        FUNCTION &&function,
        const std::vector<internal::HttpConstraint> &constraints = {},
        const std::string &handlerName = "")
    {
        LOG_TRACE << "regex:" << regExp;
        internal::HttpBinderBasePtr binder;
        binder = std::make_shared<internal::HttpBinder<FUNCTION>>(
            std::forward<FUNCTION>(function));
        std::vector<HttpMethod> validMethods;
        std::vector<std::string> middlewares;
        for (auto const &constraint : constraints)
        {
            if (constraint.type() == internal::ConstraintType::HttpMiddleware)
            {
                middlewares.push_back(constraint.getMiddlewareName());
            }
            else if (constraint.type() == internal::ConstraintType::HttpMethod)
            {
                validMethods.push_back(constraint.getHttpMethod());
            }
            else
            {
                LOG_ERROR << "Invalid controller constraint type";
                exit(1);
            }
        }
        registerHttpControllerViaRegex(
            regExp, binder, validMethods, middlewares, handlerName);
        return *this;
    }
    virtual HttpAppFramework &registerWebSocketController(
        const std::string &pathName,
        const std::string &ctrlName,
        const std::vector<internal::HttpConstraint> &constraints = {}) = 0;
    virtual HttpAppFramework &registerWebSocketControllerRegex(
        const std::string &regExp,
        const std::string &ctrlName,
        const std::vector<internal::HttpConstraint> &constraints =
            std::vector<internal::HttpConstraint>{}) = 0;
    template <typename T>
    HttpAppFramework &registerController(const std::shared_ptr<T> &ctrlPtr)
    {
        static_assert((std::is_base_of<HttpControllerBase, T>::value ||
                       std::is_base_of<HttpSimpleControllerBase, T>::value ||
                       std::is_base_of<WebSocketControllerBase, T>::value),
                      "Error! Only controller objects can be registered here");
        static_assert(!T::isAutoCreation,
                      "Controllers created and initialized "
                      "automatically by drogon cannot be "
                      "registered here");
        DrClassMap::setSingleInstance(ctrlPtr);
        T::initPathRouting();
        return *this;
    }
    template <typename T>
    HttpAppFramework &registerFilter(const std::shared_ptr<T> &filterPtr)
    {
        static_assert(std::is_base_of<HttpFilterBase, T>::value,
                      "Error! Only filter objects can be registered here");
        static_assert(!T::isAutoCreation,
                      "Filters created and initialized "
                      "automatically by drogon cannot be "
                      "registered here");
        DrClassMap::setSingleInstance(filterPtr);
        return *this;
    }
    template <typename T>
    HttpAppFramework &registerMiddleware(
        const std::shared_ptr<T> &middlewarePtr)
    {
        static_assert(std::is_base_of<HttpMiddlewareBase, T>::value,
                      "Error! Only middleware objects can be registered here");
        static_assert(!T::isAutoCreation,
                      "Middleware created and initialized "
                      "automatically by drogon cannot be "
                      "registered here");
        DrClassMap::setSingleInstance(middlewarePtr);
        return *this;
    }
    virtual HttpAppFramework &setDefaultHandler(DefaultHandler handler) = 0;
    virtual void forward(
        const HttpRequestPtr &req,
        std::function<void(const HttpResponsePtr &)> &&callback,
        const std::string &hostString = "",
        double timeout = 0) = 0;
#ifdef __cpp_impl_coroutine
    internal::ForwardAwaiter forwardCoro(HttpRequestPtr req,
                                         std::string hostString = "",
                                         double timeout = 0)
    {
        return internal::ForwardAwaiter(std::move(req),
                                        std::move(hostString),
                                        timeout,
                                        *this);
    }
#endif
    virtual std::vector<HttpHandlerInfo> getHandlersInfo() const = 0;
    virtual const Json::Value &getCustomConfig() const = 0;
    virtual HttpAppFramework &setThreadNum(size_t threadNum) = 0;
    virtual size_t getThreadNum() const = 0;
    virtual HttpAppFramework &setSSLFiles(const std::string &certPath,
                                          const std::string &keyPath) = 0;
    virtual HttpAppFramework &setSSLConfigCommands(
        const std::vector<std::pair<std::string, std::string>>
            &sslConfCmds) = 0;
    virtual HttpAppFramework &reloadSSLFiles() = 0;
    virtual void addPlugins(const Json::Value &configs) = 0;
    virtual void addPlugin(const std::string &name,
                           const std::vector<std::string> &dependencies,
                           const Json::Value &config) = 0;
    virtual HttpAppFramework &addListener(
        const std::string &ip,
        uint16_t port,
        bool useSSL = false,
        const std::string &certFile = "",
        const std::string &keyFile = "",
        bool useOldTLS = false,
        const std::vector<std::pair<std::string, std::string>> &sslConfCmds =
            {}) = 0;
    virtual HttpAppFramework &enableSession(
        const size_t timeout = 0,
        Cookie::SameSite sameSite = Cookie::SameSite::kNull,
        const std::string &cookieKey = "JSESSIONID",
        int maxAge = -1,
        std::function<std::string()> idGeneratorCallback = nullptr) = 0;
    inline HttpAppFramework &enableSession(
        const std::chrono::duration<double> &timeout,
        Cookie::SameSite sameSite = Cookie::SameSite::kNull,
        const std::string &cookieKey = "JSESSIONID",
        int maxAge = -1,
        std::function<std::string()> idGeneratorCallback = nullptr)
    {
        return enableSession((size_t)timeout.count(),
                             sameSite,
                             cookieKey,
                             maxAge,
                             idGeneratorCallback);
    }
    virtual HttpAppFramework &registerSessionStartAdvice(
        const AdviceStartSessionCallback &advice) = 0;
    virtual HttpAppFramework &registerSessionDestroyAdvice(
        const AdviceDestroySessionCallback &advice) = 0;
    virtual HttpAppFramework &disableSession() = 0;
    virtual HttpAppFramework &setDocumentRoot(const std::string &rootPath) = 0;
    virtual const std::string &getDocumentRoot() const = 0;
    virtual HttpAppFramework &setStaticFileHeaders(
        const std::vector<std::pair<std::string, std::string>> &headers) = 0;
    virtual HttpAppFramework &addALocation(
        const std::string &uriPrefix,
        const std::string &defaultContentType = "",
        const std::string &alias = "",
        bool isCaseSensitive = false,
        bool allowAll = true,
        bool isRecursive = true,
        const std::vector<std::string> &middlewareNames = {}) = 0;
    virtual HttpAppFramework &setUploadPath(const std::string &uploadPath) = 0;
    virtual const std::string &getUploadPath() const = 0;
    virtual HttpAppFramework &setFileTypes(
        const std::vector<std::string> &types) = 0;
#if !defined(_WIN32) && !TARGET_OS_IOS
    virtual HttpAppFramework &enableDynamicViewsLoading(
        const std::vector<std::string> &libPaths,
        const std::string &outputPath = "") = 0;
#endif
    virtual HttpAppFramework &setMaxConnectionNum(size_t maxConnections) = 0;
    virtual HttpAppFramework &setMaxConnectionNumPerIP(
        size_t maxConnectionsPerIP) = 0;
    virtual HttpAppFramework &enableRunAsDaemon() = 0;
    virtual HttpAppFramework &disableSigtermHandling() = 0;
    virtual HttpAppFramework &enableRelaunchOnError() = 0;
    virtual HttpAppFramework &setLogPath(
        const std::string &logPath,
        const std::string &logfileBaseName = "",
        size_t logSize = 100000000,
        size_t maxFiles = 0,
        bool useSpdlog = false) = 0;
    virtual HttpAppFramework &setLogLevel(trantor::Logger::LogLevel level) = 0;
    virtual HttpAppFramework &setLogLocalTime(bool on) = 0;
    virtual HttpAppFramework &enableSendfile(bool sendFile) = 0;
    virtual HttpAppFramework &enableGzip(bool useGzip) = 0;
    virtual bool isGzipEnabled() const = 0;
    virtual HttpAppFramework &enableBrotli(bool useBrotli) = 0;
    virtual bool isBrotliEnabled() const = 0;
    virtual HttpAppFramework &setStaticFilesCacheTime(int cacheTime) = 0;
    virtual int staticFilesCacheTime() const = 0;
    virtual HttpAppFramework &setIdleConnectionTimeout(size_t timeout) = 0;
    inline HttpAppFramework &setIdleConnectionTimeout(
        const std::chrono::duration<double> &timeout)
    {
        return setIdleConnectionTimeout((size_t)timeout.count());
    }
    virtual HttpAppFramework &setServerHeaderField(
        const std::string &server) = 0;
    virtual HttpAppFramework &enableServerHeader(bool flag) = 0;
    virtual HttpAppFramework &enableDateHeader(bool flag) = 0;
    virtual HttpAppFramework &setKeepaliveRequestsNumber(
        const size_t number) = 0;
    virtual HttpAppFramework &setPipeliningRequestsNumber(
        const size_t number) = 0;
    virtual HttpAppFramework &setGzipStatic(bool useGzipStatic) = 0;
    virtual HttpAppFramework &setBrStatic(bool useGzipStatic) = 0;
    virtual HttpAppFramework &setClientMaxBodySize(size_t maxSize) = 0;
    virtual HttpAppFramework &setClientMaxMemoryBodySize(size_t maxSize) = 0;
    virtual HttpAppFramework &setClientMaxWebSocketMessageSize(
        size_t maxSize) = 0;
    virtual HttpAppFramework &setHomePage(const std::string &homePageFile) = 0;
    virtual HttpAppFramework &setTermSignalHandler(
        const std::function<void()> &handler) = 0;
    virtual HttpAppFramework &setIntSignalHandler(
        const std::function<void()> &handler) = 0;
    virtual const std::string &getHomePage() const = 0;
    virtual HttpAppFramework &setImplicitPageEnable(bool useImplicitPage) = 0;
    virtual bool isImplicitPageEnabled() const = 0;
    virtual HttpAppFramework &setImplicitPage(
        const std::string &implicitPageFile) = 0;
    virtual const std::string &getImplicitPage() const = 0;
    virtual orm::DbClientPtr getDbClient(
        const std::string &name = "default") = 0;
    virtual orm::DbClientPtr getFastDbClient(
        const std::string &name = "default") = 0;
    virtual bool areAllDbClientsAvailable() const noexcept = 0;
    virtual nosql::RedisClientPtr getRedisClient(
        const std::string &name = "default") = 0;
    virtual nosql::RedisClientPtr getFastRedisClient(
        const std::string &name = "default") = 0;
    virtual HttpAppFramework &setJsonParserStackLimit(
        size_t limit) noexcept = 0;
    virtual size_t getJsonParserStackLimit() const noexcept = 0;
    virtual HttpAppFramework &setUnicodeEscapingInJson(
        bool enable) noexcept = 0;
    virtual bool isUnicodeEscapingUsedInJson() const noexcept = 0;
    virtual HttpAppFramework &setFloatPrecisionInJson(
        unsigned int precision,
        const std::string &precisionType = "significant") noexcept = 0;
    virtual const std::pair<unsigned int, std::string> &
    getFloatPrecisionInJson() const noexcept = 0;
    [[deprecated("Use addDbClient() instead")]] virtual HttpAppFramework &
    createDbClient(const std::string &dbType,
                   const std::string &host,
                   unsigned short port,
                   const std::string &databaseName,
                   const std::string &userName,
                   const std::string &password,
                   size_t connectionNum = 1,
                   const std::string &filename = "",
                   const std::string &name = "default",
                   bool isFast = false,
                   const std::string &characterSet = "",
                   double timeout = -1.0,
                   bool autoBatch = false) = 0;
    virtual HttpAppFramework &addDbClient(const orm::DbConfig &config) = 0;
    virtual HttpAppFramework &createRedisClient(
        const std::string &ip,
        unsigned short port,
        const std::string &name = "default",
        const std::string &password = "",
        size_t connectionNum = 1,
        bool isFast = false,
        double timeout = -1.0,
        unsigned int db = 0,
        const std::string &username = "") = 0;
    virtual const std::shared_ptr<trantor::Resolver> &getResolver() const = 0;
    virtual bool supportSSL() const = 0;
    virtual size_t getCurrentThreadIndex() const = 0;
    virtual std::vector<trantor::InetAddress> getListeners() const = 0;
    virtual void enableReusePort(bool enable = true) = 0;
    virtual bool reusePort() const = 0;
    virtual HttpAppFramework &setExceptionHandler(ExceptionHandler handler) = 0;
    virtual const ExceptionHandler &getExceptionHandler() const = 0;
    virtual HttpAppFramework &registerCustomExtensionMime(
        const std::string &ext,
        const std::string &mime) = 0;
    virtual HttpAppFramework &enableCompressedRequest(bool enable = true) = 0;
    virtual bool isCompressedRequestEnabled() const = 0;
    virtual int64_t getConnectionCount() const = 0;
    virtual HttpAppFramework &setBeforeListenSockOptCallback(
        std::function<void(int)> cb) = 0;
    virtual HttpAppFramework &setAfterAcceptSockOptCallback(
        std::function<void(int)> cb) = 0;
    virtual HttpAppFramework &setConnectionCallback(
        std::function<void(const trantor::TcpConnectionPtr &)> cb) = 0;
    virtual HttpAppFramework &enableRequestStream(bool enable = true) = 0;
    virtual bool isRequestStreamEnabled() const = 0;
  private:
    virtual void registerHttpController(
        const std::string &pathPattern,
        const internal::HttpBinderBasePtr &binder,
        const std::vector<HttpMethod> &validMethods = {},
        const std::vector<std::string> &middlewareNames = {},
        const std::string &handlerName = "") = 0;
    virtual void registerHttpControllerViaRegex(
        const std::string &regExp,
        const internal::HttpBinderBasePtr &binder,
        const std::vector<HttpMethod> &validMethods,
        const std::vector<std::string> &middlewareNames,
        const std::string &handlerName) = 0;
};
inline HttpAppFramework &app()
{
    return HttpAppFramework::instance();
}
#ifdef __cpp_impl_coroutine
namespace internal
{
inline void ForwardAwaiter::await_suspend(
    std::coroutine_handle<> handle) noexcept
{
    app_.forward(
        req_,
        [this, handle](const drogon::HttpResponsePtr &resp) {
            setValue(resp);
            handle.resume();
        },
        host_,
        timeout_);
}
}  
#endif
}  