#include "objects_query_title.h"

#include "maps/wikimap/mapspro/services/editor/src/utils.h"
#include "maps/wikimap/mapspro/services/editor/src/branch_helpers.h"
#include "maps/wikimap/mapspro/services/editor/src/srv_attrs/registry.h"
#include "maps/wikimap/mapspro/services/editor/src/views/objects_query.h"
#include "maps/wikimap/mapspro/services/editor/src/configs/config.h"

#include <unicode/unistr.h>

namespace maps::wiki {
namespace {
class ICUTextMatcher
{
public:
    ICUTextMatcher(const std::string& searchText)
    {
        DEBUG() << "ICUTextMatcher searchText: " << searchText;
        const std::set<char> delimiters {DELIMITERS.begin(), DELIMITERS.end()};
        std::string part;
        auto addPart = [&] (std::string& part) {
            DEBUG() << "ICUTextMatcher part: " << part;
            if (!part.empty()) {
                searchTextParts_.emplace_back(
                    icu::UnicodeString::fromUTF8(part).toLower());
                part.clear();
            }
        };
        for (const auto& c : searchText) {
            if (delimiters.contains(c)) {
                addPart(part);
                continue;
            }
            part += c;
        }
        addPart(part);
    }

    bool operator () (const std::string& text)
    {
        if (searchTextParts_.empty()) {
            return true;
        }
        if (text.empty()) {
            return false;
        }
        auto textICULower = icu::UnicodeString::fromUTF8(text).toLower();
        return std::all_of(searchTextParts_.begin(), searchTextParts_.end(),
            [&](const auto& searchTextPart) {
                return -1 != textICULower.indexOf(searchTextPart);
            });

    }
private:
    const std::string DELIMITERS = " ;,.\\-+/@#$%^~&(){}[]*\"|:!?";
    std::vector<icu::UnicodeString> searchTextParts_;
};
} // namespace

ObjectsQueryTitle::ObjectsQueryTitle(const Request& request)
    : controller::BaseController<ObjectsQueryTitle>(BOOST_CURRENT_FUNCTION)
    , request_(request)
{
}

std::string
ObjectsQueryTitle::Request::dump() const
{
    std::stringstream ss;
    ss << " categories[" << categories << "]";
    if (!geom.isNull()) {
        ss << " geom: " << geom.dump();
    }
    ss << " token: " << dbToken;
    ss << " branch: " << branchId;
    ss << " limit: " << limit;
    ss << " text: " << searchText;
    ss << " ll: " << ll;
    ss << " distance: " << distance;
    if (indoorLevelId) {
        ss << " indoor-level-id: " << *indoorLevelId;
    }
    return ss.str();
}

std::string
ObjectsQueryTitle::printRequest() const
{
    return request_.dump();
}

void
ObjectsQueryTitle::control()
{
    if (request_.categories.empty()) {
        return;
    }
    StringSet categories = split<StringSet>(request_.categories, ',');
    Geom searchCenter;
    WIKI_REQUIRE(!request_.geom.isNull() || !request_.ll.empty(),
        ERR_BAD_REQUEST,
        "geometry or ll is required.");
    if (!request_.geom.isNull()) {
        searchCenter = request_.geom.center();
    } else {
        ASSERT(!request_.ll.empty());
        std::vector<double> geodeticLL = splitCast<std::vector<double>>(request_.ll, ',');
        WIKI_REQUIRE(geodeticLL.size() == 2,
            ERR_BAD_REQUEST,
            "Can't parse ll for suggest.");
        searchCenter = Geom(
            createPoint(
                geodeticLL[0],
                geodeticLL[1],
                SpatialRefSystem::Geodetic));
    }

    auto workView = BranchContextFacade::acquireWorkReadViewOnly(
        request_.branchId, request_.dbToken);
    views::ObjectsQuery objectsQuery;
    objectsQuery.addCondition(views::CategoriesCondition(*workView, categories));
    if (request_.indoorLevelId) {
        objectsQuery.addCondition(views::AllOfServiceAttributesCondition(
            *workView,
            {{srv_attrs::SRV_INDOOR_LEVEL_ID, std::to_string(*request_.indoorLevelId)}}));
    } else if (!categories.count(CATEGORY_INDOOR_LEVEL)) {
        objectsQuery.addCondition(views::NoneOfServiceAttributesCondition(
            *workView, {srv_attrs::SRV_INDOOR_LEVEL_ID}));
    }
    objectsQuery.addCondition(views::WithinGeometryCondition(*workView, searchCenter, request_.distance));
    auto viewObjects = objectsQuery.exec(*workView, request_.branchId);
    ICUTextMatcher textMatcher(request_.searchText);
    auto titleMatch = [&](const views::ViewObject& obj) {
        return !textMatcher(obj.screenLabel());
    };
    viewObjects.erase(
        std::remove_if(viewObjects.begin(), viewObjects.end(), titleMatch),
        viewObjects.end());
    std::multimap<double, size_t> objectsByDistance;
    for (size_t i = 0; i < viewObjects.size(); ++i) {
        objectsByDistance.emplace(viewObjects[i].geom().distance(searchCenter), i);
    }
    for (auto it = objectsByDistance.begin();
        it != objectsByDistance.end() && result_->viewObjects.size() < request_.limit;
        ++it)
    {
        result_->viewObjects.push_back(viewObjects[it->second]);
    }
}

} // namespace maps::wiki
