#pragma once

#include <maps/wikimap/mapspro/services/mrc/libs/db/include/common.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/frame.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/object.h>

#include <utility>
#include <vector>

namespace maps::mrc::eye {

inline db::TIds toIdList(const db::TIdSet& idSet) { return {idSet.begin(), idSet.end()}; }

template<class Range>
inline db::TIdSet toIdSet(const Range& ids) { return {ids.begin(), ids.end()}; }

void insert(db::TIdSet& toInsert, const db::TIdSet& ids);

db::TIdSet join(db::TIdSet lhs, const db::TIdSet& rhs);

db::TIds join(db::TIds lhs, db::TIds rhs);

db::TIdSet diff(db::TIdSet lhs, const db::TIdSet& rhs);

db::TIds diff(db::TIds lhs, db::TIds rhs);

bool intersected(const db::TIdSet& lhs, const db::TIdSet& rhs);

template<class T>
inline db::TId getId(const T& t) { return t.id(); }

inline db::TId getId(const db::eye::FrameLocation& location) { return location.frameId(); }

inline db::TId getId(const db::eye::FramePrivacy& privacy) { return privacy.frameId(); }

inline db::TId getId(const db::eye::ObjectLocation& location) { return location.objectId(); }

template<class T>
db::IdTo<T> byId(std::vector<T> objects)
{
    db::IdTo<T> result;
    for (auto&& object: objects) {
        result.emplace(getId(object), object);
    }

    return result;
}

template<class T>
void insert(db::IdTo<T>& objectById, std::vector<T> objects)
{
    for (auto&& object: objects) {
        objectById.emplace(getId(object), std::move(object));
    }
}

template<class Object>
db::TIds collectIds(const std::vector<Object>& objects)
{
    db::TIds objectIds;
    objectIds.reserve(objects.size());

    for (const auto& object: objects) {
        objectIds.push_back(getId(object));
    }

    return objectIds;
}

template<class Object>
db::TIds collectIds(const db::IdTo<Object>& objectById)
{
    db::TIds objectIds;
    objectIds.reserve(objectById.size());

    for (const auto& [id, _]: objectById) {
        objectIds.push_back(id);
    }

    return objectIds;
}

db::IdTo<db::TId> reverse(const db::IdTo<db::TId>& idMap);

class IdMapChain {

public:
    using IdMap = db::IdTo<db::TId>;
    using IdMaps = std::vector<const IdMap*>;

    IdMapChain(std::initializer_list<const IdMap*> init): idMaps_(init) {}

    db::TId at(db::TId id) const {
        db::TId initialId = id;
        for (size_t i = 0; i < idMaps_.size(); ++i) {
            const auto& idMap = *idMaps_[i];

            const auto it = idMap.find(id);
            REQUIRE(it != idMap.end(), "Failed to resolve " << initialId
                << " invalid id " << id << " for map " << i);

            id = it->second;
        }

        return id;
    }

private:
    IdMaps idMaps_;
};


// id фотографии и id детекции на этой фотографии
struct FrameDetectionId {
    db::TId frameId;
    db::TId detectionId;
};

bool operator<(const FrameDetectionId& lhs, const FrameDetectionId& rhs);
bool operator==(const FrameDetectionId& lhs, const FrameDetectionId& rhs);

using FrameDetectionIdSet = std::set<FrameDetectionId>;


// id двух детекций
using DetectionIdPair = std::pair<db::TId, db::TId>;
using DetectionIdPairs = std::vector<DetectionIdPair>;
using DetectionIdPairSet = std::set<DetectionIdPair>;

} // namespace maps::mrc::eye
