#pragma once

#include <limits>

#include <library/cpp/containers/comptrie/comptrie.h>
#include <library/cpp/containers/comptrie/prefix_iterator.h>
#include <library/cpp/uri/http_url.h>
#include <library/cpp/string_utils/url/url.h>
#include <util/generic/hash.h>
#include <yweb/robot/wmtree/treebuilder.h>
#include <yweb/robot/wmtree/treesaver.h>

#include <wmconsole/version3/protos/urltree.pb.h>
#include <wmconsole/version3/protos/exported.pb.h>

#include "matcher_url.h"

namespace NWebmaster {

using ProtoUrlTreeMsg = proto::urltree::UrlTreeMessage;
using ProtoUrlTreeHost = proto::urltree::HostInfo;
using ProtoHttpCode = proto::urltree::HttpCodeInfo;
using ProtoTurboSource = proto::urltree::TurboSourceInfo;
using ProtoNode = proto::urltree::NodeInfo;
using ProtoUrlSample = proto::urltree::UrlSampleInfo;
using ProtoSearchShard = proto::urltree::YandexSearchShardId;
using ProtoSearchSource = proto::urltree::SearchSourceId;

enum ETreeBuildMode {
    MODE_DEFAULT,
    MODE_OLDWEBMASTER,
};

struct TCrawlerEventCounter {
    TCrawlerEventCounter& operator+=(const TCrawlerEventCounter &right) {
        Changed += right.Changed;
        Crawled += right.Crawled;
        New += right.New;
        return *this;
    }
public:
    size_t Changed = 0;
    size_t Crawled = 0;
    size_t New = 0;
};

struct TTreeData {
    using THttpCodes = THashMap<int, size_t>;
    using TUrlStatuses = THashMap<int, size_t>;
    using TCrawlerHttpCodes = THashMap<int, size_t>;
    using TTurboSources = THashMap<size_t, size_t>;

    TTreeData() = default;
    TTreeData(size_t numOfDocs)
        : NumOfDocs(numOfDocs)
    {
    }

public:
    size_t NumOfDocs = 0;
    THttpCodes HttpCodes;
    TUrlStatuses ExcludedUrlStatuses;
    TCrawlerHttpCodes CrawledHttpCodes;
    TCrawlerEventCounter CrawledEvents;
    TTurboSources TurboSource;
    size_t DocsOnSearch = 0;
    size_t DocsSearchDiffGone = 0;
    size_t DocsSearchDiffNew = 0;
};

struct TTreeRecord {
    TTreeRecord(const TString &name, const TTreeData &data, ui32 nodeId, ui32 parentId)
            : Name(name)
            , Data(data)
            , NodeId(nodeId)
            , ParentId(parentId)
    {
    }

public:
    TString Name;
    TTreeData Data;
    ui32 NodeId;
    ui32 ParentId;
};

struct TUserPattern {
    TUserPattern() {
    }

    TUserPattern(const TString &pattern)
        : Pattern(pattern)
    {
    }

public:
    TString Pattern;
    TTreeData Data;
};

struct TUserPatternMatcher {
    typedef TAtomicSharedPtr<TUserPatternMatcher> Ptr;

    TUserPatternMatcher(const TVector<TString> &filters);

    bool Matches(const TString &path) {
        return Matcher->Matches(path);
    }

public:
    TMatcher::Ptr Matcher;
    TVector<TUserPattern> Patterns;
};

struct TWrapperNode { //: public TTreeRecord {
    const static size_t TOTAL_URL_SAMPLE_ALLOWED = 10;

    typedef TSimpleSharedPtr<TWrapperNode> Ptr;

    struct TPath {
        TPath(size_t nodeId, const TString &path)
            : NodeId(nodeId)
            , Path(path)
        {
        }
    public:
        size_t NodeId;
        TString Path;
    };

    TWrapperNode()
    {
    }

    void ExpandPath(TVector<TPath> &dest) {
        ExpandPath(dest, Name);
    }

    void ExpandPath(TVector<TPath> &dest, const TString &path) {
        dest.push_back(TPath(Id, path));

        for (const TWrapperNode::Ptr &child : Children) {
            if (!child.Get()) {
                continue;
            }

            if (path == "/" && !child->Name.empty() && child->Name[0] == '/') {
                child->ExpandPath(dest, child->Name);
            } else {
                child->ExpandPath(dest, path + child->Name);
            }
        }
    }

public:
    size_t Id;
    size_t ParentId;
    TString Name;
    TTreeData Data;
    TVector<TWrapperNode::Ptr> Children;
};

struct TSiteTreeShard {
    typedef TPrefixIterator<TCompactTrie<char>> TPrefixIterator;
    typedef TSimpleSharedPtr<TSiteTreeShard> Ptr;

public:
    TSiteTreeShard(const TVector<TTreeRecord> &siteStructure, TUserPatternMatcher::Ptr userPatternMatcher, ProtoSearchSource sourceId, ProtoSearchShard shardId);
    bool IsThereRoot() const;
    void LoadSiteTree(const TVector<TTreeRecord> &siteStructure, TUserPatternMatcher::Ptr userPatternMatcher);
    void AppendCrawlerEvent(const TString &path, int httpCode, size_t countNew, size_t countChanged, size_t countCrawled);

public:
    TMap<size_t, TWrapperNode::Ptr> IdNodeMap; //key = nodeId
    TVector<TWrapperNode::TPath> TreePaths;
    TBufferStream TrieStream;
    TCompactTrie<char> Trie;
    ProtoSearchSource SourceId;
    ProtoSearchShard ShardId;
    TVector<TWrapperNode::Ptr> UserPatterns;
    TMatcher::Ptr UserPatternMatcher;
};

struct TSiteTree {
    TSiteTree(const TVector<TTreeRecord> &siteStructure, TUserPatternMatcher::Ptr userPatternMatcher);

    void Serialize(ProtoUrlTreeHost& HostProto) const;
    void SerializeTurboSources(ProtoNode *node, TTreeData::TTurboSources &turboSource) const;
    void SerializeHttpCodes(ProtoNode *node, TTreeData::THttpCodes &httpCodes) const;
    void SerializeUrlStatuses(ProtoNode *node, TTreeData::TUrlStatuses &urlStatuses) const;

public:
    TMap<std::pair<ProtoSearchSource, ProtoSearchShard>, TSiteTreeShard::Ptr> Shards;
};

struct TSiteTreeBuilder {
    const static int ROOT_NODE_ID;

    enum ETreeBuildState { IDLE, BUILDING, COMPLETED, };

    struct TProcessor {
        void AddToDescendants(TTreeData &ascendant, const TTreeData &descendant) const {
            ascendant.NumOfDocs += descendant.NumOfDocs;
            ascendant.DocsOnSearch += descendant.DocsOnSearch;
            ascendant.DocsSearchDiffGone += descendant.DocsSearchDiffGone;
            ascendant.DocsSearchDiffNew += descendant.DocsSearchDiffNew;

            //Здесь данные по источникам турбо разнесены, т.к. обработаны в  AddPath, в методе AddTurbo
            for(const auto &obj : descendant.TurboSource) {
                ascendant.TurboSource[obj.first] += obj.second;
            }
            for (const auto &obj : descendant.HttpCodes) {
                ascendant.HttpCodes[obj.first] += obj.second;
            }
            for (const auto &obj : descendant.ExcludedUrlStatuses) {
                ascendant.ExcludedUrlStatuses[obj.first] += obj.second;
            }
        }

        void AddToNode(TTreeData &urldata, const TTreeData &data) const {
            urldata.NumOfDocs += data.NumOfDocs;
            urldata.DocsOnSearch += data.DocsOnSearch;
            urldata.DocsSearchDiffGone += data.DocsSearchDiffGone;
            urldata.DocsSearchDiffNew += data.DocsSearchDiffNew;

            for(const auto &obj : data.TurboSource) {
                urldata.TurboSource[obj.first] += obj.second;
            }
            for (const auto &obj : data.HttpCodes) {
                urldata.HttpCodes[obj.first] += obj.second;
            }
            for (const auto &obj : data.ExcludedUrlStatuses) {
                urldata.ExcludedUrlStatuses[obj.first] += obj.second;
            }
        }
    };

    typedef ::TTreeBuilder<TString, TTreeData, TProcessor> TTreeBuilder;
    typedef TTreeBuilder::TNode TBuilderNode;

    struct TTreeRecordWriter {
        TTreeRecordWriter(TVector<TTreeRecord> &_structure)
            : structure(_structure)
        {
        }

        void operator ()(const TBuilderNode *node, const TString &name, ui32 nodeId, ui32 parentId) {
            const TTreeData &data = node->GetData();
            structure.push_back(TTreeRecord(name, data, nodeId, parentId));
        }

    private:
        TVector<TTreeRecord> &structure;
    };

public:
    TSiteTreeBuilder(ETreeBuildMode mode = MODE_DEFAULT, size_t DefaultSliceSize = 1000, size_t DefaultCompressFactor = 100, size_t DefaultMinNumOfUrlsInGroup = 10);

    void AddPath(const proto::urltree::RecordSourceInfo &path);
    void Commit(TVector<TTreeRecord> &siteStructure);
    void SetupUserPartitions(const TVector<TString> &filters);
    void AddTurbo(TTreeData::TTurboSources &turboSource, size_t turboPageSource);
    static void UrlSegmentParser(const TString& path, TTreeBuilder::TParseResult& segments, bool needUrlNormalize = true);

    inline void StartBuilding() {
        BuilderState = BUILDING;
    }

    inline bool BuildingInProgress() const {
        return BuilderState == BUILDING;
    }

public:
    TTreeBuilder TreeBuilder;
    ETreeBuildState BuilderState;
    TUserPatternMatcher::Ptr UserPatternMatcher;
};

} //namespace NWebmaster
