#include <yandex/maps/wiki/configs/editor/overlay_groups.h>

namespace maps {
namespace wiki {
namespace configs {
namespace editor {

constexpr enum_io::Representations<OverlayMode> OVERLAYMODE_ENUM_REPRESENTATION {
    {OverlayMode::Required, "required"},
    {OverlayMode::Forbidden, "forbidden"}
};

DEFINE_ENUM_IO(OverlayMode, OVERLAYMODE_ENUM_REPRESENTATION);

OverlayGroup::OverlayGroup(const maps::xml3::Node& node)
    : id_(node.attr<std::string>("id"))
    , label_(node.firstElementChild("label").value<std::string>())
{
    auto categoryNodes = node.nodes("base-categories/category", true);
    for (size_t j = 0; j < categoryNodes.size(); ++j) {
        baseCategoryIds_.insert(categoryNodes[j].attr<std::string>("id"));
    }
    REQUIRE(!baseCategoryIds_.empty(), "Empty base-ategories group, overlay-group id: " << id_);

    auto overlayCategoriesNode = node.nodes("overlay-categories", true);
    for (size_t i = 0; i < overlayCategoriesNode.size(); ++i) {
        OverlayMode mode;
        REQUIRE(tryFromString(overlayCategoriesNode[i].attr<std::string>("mode"), mode),
            "Invalid overlay mode specified.");
        categoryNodes = overlayCategoriesNode[i].nodes("category", true);
        if (mode == OverlayMode::Required) {
            for (size_t j = 0; j < categoryNodes.size(); ++j) {
                overlayCategoryIdsRequired_.insert(categoryNodes[j].attr<std::string>("id"));
            }
        } else if (mode == OverlayMode::Forbidden) {
             for (size_t j = 0; j < categoryNodes.size(); ++j) {
                overlayCategoryIdsForbidden_.insert(categoryNodes[j].attr<std::string>("id"));
             }
        }
        REQUIRE(!overlayCategoryIdsRequired_.empty() || !overlayCategoryIdsForbidden_.empty(),
            "Empty overlay-categories group, overlay-group id: " << id_);
    }
}

const std::string& OverlayGroup::id() const { return id_; }
const std::string& OverlayGroup::label() const { return label_; }
const StringSet& OverlayGroup::baseCategoryIds() const { return baseCategoryIds_; }

const StringSet& OverlayGroup::overlayCategoryIds(OverlayMode mode) const
{
    if (mode == OverlayMode::Required) {
        return overlayCategoryIdsRequired_;
    } else {
        return overlayCategoryIdsForbidden_;
    }
}

bool
OverlayGroup::isOverlayModeAllowed(const std::string& categoryId, OverlayMode mode) const
{
    if (mode == OverlayMode::Required) {
        return overlayCategoryIdsRequired_.find(categoryId) != overlayCategoryIdsRequired_.end();
    }
    if (mode == OverlayMode::Forbidden) {
        return overlayCategoryIdsForbidden_.find(categoryId) != overlayCategoryIdsForbidden_.end();
    }
    return false;
}

OverlayGroups::OverlayGroups(const maps::xml3::Node& node)
{
    const bool QUIET = true;
    auto groupNodes = node.nodes("overlay-group", QUIET); // Overlays made temporarily optional (see NMAPS-9323)
    for (size_t i = 0; i < groupNodes.size(); ++i) {
        auto group = std::make_shared<OverlayGroup>(groupNodes[i]);
        overlayGroupsByIds_.insert({group->id(), group});
    }
}

const StringSet&
OverlayGroups::baseCategoryIdsByGroup(const std::string& groupId) const
{
    return groupById(groupId)->baseCategoryIds();
}

const StringSet&
OverlayGroups::overlayCategoryIdsByGroup(const std::string& groupId, OverlayMode mode) const
{
    return groupById(groupId)->overlayCategoryIds(mode);
}

std::vector<OverlayGroupPtr>
OverlayGroups::findGroupsByBaseCategoryId(const std::string& categoryId) const
{
    std::vector<OverlayGroupPtr> resultGroups = {};
    for (const auto& idAndGrp : overlayGroupsByIds_) {
        if (idAndGrp.second->baseCategoryIds().count(categoryId)) {
            resultGroups.push_back(idAndGrp.second);
        }
    }
    return resultGroups;
}

std::vector<OverlayGroupPtr>
OverlayGroups::findGroupsByOverlayCategoryId(const std::string& categoryId, OverlayMode mode) const
{
    std::vector<OverlayGroupPtr> resultGroups = {};
    for (const auto& idAndGrp : overlayGroupsByIds_) {
        if (idAndGrp.second->overlayCategoryIds(mode).count(categoryId)) {
            resultGroups.push_back(idAndGrp.second);
        }
    }
    return resultGroups;
}

bool
OverlayGroups::isGroupExists(const std::string& groupId) const
{
    return overlayGroupsByIds_.count(groupId) > 0;
}

const OverlayGroupPtr&
OverlayGroups::groupById(const std::string& groupId) const
{
    auto it = overlayGroupsByIds_.find(groupId);
    REQUIRE(it != overlayGroupsByIds_.end(),
            "Category group " << groupId << " not found");
    return it->second;
}

const OverlayGroupByIdMap&
OverlayGroups::allGroups() const { return overlayGroupsByIds_; }

} // editor
} // configs
} // wiki
} // maps
