// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#ifndef QHTTPSERVERROUTERRULE_H
#define QHTTPSERVERROUTERRULE_H

#include <QtHttpServer/qhttpserverrequest.h>
#include <QtHttpServer/qhttpserverresponder.h>
#include <QtHttpServer/qhttpserverrouterviewtraits.h>

#include <QtCore/qcontainerfwd.h>
#include <QtCore/qregularexpression.h>

#include <initializer_list>
#include <memory>

QT_BEGIN_NAMESPACE

class QString;
class QHttpServerRequest;
class QHttpServerResponder;
class QRegularExpressionMatch;
class QHttpServerRouter;

class QHttpServerRouterRulePrivate;
class Q_HTTPSERVER_EXPORT QHttpServerRouterRule
{
    Q_DECLARE_PRIVATE(QHttpServerRouterRule)
    Q_DISABLE_COPY_MOVE(QHttpServerRouterRule)

private:
    using RouterHandlerPrototype = void (*)(const QRegularExpressionMatch &,
                                            const QHttpServerRequest &, QHttpServerResponder &);

    template <typename T>
    using if_routerhandler_prototype_compatible = typename std::enable_if<
            QtPrivate::AreFunctionsCompatible<RouterHandlerPrototype, T>::value, bool>::type;

    QHttpServerRouterRule(const QString &pathPattern, const QHttpServerRequest::Methods methods,
                          const QObject *context, QtPrivate::QSlotObjectBase *slotObjRaw);

public:
#ifdef Q_QDOC
    template <typename Functor>
    QHttpServerRouterRule(
            const QString &pathPattern, const QHttpServerRequest::Methods methods,
            const QObject *receiver,
            Functor &&slot);

    template <typename Functor>
    QHttpServerRouterRule(
            const QString &pathPattern,
            const QObject *receiver,
            Functor &&slot);
#else
    template <typename Handler, if_routerhandler_prototype_compatible<Handler> = true>
    QHttpServerRouterRule(
            const QString &pathPattern,
            const typename QtPrivate::ContextTypeForFunctor<Handler>::ContextType *context,
            Handler &&func)
        : QHttpServerRouterRule(
                  pathPattern, QHttpServerRequest::Method::AnyKnown, context,
                  QtPrivate::makeCallableObject<RouterHandlerPrototype>(std::forward<Handler>(func)))
    {
    }

    template <typename Handler, if_routerhandler_prototype_compatible<Handler> = true>
    QHttpServerRouterRule(
            const QString &pathPattern, const QHttpServerRequest::Methods methods,
            const typename QtPrivate::ContextTypeForFunctor<Handler>::ContextType *context,
            Handler &&func)
        : QHttpServerRouterRule(
                  pathPattern, methods, context,
                  QtPrivate::makeCallableObject<RouterHandlerPrototype>(std::forward<Handler>(func)))
    {
    }
#endif

#ifdef Q_QDOC
    template <typename Functor, typename ViewTraits = QHttpServerRouterViewTraits<Functor>>
    static typename ViewTraits::BindableType bindCaptured(
                    QObject *receiver,
                    Functor &&slot,
                    const QRegularExpressionMatch &match) const;
# else
    template<typename ViewHandler, typename ViewTraits = QHttpServerRouterViewTraits<ViewHandler>>
    static typename ViewTraits::BindableType bindCaptured(
                    const typename QtPrivate::ContextTypeForFunctor<ViewHandler>::ContextType *context,
                    ViewHandler &&handler,
                    const QRegularExpressionMatch &match)
    {
        return bindCapturedImpl<ViewHandler, ViewTraits>(
                context, std::forward<ViewHandler>(handler), match,
                typename ViewTraits::Arguments::CapturableIndexes{});
    }
#endif

    const QObject *contextObject() const;

    virtual ~QHttpServerRouterRule();

protected:
    bool exec(const QHttpServerRequest &request, QHttpServerResponder &responder) const;

    bool hasValidMethods() const;

    bool createPathRegexp(std::initializer_list<QMetaType> metaTypes,
                          const QHash<QMetaType, QString> &converters);

    virtual bool matches(const QHttpServerRequest &request,
                         QRegularExpressionMatch *match) const;

    QHttpServerRouterRule(QHttpServerRouterRulePrivate *d);

    // Implementation of C++20 std::bind_front() in C++17
    template<typename F, typename... Args>
    static auto bind_front(F &&f, Args &&...args)
    {
        return [f = std::forward<F>(f),
                args = std::make_tuple(std::forward<Args>(args)...)](auto &&...callArgs) {
            return std::apply(f,
                              std::tuple_cat(args,
                                             std::forward_as_tuple(std::forward<decltype(callArgs)>(
                                                     callArgs)...)));
        };
    }

    template<typename ViewHandler, typename ViewTraits, int... Cx>
    static typename ViewTraits::BindableType bindCapturedImpl(
                const typename QtPrivate::ContextTypeForFunctor<ViewHandler>::ContextType *context,
                ViewHandler &&handler,
                const QRegularExpressionMatch &match,
                QtPrivate::IndexesList<Cx...>)
    {
        if constexpr (std::is_member_function_pointer_v<ViewHandler>) {
            return bind_front(
                handler, const_cast<typename QtPrivate::ContextTypeForFunctor<ViewHandler>::ContextType*>(context),
                QVariant(match.captured(Cx + 1))
                        .value<typename ViewTraits::Arguments::template Arg<Cx>::CleanType>()...);
        } else {
            Q_UNUSED(context);
            return bind_front(
                    handler,
                    QVariant(match.captured(Cx + 1))
                            .value<typename ViewTraits::Arguments::template Arg<Cx>::CleanType>()...);
        }
    }

private:
    std::unique_ptr<QHttpServerRouterRulePrivate> d_ptr;

    friend class QHttpServerRouter;
};

QT_END_NAMESPACE

#endif // QHTTPSERVERROUTERRULE_H
