#ifdef _WIN32
    #include "io.h"
    #include "fcntl.h"
#endif

#include <ysite/yandex/posfilter/filter_tree.h>
#include <ysite/yandex/posfilter/hits_holder.h>
#include <ysite/yandex/posfilter/leafhits.h>
#include <ysite/yandex/posfilter/quorum.h>
#include <ysite/yandex/posfilter/quorumiter.h>
#include <ysite/yandex/posfilter/tr_iterator.h>
#include <ysite/yandex/pure/pure.h>
#include <kernel/qtree/request/reqattrlist.h>
#include <kernel/qtree/richrequest/loadfreq.h>
#include <kernel/qtree/richrequest/richnode.h>

#include <library/cpp/charset/wide.h>
#include <library/cpp/getopt/opt.h>
#include <kernel/keyinv/hitlist/full_pos.h>
#include <kernel/keyinv/hitlist/hits_saver.h>

#include <util/generic/hash.h>
#include <util/generic/ptr.h>
#include <util/generic/string.h>
#include <util/system/defaults.h>

#include <cassert>
#include <cerrno>
#include <iostream>
#include <kernel/qtree/richrequest/printrichnode.h>
#include <util/string/split.h>

using namespace std;

struct TFakeIndex: public IHitsAccess {
private:
    typedef TAutoPtr<THitsForWrite> PHits;
    typedef THashMap<TString,PHits> THitsHash;
private:
    THitsHash HitsHash;
    TVector<TVector<TString> > Text;
    TPure Pure;

public:
    TFakeIndex()
    {
        Pure.Init();
    }

    void AddKeys(ui64 keyPrefix, const TString& key, TIntervalLimits, const IFormSelector*, TLeafHits *res, float *docFreqLnk, bool noFormFilter, IYndexFormNotification*) const override {
        LoadExactKey(keyPrefix, key.data(), res, noFormFilter);
        if (docFreqLnk)
            *docFreqLnk = 0;
    }
    void AddKeysSubstringMatch(ui64, const TString&, TLeafHits*, bool /*noFormFilter*/) const override
    {
    }

    void AddInterval(ui64 keyPrefix, const char* rangeBegin, const char* rangeEnd, TIntervalLimits /*limits*/, TLeafHits* res, EFormClass /*defaultFormClass*/) const override {
        for (auto&& i : HitsHash) {
            if (TString(i.first) >= TString(rangeBegin) && TString(i.first) < TString(rangeEnd)) {
                LoadExactKey(keyPrefix, i.first.data(), res, false);
            }
        }
    }

    void LoadExactKey(ui64, const char* sz, TLeafHits* res, bool) const override {
        THitsHash::const_iterator i = HitsHash.find(sz);
        if (i == HitsHash.end())
            return;
        THitsHolderPtr hitsHolder(new THitsHolder(TWordFormFlags(0)));
        hitsHolder->HitsData.Init(i->second->GetData(), i->second->GetLength(),
                                  i->second->GetCount(), HIT_FMT_RAW_I64);

        res->JustAdd(hitsHolder, (i32)i->second->GetCount());
    }

    void LoadExternalHits(THitsForRead*, const char *) override {
    }

    TLangMask GetLemmatizedLangMask() const override {
        return TLangMask(); // unused
    }

    TLangMask GetFlatBastardsLangMask() const override {
        return TLangMask(); // unused
    }

    void IndexDocument(const char *text);
    void TypeReqResult(const TString& query, const TReqAttrList& attrList);
private:
    void AddWords(const TString& text, ui32& br, ui32& pos);
    void TypeResult(TQuorumIterator *res);
    void TypePosition(SUPERLONG beg, SUPERLONG end);
    void AddWord(const TString &word, int br, int pos);
    EIndexType GetIndexType() const override { return IDX_TEXT; }
};

void TFakeIndex::AddWord(const TString &word, int br, int pos)
{
    int docId = 0;
    TWordPosition wp(docId, br, pos);
    PHits &pHits = HitsHash[word];
    if (!pHits) {
        pHits = new THitsForWrite;
        pHits->SetHitFormat(HIT_FMT_RAW_I64);
    }
    pHits->Add(wp.SuperLong());
    if (word[0] != '(' && word[0] != ')') {
        if (br >= (int)Text.size())
            Text.resize(br + 1);
        TVector<TString> &resBr = Text[br];
        if (pos >= (int)resBr.size())
            resBr.resize(pos + 1);
        resBr[pos] = word;
    }
}

void TFakeIndex::AddWords(const TString& text, ui32& br, ui32& pos) {
    TString word;
    for (const char *sz = text.data(); *sz; ++sz) {
        if (sz[0] == ' ' || sz[0] == '.') {
            if (!word.empty())
                AddWord(word, br, pos);
            if (sz[0] == '.') {
                pos = 1;
                ++br;
            } else {
                if (!word.empty() && word[0] != '(' && word[0] != ')')
                    ++pos;
            }
            word = "";
        } else
            word += *sz;
    }
    if (!word.empty())
        AddWord(word, br, pos);
}

void TFakeIndex::IndexDocument(const char *text)
{

    ui32 br = 1, pos = 1;
    TVector<TString> data = StringSplitter(text).SplitBySet("[]").SkipEmpty();

    for (auto&& s : data) {
        bool intAttr = s.StartsWith("ia");
        bool litAttr = s.StartsWith("la");
        if (intAttr || litAttr) {
            TVector<TString> attrs = SplitString(s, ";");
            attrs.erase(attrs.begin());
            for (auto&& attr : attrs) {
                TVector<TString> params = SplitString(attr, ":");
                Y_ASSERT(params.size() == 2);
                const TString value = params[1];
                if (params.size() != 2)
                    ythrow yexception() << "incorrect attr description: " << attr;
                if (litAttr) {
                    AddWord("#" + params[0] + "=\"" + value, ++br, 0);
                } else {
                    AddWord("#" + params[0] + "=" + Sprintf("%011u", FromString<ui32>(value)), ++br, 0);
                }
            }
        } else {
            AddWords(s, br, pos);
        }
    }
    AddWord("(tr", 0, 0);
    AddWord(")tr", 100, 0);
}

void TFakeIndex::TypePosition(SUPERLONG b, SUPERLONG e)
{
    TWordPosition beg(b), end(e);

    size_t begPos = 0, endPos = 0;
    for (size_t j = 0; j < Text.size(); ++j) {
        const TVector<TString> &line = Text[j];
        for (size_t i = 0; i < line.size(); ++i) {
            printf("%s%s", line[i].c_str(), i + 1 == line.size() ? ". " : " ");
            if (j < beg.Break() || j == beg.Break() && i < beg.Word())
                begPos += line[i].length() + (i + 1 == line.size() ? 2 : 1);
            if (j < end.Break() || j == end.Break() && i < end.Word())
                endPos += line[i].length() + (i + 1 == line.size() ? 2 : 1);
        }
    }
    printf("\n");
    for (size_t i = 0; i < begPos; ++i)
        printf(" ");
    printf("^");
    if (begPos < endPos) {
        for (size_t i = begPos + 1; i < endPos; ++i)
            printf(" ");
        printf("^");
    }
    printf("\n");
    for (size_t i = 0; i < begPos; ++i)
        printf(" ");
    if (begPos == endPos)
        printf("[%d.%d]\n", beg.Break(), beg.Word());
    else
        printf("[%d.%d]-[%d.%d]\n", beg.Break(), beg.Word(), end.Break(), end.Word());
}

void TFakeIndex::TypeResult(TQuorumIterator *iterator)
{
    TFullPositionEx res;
    if (iterator->GetNumHits() == 1) {
        for (; iterator->GetNextPosition(&res);) {
            TypePosition(res.Pos.Beg, res.Pos.End);
        }
    } else {
        for (; iterator->GetNextContactedPosition(&res);) {
            TypePosition(res.Pos.Beg, res.Pos.End);
        }
    }
}

void TFakeIndex::TypeReqResult(const TString& query, const TReqAttrList& attrList)
{
    printf("> %s\n", query.data());
    TLanguageContext lang(LI_DEFAULT_REQUEST_LANGUAGES, nullptr);
    try {
        TRichTreeConstPtr richtree;
        richtree = CreateRichTree(CharToWide(query, csYandex), TCreateTreeOptions(lang, &attrList));
        LoadFreq(Pure, *richtree->Root);
        TQuorumIterator iterator(this, this, *richtree.Get());
        if (!iterator.IsValid()) {
            printf("iterator is incorrect \n");
            return;
        }
        TypeResult(&iterator);
        printf("\n");
    } catch (const std::exception& e) {
        cout << e.what() << endl << endl;
        return;
    }
}

int main(int argc, char *argv[])
{
#ifdef _WIN32
    _setmode(_fileno(stdout), _O_BINARY);
#endif

    class Opt opt(argc, argv, "i:o:");
    int c;
    while ((c = opt.Get()) != EOF) {
        switch (c) {
            case 'o':
                if (freopen(opt.Arg, "w", stdout) == nullptr) {
                    perror(opt.Arg);
                    return 1;
                }
                break;
            case 'i':
                if (freopen(opt.Arg, "r", stdin) == nullptr) {
                    perror(opt.Arg);
                    return 1;
                }
                break;
            case '?':
            default:
                cerr << "usage: " << argv[0] << " [iname=in.txt [oname=out.txt]]" << endl;
                return 1;
                break;
        }
    }

    if (opt.Ind < argc)
        cerr << "extra arguments ignored: " << argv[opt.Ind] << "..." << endl;

    try {
        char sBuffer[20000];
        THolder<TFakeIndex> fakeIndex;
        TReqAttrList attrList("a:ZONE\nd:ZONE\nz:ZONE\ntr:ZONE,,,ngr-3\nporno:ATTR_INTEGER,doc\nft:ATTR_INTEGER,doc\nlink:ATTR_LITERAL,doc\n");

        while (cin.getline(sBuffer, 20000)) {
            if (sBuffer[0] == ':'|| sBuffer[0] == '\0') {
                cout << sBuffer << '\n';
                continue;
            }
            if (sBuffer[0] == '<' && sBuffer[1] == '<') {
                cout << sBuffer << "\n\n";
                const char* text = sBuffer + 2;
                fakeIndex.Reset(new TFakeIndex);
                fakeIndex->IndexDocument(text);
                continue;
            }
            if (!fakeIndex) {
                cout << "Index was not specified.\n";
                continue;
            }
            fakeIndex->TypeReqResult(sBuffer, attrList);
        }
    } catch (const std::exception& e) {
        cout << endl << e.what();
        return 1;
    }
    return 0;
}
