#include <yandex/maps/wiki/diffalert/revision/editor_config.h>
#include <maps/libs/xml/include/xml.h>
#include <maps/libs/common/include/map_at.h>

#include <map>
#include <set>

namespace maps::wiki::diffalert {

namespace {

const std::set<std::string> EMPTY;

}

class EditorConfig::Impl
{
public:
    explicit Impl(const xml3::Doc& doc);

    void addTableAttr(const std::string& master, std::string role)
    {
        categoryToTableAttrRole[master].insert(std::move(role));
    }

    void addGeomPart(const std::string& master, std::string role, std::string slave)
    {
        categoryToGeomPartMasterRole[slave].insert(role);
        categoryToGeomPartCategory[master].insert(std::move(slave));
        categoryToGeomPartRole[master].insert(std::move(role));
    }

    std::map<std::string, std::set<std::string>> categoryToTableAttrRole;

    std::map<std::string, std::set<std::string>> categoryToGeomPartCategory;
    std::map<std::string, std::set<std::string>> categoryToGeomPartRole;
    std::map<std::string, std::set<std::string>> categoryToGeomPartMasterRole;

    std::set<std::string> faceCategories;
    std::set<std::string> faceElementCategories;
    std::set<std::string> faceJunctionCategories;
};

COPYABLE_PIMPL_DEFINITIONS(EditorConfig)

EditorConfig::Impl::Impl(const xml3::Doc& doc)
{
    auto categoryNodes = doc.nodes("/editor/categories/category");
    for (size_t iCat = 0; iCat < categoryNodes.size(); ++iCat) {
        const auto& categoryNode = categoryNodes[iCat];
        auto id = categoryNode.attr<std::string>("id");
        auto relationNodes = categoryNode.nodes("relations/role", /* quiet = */true);

        for (size_t iRel = 0; iRel < relationNodes.size(); ++iRel) {
            const auto& relNode = relationNodes[iRel];
            auto role = relNode.attr<std::string>("id");
            auto slaveCategoryId = relNode.attr<std::string>("category-id");
            if (relNode.attr<bool>("geom-part", false)) {
                addGeomPart(id, role, slaveCategoryId);
            }
            if (relNode.attr<bool>("table-row", false)) {
                addTableAttr(id, role);
            }
        }
    }

    auto contourNodes = doc.nodes("/editor/contour-objects/contour-object/contour");
    for (size_t iContour = 0; iContour < contourNodes.size(); ++iContour) {
        const auto& contourNode = contourNodes[iContour];
        auto id = contourNode.attr<std::string>("category-id");
        faceCategories.insert(std::move(id));
        const auto& elementNode = contourNode.firstElementChild("linear-element");
        faceElementCategories.insert(elementNode.attr<std::string>("category-id"));
    }

    auto topologyGroupNodes = doc.nodes("/editor/topology-groups/topology-group");
    for (size_t iGroup = 0; iGroup < topologyGroupNodes.size(); ++iGroup) {
        const auto& topologyGroupNode = topologyGroupNodes[iGroup];
        if (faceElementCategories.count(
                topologyGroupNode.firstElementChild("linear-element").attr<std::string>("category-id"))) {
            faceJunctionCategories.insert(
                topologyGroupNode.firstElementChild("junction").attr<std::string>("category-id"));
        }
    }
}

EditorConfig::EditorConfig(const xml3::Doc& doc)
    : impl_(new Impl(doc))
{}

EditorConfig::EditorConfig(const std::string& path)
    : impl_(new Impl(xml3::Doc(path)))
{}

const std::set<std::string>& EditorConfig::tableAttrRoles(const std::string& categoryId) const
{
    return mapAt(impl_->categoryToTableAttrRole, categoryId, EMPTY);
}

const std::set<std::string>& EditorConfig::geomPartCategories(const std::string& categoryId) const
{
    return mapAt(impl_->categoryToGeomPartCategory, categoryId, EMPTY);
}

const std::set<std::string>& EditorConfig::geomPartRoles(const std::string& categoryId) const
{
    return mapAt(impl_->categoryToGeomPartRole, categoryId, EMPTY);
}

const std::set<std::string>& EditorConfig::geomPartMasterRoles(const std::string& categoryId) const
{
    return mapAt(impl_->categoryToGeomPartMasterRole, categoryId, EMPTY);
}

bool EditorConfig::isFaceCategory(const std::string& categoryId) const
{
    return impl_->faceCategories.count(categoryId);
}

bool EditorConfig::isFaceElementCategory(const std::string& categoryId) const
{
    return impl_->faceElementCategories.count(categoryId);
}

bool EditorConfig::isFaceJunctionCategory(const std::string& categoryId) const
{
    return impl_->faceJunctionCategories.count(categoryId);
}

} // namespace maps::wiki::diffalert
