#include <util/generic/maybe.h>
#include <util/memory/blob.h>
#include <util/stream/file.h>
#include <util/string/join.h>

#include <library/cpp/archive/yarchive.h>
#include <library/cpp/json/json_reader.h>
#include <library/cpp/on_disk/aho_corasick/reader.h>
#include <library/cpp/on_disk/aho_corasick/writer.h>
#include <library/cpp/regex/pcre/regexp.h>

#include <wmconsole/version3/wmcutil/archive.h>

#include "searchengines.h"

namespace NWebmaster {

static const unsigned char SEARCH_ENGINES_DATA[] = {
    #include "searchengines.inc"
};

TSearchEngine::TSearchEngine(const NJson::TJsonValue &json) {
    using namespace NUtils;
    FromJsonNode(json["Id"],                    Id);
    FromJsonNode(json["SearchEngine"],          SearchEngine);
    FromJsonNode(json["ParentId"],              ParentId);
    FromJsonNode(json["weight"],                weight);
    FromJsonNode(json["URL"],                   URL);
    FromJsonNode(json["form_num"],              form_num);
    FromJsonNode(json["field_name"],            field_name);
    FromJsonNode(json["Hide"],                  Hide);
    FromJsonNode(json["CheckType"],             CheckType);
    FromJsonNode(json["PageParameterPattern"],  PageParameterPattern);
    FromJsonNode(json["PageParameterOffset"],   PageParameterOffset);
    FromJsonNode(json["PageParameterDivisor"],  PageParameterDivisor);
    FromJsonNode(json["StrId"],                 StrId);
    FromJsonNode(json["Domain"],                Domain);
    //Cout << Domain << Endl;
}

TSearchEnginePattern::TSearchEnginePattern(const NJson::TJsonValue &json) {
    using namespace NUtils;
    FromJsonNode(json["Id"],                Id);
    FromJsonNode(json["SearchEngineId"],    SearchEngineId);
    FromJsonNode(json["Pattern"],           Pattern);
    FromJsonNode(json["QuickPattern"],      QuickPattern);
    FromJsonNode(json["Charset"],           Charset);
    Regex.Compile(Pattern);
}

bool TSearchEnginePattern::Match(const TString &str) const {
    return Regex.Match(str.c_str());
}

TSearchEngines::TSearchEngines() {
    LoadSearchEngines();
    LoadSearchEnginesPatterns();

    TAhoCorasickBuilder<TString, ui32> ahoBuilder;
    QuickPatternsList.assign(QuickPatternsMap.begin(), QuickPatternsMap.end());
    for (size_t i = 0; i < QuickPatternsList.size(); i++) {
        ahoBuilder.AddString(QuickPatternsList[i].first, i);
    }

    TBlob ahoData = ahoBuilder.Save();
    AhoSearcher.Reset(new TAhoSearcher(ahoData));
}

void TSearchEngines::LoadSearchEngines() {
    const int ARCHIVE_INDEX_SEARCH_ENGINES = 0;
    NJson::TJsonValue searchEnginesJson;
    NUtils::ReadJsonFromArchive(SEARCH_ENGINES_DATA, searchEnginesJson, ARCHIVE_INDEX_SEARCH_ENGINES);
    for (const auto &searchEngineJson : searchEnginesJson.GetArray()) {
        TSearchEngine se(searchEngineJson);
        SearchEngines[se.Id] = se;
    }
}

void TSearchEngines::LoadSearchEnginesPatterns() {
    const int ARCHIVE_INDEX_SEARCH_ENGINES_PATTERNS = 1;
    NJson::TJsonValue searchEnginesPatternsJson;
    NUtils::ReadJsonFromArchive(SEARCH_ENGINES_DATA, searchEnginesPatternsJson, ARCHIVE_INDEX_SEARCH_ENGINES_PATTERNS);
    for (const auto &searchEnginePatternJson : searchEnginesPatternsJson.GetArray()) {
        TSearchEnginePattern sep(searchEnginePatternJson);
        SearchEnginesPatterns[sep.Id] = sep;
        QuickPatternsMap[sep.QuickPattern].push_back(sep.Id);
    }
}

const TSearchEngine &TSearchEngines::Get(int id) const {
    return SearchEngines.at(id);
}

bool TSearchEngines::Match(const TString &str, THashSet<int> &matchedSEIds) const {
    const TAhoSearcher::TSearchResult result = AhoSearcher->AhoSearch(str);
    THashSet<int> matchedSearchEngines;
    THashSet<int> checkedQuickPatterns;
    for (const auto &obj : result) {
        const int quickPatternIdx = obj.second;
        if (checkedQuickPatterns.contains(quickPatternIdx)) {
            continue;
        }
        checkedQuickPatterns.insert(quickPatternIdx);
        for (int sepId : QuickPatternsList[quickPatternIdx].second) {
            const TSearchEnginePattern &sep = SearchEnginesPatterns.at(sepId);
            const TSearchEngine &se = SearchEngines.at(sep.SearchEngineId);
            const bool matched = sep.Match(str);
            if (matched) {
                matchedSearchEngines.insert(se.Id);
            }
        }
    }
    matchedSEIds.swap(matchedSearchEngines);
    return !matchedSearchEngines.empty();
}

} //namespace NWebmaster
