#include "search_size_chooser.h"

#include <util/generic/vector.h>
#include <util/string/split.h>

namespace NRTYServer {

static constexpr char DelimiterDerictives = ' ';
static constexpr char DelimiterNameAndVal = '=';
static constexpr char SegmentBeginChar = '[';
static constexpr char SegmentEndChar = ')';
static constexpr char DelimiterInSegment = ';';
static constexpr TStringBuf DefaultSearchSizeArgName = "Default";

class THnswSearchSizeChooser::TImpl {
public:
    TMaybe<TString> ParseErrorMessage;
    struct TDerictive {
        size_t TopSizeBegin = 0;
        size_t TopSizeEnd = 0;
        size_t SearchSize = 0;
    };

    TVector<TDerictive> Derictives;
    size_t DefaultSearchSize = 50;
};

THnswSearchSizeChooser::THnswSearchSizeChooser() {
    Impl.Reset(new TImpl);
}

THnswSearchSizeChooser::~THnswSearchSizeChooser() {}

size_t THnswSearchSizeChooser::GetRecommendedSearchSize(size_t requestedTopSize) const {
    size_t result = 0;

    for (const TImpl::TDerictive& d : Impl->Derictives) {
        if (requestedTopSize >= d.TopSizeBegin && requestedTopSize < d.TopSizeEnd) {
            result = Max(d.SearchSize, result);
        }
    }

    if (result == 0) {
        result = Impl->DefaultSearchSize;
    }
    return result;
}

void THnswSearchSizeChooser::ParseFromConfigStr(TStringBuf in) {
    if (!in) {
        return;
    }

    try {
        for (TStringBuf part = in.NextTok(DelimiterDerictives); part || in; part = in.NextTok(DelimiterDerictives)) {
            if (!part) {
                continue;
            }

            TStringBuf l, r;

            part.Split(DelimiterNameAndVal, l ,r);
            if (l == DefaultSearchSizeArgName) {
                Impl->DefaultSearchSize = FromString(r);
                continue;
            }

            Y_ENSURE(l.size() >= 5 && l.front() == SegmentBeginChar && l.back() == SegmentEndChar);
            l.Skip(1).Chop(1);

            TImpl::TDerictive res;
            Split(l, ';', res.TopSizeBegin, res.TopSizeEnd);
            res.SearchSize = FromString(r);
            Y_ENSURE(res.TopSizeBegin < res.TopSizeEnd);

            Impl->Derictives.push_back(res);
        }

    } catch (yexception& e) {
        Impl.Reset(new TImpl{});
        Impl->ParseErrorMessage = e.what();
    }
}

void THnswSearchSizeChooser::DoToString(IOutputStream& so) const {
    so << DefaultSearchSizeArgName << DelimiterNameAndVal << Impl->DefaultSearchSize;
    for (const TImpl::TDerictive& d : Impl->Derictives) {
        so << DelimiterDerictives << SegmentBeginChar
            << d.TopSizeBegin << DelimiterInSegment << d.TopSizeEnd
            << SegmentEndChar << DelimiterNameAndVal << d.SearchSize;
    }
}

TMaybe<TString> THnswSearchSizeChooser::GetParseErrorString() const {
    return Impl->ParseErrorMessage;
}

}
