#include "getrenderersublayers2.h"

#include <maps/wikimap/mapspro/services/editor/src/check_permissions.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/config.h>
#include <maps/wikimap/mapspro/services/editor/src/exception.h>
#include <maps/wikimap/mapspro/services/editor/src/acl_utils.h>
#include <maps/wikimap/mapspro/libs/acl/include/exception.h>
#include <yandex/maps/wiki/configs/editor/categories.h>

#include <contrib/libs/yaml-cpp/include/yaml-cpp/yaml.h>

#include <filesystem>

namespace maps::wiki {

namespace {

bool intersects(const StringSet& set1, const StringSet& set2)
{
    auto it1 = set1.begin();
    auto it2 = set2.begin();
    while (it1 != set1.end() && it2 != set2.end()) {
        if (*it1<*it2) {
            ++it1;
        } else if (*it2<*it1) {
            ++it2;
        } else {
            return true;
        }
    }
    return false;
}

} // namespace

bool
GetLayers2::ProjectParams::match(
    const ProjectParams& requested,
    FilterPolicy filterByAcl) const
{
    if (!mapTypes.empty() && !intersects(mapTypes, requested.mapTypes)) {
        return false;
    }
    if (!projects.empty() && !intersects(projects, requested.projects)) {
        return false;
    }
    if (filterByAcl == FilterPolicy::Filter && !aclGroups.empty() &&
        !intersects(aclGroups, requested.aclGroups)) {
        return false;
    }
    return true;
}

namespace {

StringSet
geompartMasters(const std::string& catId)
{
    if (!cfg()->editor()->categories().defined(catId)){
        return {};
    }
    StringSet allGeomPartMasters;
    auto masterCatIds =
        cfg()->editor()->categories()[catId].masterCategoryIds(
            roles::filters::IsGeom);
    for (const auto& masterCatId : masterCatIds) {
        auto masterMasters = geompartMasters(masterCatId);
        allGeomPartMasters.insert(masterMasters.begin(), masterMasters.end());
    }
    allGeomPartMasters.insert(masterCatIds.begin(), masterCatIds.end());
    return allGeomPartMasters;
}

std::set<std::string> getCategories(const YAML::Node& categories)
{
    std::set<std::string> result;
    if (categories.IsDefined()) {
        for (auto category : categories) {
            result.insert(category.as<std::string>());
        };
    }
    return result;
}

const std::string MPRO_LAYER_PROJECTS = "projects";
const std::string MPRO_LAYER_MAPTYPES = "map_type";
const std::string MPRO_LAYER_ACL_GROUPS = "acl_groups";

const char MPRO_LAYER_ARRAY_DELIMITER = ',';

GetLayers2::ProjectParams getProjectParams(const YAML::Node& node)
{
    return {
        split<StringSet>(node[MPRO_LAYER_MAPTYPES].as<std::string>(""), MPRO_LAYER_ARRAY_DELIMITER),
        split<StringSet>(node[MPRO_LAYER_PROJECTS].as<std::string>(""), MPRO_LAYER_ARRAY_DELIMITER),
        split<StringSet>(node[MPRO_LAYER_ACL_GROUPS].as<std::string>(""), MPRO_LAYER_ARRAY_DELIMITER)
    };
}

std::list<GetLayers2::LayersGroup> getLayersGroups()
{
    auto loadYaml = [] (const std::string& layersPath) {
        std::list<GetLayers2::LayersGroup> result;
        const auto groupsFile = layersPath + "/v2/groups.yaml";
        if (!std::filesystem::exists(groupsFile)) {
            throw maps::LogicError() << "Renderers groups.yaml file does not exist: " << groupsFile;
        }

        YAML::Node yaml = YAML::LoadFile(groupsFile);

        for (auto groupNode : yaml) {
            result.push_back({});
            auto& group = result.back();
            group.id = groupNode["id"].as<std::string>("");
            group.label = groupNode["label"].as<std::string>("");
            group.categories = getCategories(groupNode["categories"]);

            for (auto layerNode : groupNode["layers"]) {
                group.layers.push_back({});
                auto& layer = group.layers.back();
                layer.id = layerNode["id"].as<std::string>("");
                layer.label = layerNode["label"].as<std::string>("");
                layer.name = layerNode["name"].as<std::string>("");
                layer.type = layerNode["type"].as<std::string>("base");
                layer.categories = getCategories(layerNode["categories"]);
                layer.projectParams = getProjectParams(layerNode);

                auto representations = layerNode["presentations"];
                if (representations.IsDefined() && representations.size() > 0) {
                    for (auto presentationNode : representations) {
                        layer.representations.push_back({});
                        auto& representaion = layer.representations.back();
                        representaion.id = presentationNode["id"].as<std::string>("");
                        representaion.label = presentationNode["label"].as<std::string>("");
                        representaion.name = presentationNode["name"].as<std::string>("");
                        representaion.type = presentationNode["type"].as<std::string>("base");
                        representaion.projectParams = getProjectParams(presentationNode);
                    }
                }
            }
            for (const auto& layer : group.layers) {
                for (const auto& catId : layer.categories) {
                    auto masters = geompartMasters(catId);
                    group.categories.insert(masters.begin(), masters.end());
                }
            }
        }

        return result;
    };
    const std::string layersPath = cfg()->rendererParams().layersPath();
    static const auto groups = loadYaml(layersPath);
    return groups;
};

struct FilterParams
{
    GetLayers2::ProjectParams project;
    bool showFilterTypeLayers;
    GetLayers2::FilterPolicy filterByAcl;
};

// todo enum presentations
bool isFilter(const GetLayers2::Layer& layer)
{
    return layer.type == "filter";
}

std::list<GetLayers2::LayersGroup> filterLayersGroups(
    const std::list<GetLayers2::LayersGroup>& src,
    const FilterParams& params)
{
    std::list<GetLayers2::LayersGroup> groups = src;
    for (auto& group : groups) {
        EraseIf(group.layers, [&] (const GetLayers2::Layer& layer) {
            if (isFilter(layer) && !params.showFilterTypeLayers)
                return true;
            return !layer.projectParams.match(params.project, params.filterByAcl);
        });

        for (auto& layer : group.layers) {
            EraseIf(layer.representations, [&] (const GetLayers2::Representation& pres) {
                return !pres.projectParams.match(params.project, params.filterByAcl);
            });
        }
    }
    EraseIf(groups, [] (const GetLayers2::LayersGroup& group) {
        return group.layers.empty();
    });

    return groups;
}

} // namespace

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

std::string GetLayers2::printRequest() const
{
    std::ostringstream os;
    os << " mapType: " << request_.mapType;
    os << " project: " << request_.project;
    os << " uid: " << request_.uid;
    return os.str();
}

void GetLayers2::control()
{
    FilterParams params = {
        {
            request_.mapType.empty() ? StringSet{} : StringSet{request_.mapType},
            request_.project.empty() ? StringSet{} : StringSet{request_.project},
            StringSet{}
        },
        false, /*showFilterTypeLayers*/
        request_.filterByAcl
    };

    if (request_.uid) {
        params.project.aclGroups = userGroups(request_.uid, request_.token);
        auto work = cfg()->poolCore().slaveTransaction(request_.token);
        try {
            if (CheckPermissions(request_.uid, *work).isUserHasAccessToViewFilters()) {
                params.showFilterTypeLayers = true;
            }
        } catch (const acl::ACLException& ex) {
            WARN() << "Caught ACLException while checking access to renderer layers: " << ex;
        }
    }

    auto layersGroups = getLayersGroups();
    result_->groups = filterLayersGroups(layersGroups, params);
}

} // namespace maps::wiki
