#include <maps/wikimap/mapspro/services/mrc/libs/addr_pt_searcher/include/addr_pt_searcher.h>

#include <maps/libs/geolib/include/conversion.h>

#include <maps/wikimap/mapspro/services/mrc/libs/object/include/mock_loader.h>

#include <library/cpp/testing/gtest/gtest.h>

#include <util/charset/utf8.h>
#include <util/charset/wide.h>

#include <unordered_set>

namespace maps::mrc::addr_pt_searcher {

namespace {

static const geolib3::Point2 FEATURE_POS_MERCATOR =
    geolib3::convertGeodeticToMercator({44.006864, 56.326775});
static const TString FEATURE_HOUSE_NUMBER = "12";
static const double MIN_CONFIDENCE = 0.7;

static const object::AddressPointWithNames ADDRESS_POINT_GROUND_TRUTH = {
    { object::RevisionID{ 1, 1 }, geolib3::convertGeodeticToMercator({44.006864, 56.326775}), "12"},
    { object::RevisionID{ 1, 1 }, geolib3::convertGeodeticToMercator({44.006705, 56.326845}), "12/1"}
};

std::set<AddressPointCandidate> ADDRESS_POINT_GROUND_TRUTH_CANDIDATES() {
    std::set<AddressPointCandidate> candidates;
    for (size_t i = 0; i < ADDRESS_POINT_GROUND_TRUTH.size(); i++) {
        candidates.insert({
            ADDRESS_POINT_GROUND_TRUTH[i].geom(),
            ADDRESS_POINT_GROUND_TRUTH[i].name().c_str(),
            1.0
        });
    }
    return candidates;
}

void initLoader(object::MockLoader& mockLoader) {
    object::AddressPointWithNames addressPoints = {
        // inside search box around featurePosMercator
        { object::RevisionID{ 1, 1 },
        geolib3::convertGeodeticToMercator({44.006754, 56.326720}),
        "11" },
        { object::RevisionID{ 1, 1 },
        geolib3::convertGeodeticToMercator({44.006694, 56.326682}),
        "13" },
        { object::RevisionID{ 1, 1 },
        geolib3::convertGeodeticToMercator({44.006904, 56.326486}),
        "14" },
        // outside search box around featurePosMercator
        { object::RevisionID{ 1, 1 },
        geolib3::convertGeodeticToMercator({44.005482, 56.326775}),
        "11" },
        { object::RevisionID{ 1, 1 },
        geolib3::convertGeodeticToMercator({44.006864, 56.326018 }),
        "12" },
        { object::RevisionID{ 1, 1 },
        geolib3::convertGeodeticToMercator({44.005482, 56.326018}),
        "12/1" },
        { object::RevisionID{ 1, 1 },
        geolib3::convertGeodeticToMercator({44.008455, 56.326014 }),
        "13" },
        { object::RevisionID{ 1, 1 },
        geolib3::convertGeodeticToMercator({44.008455, 56.326777 }),
        "14" },
    };
    mockLoader.add(addressPoints);
    object::Buildings buildings;
    for (size_t i = 0; i < addressPoints.size(); i++) {
        const geolib3::Point2& pt = addressPoints[i].geom();
        buildings.emplace_back(
            object::RevisionID{ 2 + i, 2 + i },
            geolib3::Polygon2(
                { geolib3::LinearRing2(
                    {{pt.x() - 1., pt.y() - 1.},
                     {pt.x() + 1., pt.y() - 1.},
                     {pt.x() + 1., pt.y() + 1.},
                     {pt.x() - 1., pt.y() + 1.}},
                false), {}, false })
        );
    }
    mockLoader.add(ADDRESS_POINT_GROUND_TRUTH);
}

};

namespace tests {

TEST(test_addr_pt_searcher, test_string_normalization)
{
    std::vector<
        std::pair<
            std::pair<TUtf16String, std::set<char16_t>>,
            TUtf16String
        >
    > TEST_DATA = {
        {{UTF8ToWide("0123456789АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz:-/"),
         {}},
         UTF8ToWide("0123456789AБBГДEЖ3ИЙKЛMHOПPCTYФXЦЧШЩЪЫЬЭЮЯAБBГДEЖ3ИЙKЛMHOПPCTYФXЦЧШЩЪЫЬЭЮЯABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ:-/")},
        {{UTF8ToWide("0123456789АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz:-/"),
         {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A'}},
         UTF8ToWide("0123456789A3A3AA")}
    };
    for (const auto& data : TEST_DATA) {
        EXPECT_EQ(normalizeSymbolsInString(data.first.first, data.first.second), data.second);
    }
} // test_string_distance

TUtf16String normalizeSymbolsInString(const TUtf16String& str, const std::set<char16_t>& validSymbols);

TEST(test_addr_pt_searcher, test_string_distance)
{
    std::vector<
        std::pair<
            std::pair<TString, TString>,
            double
        >
    > TEST_DATA = {
        {{"123A",  "123A"},    0. },
        {{"123",   "123/4"},   0.2 + 0.2},
        {{"123/4", "123"},     1.2},
        {{"123/4", "123/5"},   2.0},
        {{"123",   "124"},     2.0},
        {{"123A",  "123B"},    2.0}
    };
    for (const auto& data : TEST_DATA) {
        EXPECT_EQ(stringDistance(UTF8ToWide(data.first.first), UTF8ToWide(data.first.second)),
                  data.second);
    }
} // test_string_distance

TEST(test_addr_pt_searcher, test_address_points_searcher_find)
{
    object::MockLoader mockLoader;
    initLoader(mockLoader);

    AddressPointSearcher addrPtSearcher(mockLoader, {});
    addrPtSearcher.resetPosition(FEATURE_POS_MERCATOR);
    std::vector<AddressPointCandidate> candidates = addrPtSearcher.find(FEATURE_POS_MERCATOR, FEATURE_HOUSE_NUMBER, MIN_CONFIDENCE);
    std::set<AddressPointCandidate> candidatesGroundTruth = ADDRESS_POINT_GROUND_TRUTH_CANDIDATES();
    EXPECT_EQ(candidates.size(), candidatesGroundTruth.size());
    for (size_t i = 0; i < candidates.size(); i++) {
        EXPECT_GT(candidatesGroundTruth.count(candidates[i]), 0u);
    }
    EXPECT_THROW(
        addrPtSearcher.find({0., 0.}, "123", MIN_CONFIDENCE);,
        RuntimeError
    );
}// test_address_points_searcher_find

TEST(test_addr_pt_searcher, test_address_points_searcher_has_candidate)
{
    object::MockLoader mockLoader;
    initLoader(mockLoader);

    AddressPointSearcher addrPtSearcher(mockLoader, {});
    addrPtSearcher.resetPosition(FEATURE_POS_MERCATOR);
    EXPECT_TRUE(addrPtSearcher.hasCandidate(FEATURE_POS_MERCATOR, FEATURE_HOUSE_NUMBER, MIN_CONFIDENCE));
}// test_address_points_searcher_has_candidate

TEST(test_addr_pt_searcher, test_address_points_searcher_no_buildings)
{
    object::MockLoader mockLoader;

    AddressPointSearcher addrPtSearcher(mockLoader, {});
    addrPtSearcher.resetPosition(FEATURE_POS_MERCATOR);
    EXPECT_FALSE(addrPtSearcher.hasCandidate(FEATURE_POS_MERCATOR, FEATURE_HOUSE_NUMBER, MIN_CONFIDENCE));
}// test_address_points_searcher_no_buildings

} //namespace tests
} // maps::mrc::addr_pt_searcher
