#include <drogon/plugins/SlashRemover.h>
#include <drogon/plugins/Redirector.h>
#include <drogon/HttpAppFramework.h>
#include "drogon/utils/FunctionTraits.h"
#include <cstddef>
#include <cstdint>
#include <functional>
#include <string>
#include <string_view>
#include <utility>
using namespace drogon;
using namespace drogon::plugin;
using std::string;
using std::string_view;
enum removeSlashMode : uint8_t
{
    trailing = 1 << 0,
    duplicate = 1 << 1,
    both = trailing | duplicate,
};
static inline size_t findTrailingSlashes(string_view url)
{
    auto len = url.size();
    if (len < 2 || url.back() != '/')
        return string::npos;
    size_t a = len - 1;  
    while (--a > 0 && url[a] == '/')
        ;  
    return a;
}
static inline void removeTrailingSlashes(string &url,
                                         size_t start,
                                         string_view originalUrl)
{
    url = originalUrl.substr(0, start + 1);
}
static inline size_t findDuplicateSlashes(string_view url)
{
    size_t len = url.size();
    if (len < 2)
        return string::npos;
    bool startedPair = true;  
    for (size_t a = 1; a < len; ++a)
    {
        if (url[a] != '/')  
        {
            startedPair = false;
            continue;
        }
        if (startedPair)  
            return a;
        startedPair = true;
    }
    return string::npos;
}
static inline void removeDuplicateSlashes(string &url, size_t start)
{
    for (size_t b = (start--) + 1, len = url.size(); b < len; ++b)
    {
        const char c = url[b];
        if (c != '/' || url[start] != '/')
        {
            ++start;
            url[start] = c;
        }
    }
    url.resize(start + 1);
}
static inline std::pair<size_t, size_t> findExcessiveSlashes(string_view url)
{
    size_t len = url.size();
    if (len < 2)  
        return {string::npos, string::npos};
    size_t trailIdx = len;  
    while (--trailIdx > 0 && url[trailIdx] == '/')
        ;  
    if (trailIdx == 0)
        return {
            0,             
            string::npos,  
        };
    size_t dupIdx = 1;
    for (bool startedPair = true; dupIdx < trailIdx;
         ++dupIdx)  
    {
        if (url[dupIdx] != '/')  
        {
            startedPair = false;
            continue;
        }
        if (startedPair)  
            break;
        startedPair = true;
    }
    if (dupIdx == trailIdx)
        return {
            trailIdx != len - 1
                ?  
                trailIdx
                : string::npos,  
            string::npos,        
        };
    return {
        trailIdx != len - 1
            ?  
            trailIdx
            : string::npos,  
        dupIdx,
    };
}
static inline void removeExcessiveSlashes(string &url,
                                          std::pair<size_t, size_t> start,
                                          string_view originalUrl)
{
    if (start.first != string::npos)
        removeTrailingSlashes(url, start.first, originalUrl);
    else
        url = originalUrl;
    if (start.second != string::npos)
        removeDuplicateSlashes(url, start.second);
}
static inline bool handleReq(const drogon::HttpRequestPtr &req,
                             uint8_t removeMode)
{
    switch (removeMode)
    {
        case trailing:
        {
            auto find = findTrailingSlashes(req->path());
            if (find == string::npos)
                return false;
            string newPath;
            removeTrailingSlashes(newPath, find, req->path());
            req->setPath(std::move(newPath));
            break;
        }
        case duplicate:
        {
            auto find = findDuplicateSlashes(req->path());
            if (find == string::npos)
                return false;
            string newPath = req->path();
            removeDuplicateSlashes(newPath, find);
            req->setPath(std::move(newPath));
            break;
        }
        case both:
        default:
        {
            auto find = findExcessiveSlashes(req->path());
            if (find.first == string::npos && find.second == string::npos)
                return false;
            string newPath;
            removeExcessiveSlashes(newPath, find, req->path());
            req->setPath(std::move(newPath));
            break;
        }
    }
    return true;
}
void SlashRemover::initAndStart(const Json::Value &config)
{
    trailingSlashes_ = config.get("remove_trailing_slashes", true).asBool();
    duplicateSlashes_ = config.get("remove_duplicate_slashes", true).asBool();
    redirect_ = config.get("redirect", true).asBool();
    const uint8_t removeMode =
        (trailingSlashes_ * trailing) | (duplicateSlashes_ * duplicate);
    if (!removeMode)
        return;
    auto redirector = app().getPlugin<Redirector>();
    if (!redirector)
    {
        LOG_ERROR << "Redirector plugin is not found!";
        return;
    }
    auto func = [removeMode](const HttpRequestPtr &req) -> bool {
        return handleReq(req, removeMode);
    };
    if (redirect_)
    {
        redirector->registerPathRewriteHandler(std::move(func));
    }
    else
    {
        redirector->registerForwardHandler(std::move(func));
    }
}
void SlashRemover::shutdown()
{
    LOG_TRACE << "SlashRemover plugin is shutdown!";
}