#pragma once

#include <util/charset/wide.h>
#include <util/datetime/cputimer.h>
#include <util/digest/fnv.h>
#include <util/draft/datetime.h>
#include <util/generic/vector.h>
#include <util/generic/deque.h>
#include <util/generic/hash_set.h>
#include <util/string/join.h>
#include <util/string/strip.h>
#include <util/string/type.h>
#include <util/thread/pool.h>

#include <kernel/mirrors/mirrors_trie.h>

#include <library/cpp/containers/comptrie/comptrie.h>
#include <library/cpp/containers/comptrie/prefix_iterator.h>
#include <library/cpp/string_utils/url/url.h>
#include <library/cpp/uri/common.h>

#include <mapreduce/yt/interface/client.h>

#include <robot/library/url/canonizer/canonizer.h>
#include <robot/library/yt/static/command.h>
#include <robot/library/yt/static/tags.h>

#include <wmconsole/version3/library/jupiter/jupiter.h>
#include <wmconsole/version3/wmcutil/log.h>
#include <wmconsole/version3/wmcutil/url.h>
#include <wmconsole/version3/wmcutil/yt/yt_runner.h>
#include <wmconsole/version3/wmcutil/yt/yt_utils.h>

#include "config.h"

namespace NWebmaster {
namespace NCatalogia {

using namespace NJupiter;

using THostId   = ui32;
using TOwnerId  = ui32;
using TQueryId  = ui64;
using TUrlId    = ui64;
using TRegionId = ui32;

inline TUrlId GetUrlId(const TStringBuf &host, const TStringBuf &path) {
    const ui64 hostId = FnvHash<ui32>(host.data(), host.size());
    const ui64 pathId = FnvHash<ui32>(path.data(), path.size());
    return (hostId << 32ul) | pathId;
}

inline TQueryId GetQueryId(const TString &query) {
    return FnvHash<TQueryId>(query.data(), query.size());
}

inline THostId GetHostIdFromUrlId(TUrlId urlId) {
    return urlId >> 32ul;
}

struct TMirrors {
    TMirrors(const TString &mirrorsTrieFile)
        : MirrorsTrie(new TMirrorsMappedTrie(mirrorsTrieFile.data(), PCHM_Force_Lock))
        , MirrorCharBuffer(MirrorsTrie->MakeCharBuffer())
    {
    }

    TString GetMainMirror(TString host) {
        host.to_lower();
        TString mainMirror = host;
        const char *mainMirrorPtr = MirrorsTrie->GetCheck(host.data(), MirrorCharBuffer.Get());
        if (mainMirrorPtr) {
            mainMirror = mainMirrorPtr;
        }
        return mainMirror;
    }

public:
    TSimpleSharedPtr<TMirrorsMappedTrie> MirrorsTrie;
    TMirrorsMappedTrie::TCharBuffer MirrorCharBuffer;
};

struct TRobotsCanonizer {
    TRobotsCanonizer(const THashMap<TString, TString> &robotsMap)
        : RobotsMap(robotsMap)
    {
    }

    TString GetCanonizedPath(const TString &url) {
        TString host, path;
        SplitUrlToHostAndPath(url, host, path);

        auto robotsIt = RobotsMap.find(host);
        if (robotsIt == RobotsMap.end()) {
            return path;
        }

        TAtomicSharedPtr<NGemini::TUrlCanonizer> canonizer;
        with_lock(Mutex) {
            TAtomicSharedPtr<NGemini::TUrlCanonizer> &tmp = CanonizerMap[host];
            if (tmp.Get() == nullptr) {
                NGemini::TUrlCanonizer::TConfig config;
                config.RflString = robotsIt->second;
                tmp.Reset(new NGemini::TUrlCanonizer(config));
            }
            canonizer = tmp;
        }

        try {
            TString canonizedPath;
            TString canonizedUrl;
            bool strong = false;
            ui32 info = 0;
            ui32 flags = strong ? NGemini::CT_STRONG : NGemini::CT_RFL;
            canonizer->CanonizeUrl(url, flags, info, canonizedUrl, nullptr, &canonizedPath);
            return canonizedPath;
        } catch (const NGemini::TParseException& e) {
            Cerr << "unable to canonize url: " << e.what() << Endl;
        }

        return path;
    }

public:
    TMutex Mutex;
    const THashMap<TString, TString> &RobotsMap;
    THashMap<TString, TAtomicSharedPtr<NGemini::TUrlCanonizer>> CanonizerMap;
};

struct TRobotsCanonizerV2 {
    TRobotsCanonizerV2(const THashMap<TString, TString> &robotsMap)
        : RobotsMap(robotsMap)
    {
    }

    TString GetCanonizedPath(const TString &url, const TString &host, const TString &path) {
        auto robotsIt = RobotsMap.find(host);
        if (robotsIt == RobotsMap.end()) {
            return path;
        }

        if (CanonizerPtr.Get() == nullptr) {
            NGemini::TUrlCanonizer::TConfig config;
            config.RflString = robotsIt->second;
            CanonizerPtr.Reset(new NGemini::TUrlCanonizer(config));
        }

        try {
            TString canonizedPath;
            TString canonizedUrl;
            bool strong = false;
            ui32 info = 0;
            ui32 flags = strong ? NGemini::CT_STRONG : NGemini::CT_RFL;
            CanonizerPtr->CanonizeUrl(url, flags, info, canonizedUrl, nullptr, &canonizedPath);
            return canonizedPath;
        } catch (const NGemini::TParseException& e) {
            Cerr << "unable to canonize url: " << e.what() << Endl;
        }

        return path;
    }

public:
    const THashMap<TString, TString> &RobotsMap;
    THolder<NGemini::TUrlCanonizer> CanonizerPtr;
};

void LoadCatalogiaDomains(NYT::IClientBasePtr client, const TString &table, THashSet<TString> &domains, TVector<char> &trie);
void LoadCatalogiaRobots(NYT::IClientBasePtr client, const TString &table, THashMap<TString, TString> &robotsMap);
void LoadCatalogiaMirrors(NYT::IClientBasePtr client, const TString &table, THashMap<TString, TString> &mirrorsMap);
bool MatchPeriod(const TString &tableName, time_t periodBegin, time_t periodEnd, time_t &matched);

} //namespace NCatalogia
} //namespace NWebmaster
