#include "http2_common.h"
#include "http2_headers_fsm.h"

#include <balancer/kernel/http/parser/common_headers.h>
#include <balancer/kernel/regexp/regexp_pire.h>

#include <util/string/util.h>

#include <algorithm>

namespace NSrvKernel::NHTTP2 {
    namespace {
        TMatcher& Match(TMatcher& matcher, const TChunkList& lst) noexcept {
            for (auto it = lst.ChunksBegin(); it != lst.ChunksEnd(); ++it) {
                matcher.Match(it->Data(), it->Length());
            }

            return matcher;
        }

        inline bool Match(const TFsm& fsm, const TChunkList& lst) noexcept {
            TMatcher matcher{ fsm };
            return Match(matcher, lst).Final();
        }

        class THeadersFSMs {
        public:
            static const THeadersFSMs& Instance() noexcept {
                return Default<THeadersFSMs>();
            }

        public:
            const TFsm ReqSpecName{
                TFsm(REQ_HEADER_METHOD) |
                TFsm(REQ_HEADER_SCHEME) |
                TFsm(REQ_HEADER_PATH) |
                TFsm(REQ_HEADER_AUTHORITY)
            };

            const TFsm ProhibitedName{
                TFsm(HEADER_CONNECTION, TFsm::TOptions().SetCaseInsensitive(true)) |
                TFsm(HEADER_KEEP_ALIVE, TFsm::TOptions().SetCaseInsensitive(true)) |
                TFsm(HEADER_PROXY_CONNECTION, TFsm::TOptions().SetCaseInsensitive(true)) |
                TTransferEncodingFsm::Instance() |
                TFsm(REQ_HEADER_UPGRADE, TFsm::TOptions().SetCaseInsensitive(true))
            };

            const TFsm HighPrioContentType{
                TFsm("text/(css|html|xml)",
                    TFsm::TOptions().SetCaseInsensitive(true).SetSurround(true)) |
                TFsm("application/(xhtml[+]|xslt[+])?xml",
                    TFsm::TOptions().SetCaseInsensitive(true).SetSurround(true)) |
                TFsm("(text|application)/(ecma|java|type)script",
                    TFsm::TOptions().SetCaseInsensitive(true).SetSurround(true)) |
                TFsm("font/",
                    TFsm::TOptions().SetCaseInsensitive(true).SetSurround(true))
            };

            const TFsm XYandexH2PrioEdge{
                RESP_HEADER_X_YANDEX_H2_PRIO_EDGE, TFsm::TOptions().SetCaseInsensitive(true)
            };

            // RFC 7230:
            //     field-name     = token
            //     token          = 1*tchar
            //     tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
            //                    / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
            //                    / DIGIT / ALPHA
            // RFC 7540: A request or response containing uppercase
            //           header field names MUST be treated as malformed
            const str_spn GoodForName{"a-z0-9!#$%&'*+._`|~^-", true};
        };
    }

    bool IsRequestHeaderSpecial(const TChunkList& name) noexcept {
        return Match(THeadersFSMs::Instance().ReqSpecName, name);
    }

    bool IsRequestHeaderSpecial(TStringBuf name) noexcept {
        return TMatcher(THeadersFSMs::Instance().ReqSpecName).Match(name).Final();
    }

    bool IsResponseHeaderSpecial(const TStringBuf name) noexcept{
        return RESP_HEADER_STATUS == name;
    }

    bool IsResponseHeaderSpecial(const TChunkList& name) noexcept{
        return RESP_HEADER_STATUS == name;
    }

    bool IsHeaderProhibited(const TChunkList& name) noexcept {
        return Match(THeadersFSMs::Instance().ProhibitedName, name);
    }

    bool IsHeaderProhibited(TStringBuf name) noexcept {
        return NSrvKernel::Match(THeadersFSMs::Instance().ProhibitedName, name);
    }

    bool IsRequestHeaderNameValid(const TChunkList& name) noexcept {
        size_t len = 0;
        for (auto it = name.ChunksBegin(); it != name.ChunksEnd(); ++it) {
            auto str = it->AsStringBuf();
            len += str.size();
            if (THeadersFSMs::Instance().GoodForName.cbrk(str.begin(), str.end()) != str.end()) {
                return false;
            }
        }
        return len > 0;
    }

    bool IsResponseHeaderNameValid(const TStringBuf name) noexcept {
        // TODO (velavokr): better checks?
        return !name.Empty();
    }

    bool IsRequestHeaderValueValid(const TChunkList& value) noexcept {
        // RFC 7230 (errata 4189):
        //     field-content  = field-vchar [ 1*( SP / HTAB / field-vchar )
        //                      field-vchar ]
        //     field-vchar    = VCHAR / obs-text
        constexpr TStringBuf badForValue("\0\n\r", 3);
        constexpr TStringBuf badForValueEnds = "\t ";

        {
            auto it = value.ChunksBegin();

            if (it != value.ChunksEnd() &&
                badForValueEnds.find(it->AsStringBuf().front()) != TStringBuf::npos
            ) {
                return false;
            }

            if (std::find_if(it, value.ChunksEnd(), [badForValue](const TChunk& item) {
                return item.AsStringBuf().find_first_of(badForValue) != TStringBuf::npos;
            }) != value.ChunksEnd()) {
                return false;
            }
        }

        {
            auto rit = value.ChunksRBegin();

            if (rit != value.ChunksREnd() &&
                badForValueEnds.find(rit->AsStringBuf().back()) != TStringBuf::npos) {
                return false;
            }
        }

        return true;
    }

    bool IsValueHighPrioContentType(TStringBuf contentType) noexcept {
        return NSrvKernel::Match(THeadersFSMs::Instance().HighPrioContentType, contentType);
    }

    bool IsValueHighPrioContentType(const TChunkList& contentType) noexcept {
        return Match(THeadersFSMs::Instance().HighPrioContentType, contentType);
    }

    bool IsXYandexH2PrioEdge(TStringBuf name) noexcept {
        return NSrvKernel::Match(THeadersFSMs::Instance().XYandexH2PrioEdge, name);
    }

    bool IsContentType(TStringBuf name) noexcept {
        return Match(TContentTypeFsm::Instance(), name);
    }

    bool IsTransferEncoding(const TChunkList& name) noexcept {
        return Match(TTransferEncodingFsm::Instance(), name);
    }

    bool IsTransferEncoding(TStringBuf name) noexcept {
        return Match(TTransferEncodingFsm::Instance(), name);
    }

    void InitHeadersFSMs() noexcept {
        (void)THeadersFSMs::Instance();
    }
}
