#pragma once

#include <balancer/kernel/helpers/errors.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/module/conn_descr.h>

#include <library/cpp/regex/pire/regexp.h>

using namespace NRegExp;

namespace NSrvKernel {
    TRawIpV6 MaskedIpV6(const struct sockaddr_in6& v6addr, int prefixLen) noexcept {
        TRawIpV6 parts;
        memcpy(&parts, &v6addr.sin6_addr.s6_addr, sizeof(v6addr.sin6_addr.s6_addr));
        TRawIpV6 masked(0, 0);

        if (prefixLen <= 64) {
            masked.first = SwapBytes64(SwapBytes64(parts.first) & ((ui64) (~0) << (64 - prefixLen)));
            return masked;
        }

        prefixLen -= 64;

        masked.first = parts.first;
        masked.second = SwapBytes64(SwapBytes64(parts.second) & (ui64) (~0) << (64 - prefixLen));
        return masked;
    }

    TErrorOr<ui64> CalcHashSubnet(const TConnDescr& descr, const TFsm* ipFsm, unsigned v4Mask, unsigned v6Mask) noexcept {
        const sockaddr* sa = nullptr;
        NAddr::TOpaqueAddr addr;

        if (ipFsm) {
            for (auto& header : descr.Request->Headers()) {
                TMatcher m(*ipFsm);

                for (auto& headerVal : header.second) {
                    if (Match(m, header.first.AsStringBuf()).Final() && !headerVal.Empty()) {
                        const TStringBuf str = headerVal.AsStringBuf();

                        if (Parse(&addr, str)) {
                            sa = addr.Addr();
                            break;
                        }
                    }
                }
            }
        }

        if (!sa) {
            sa = descr.RemoteAddr().Addr();
        }

        switch (sa->sa_family) {
            case AF_INET: {
                const ui32 addr = InetToHost(((const sockaddr_in*)sa)->sin_addr.s_addr);
                const ui32 tmp = addr >> (32 - v4Mask);

                return Crc<ui64>(&tmp, sizeof(tmp), 0);
            }
            case AF_INET6: {
                const TRawIpV6 addr = MaskedIpV6(*((const sockaddr_in6*)sa), v6Mask);

                return Crc<ui64>(&addr, sizeof(addr), 0);
            }
        }

        return static_cast<ui64>(0);
    }
}

