#include <maps/wikimap/mapspro/services/mrc/eye/lib/location/include/location.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/detection/include/match.h>

#include <maps/libs/log8/include/log8.h>

namespace maps::mrc::eye {

namespace {

MatchedData reverse(const MatchedData& matchedData)
{
    cv::Mat fundMatrix;
    cv::transpose(matchedData.fundMatrix, fundMatrix);
    return MatchedData{
        .goodPtsCnt = matchedData.goodPtsCnt,
        .sampsonDistance = matchedData.sampsonDistance,
        .fundMatrix = fundMatrix,
        .hullDistance0 = matchedData.hullDistance1,
        .hullDistance1 = matchedData.hullDistance0
    };
}


} // namespace

void CachingFrameMatcher::insertMatches(MatchedFramesPairs framesMatches) const
{
    std::lock_guard<std::mutex> lock(mutex_);
    for (auto& framesMatch : framesMatches) {
        const auto framesPair = std::make_pair(framesMatch.id0, framesMatch.id1);
        auto it = frameIdsPairToMatch_.find(framesPair);
        if (it == frameIdsPairToMatch_.end()) {
            frameIdsPairToMatch_.emplace(std::move(framesPair), std::move(framesMatch));
        } else {
            it->second = std::move(framesMatch);
        }
    }
}

MatchedFramesPairs CachingFrameMatcher::makeMatches(
        const DetectionStore& store,
        const std::vector<std::pair<db::TId, db::TId>>& frameIdPairs) const
{
    MatchedFramesPairs result;
    std::vector<std::pair<db::TId, db::TId>> pairsForMatching;

    std::unique_lock<std::mutex> lock(mutex_);

    for (const auto& frameIdPair : frameIdPairs) {
        auto it = frameIdsPairToMatch_.find(frameIdPair);
        if (it != frameIdsPairToMatch_.end()) {
            result.push_back(it->second);
            continue;
        }
        const auto reversedFrameIdPair = std::make_pair(frameIdPair.second, frameIdPair.first);
        it = frameIdsPairToMatch_.find(reversedFrameIdPair);
        if (it != frameIdsPairToMatch_.end()) {
            result.push_back(it->second);
            continue;
        }

        pairsForMatching.push_back(frameIdPair);
    }
    lock.unlock();
    auto matches = frameMatcher_.makeMatches(store, pairsForMatching);
    insertMatches(matches);
    result.insert(result.end(),
        std::make_move_iterator(matches.begin()),
        std::make_move_iterator(matches.end()));
    return result;
}

MatchedFrameDetection::MatchedFrameDetection(
    FrameDetectionId id0,
    FrameDetectionId id1,
    float relevance,
    std::optional<MatchedFrameDetection::Verdict> verdict,
    std::optional<MatchedData> data)
    : id0_(std::move(id0))
    , id1_(std::move(id1))
    , relevance_(relevance)
    , verdict_(std::move(verdict))
    , data_(std::move(data))
{
    REQUIRE(relevance >= 0., "Relevance must not be negative but given value " << relevance);
}

MatchedFrameDetection reverseMatch(const MatchedFrameDetection& match)
{
    return MatchedFrameDetection(
        match.id1(),
        match.id0(),
        match.relevance(),
        match.verdict(),
        match.data().has_value()
            ? std::make_optional(reverse(match.data().value()))
            : std::nullopt);
}

bool operator<(const RelevanceWithVerdict& lhs, const RelevanceWithVerdict& rhs)
{
    if (lhs.verdict == rhs.verdict) {
        return lhs.relevance < rhs.relevance;
    }

    if (lhs.verdict.has_value()) {
        if (lhs.verdict.value() == MatchedFrameDetection::Verdict::Yes) {
            return false;
        }
        return true;
    }

    ASSERT(rhs.verdict.has_value());

    if (rhs.verdict.value() == MatchedFrameDetection::Verdict::Yes) {
        return true;
    }
    return false;
}


PatchedMatcher::PatchedMatcher(
    const DetectionMatcher* baseMatcher, MatchDetectionsFunc matchFunc)
    : baseMatcher_(baseMatcher), matchFunc_(matchFunc)
{ }

MatchedFrameDetections
PatchedMatcher::makeMatches(
    const DetectionStore& store,
    const DetectionIdPairSet& detectionPairs,
    const FrameMatcher* frameMatcherPtr) const
{
    DetectionIdPairSet pairsToCheck;
    MatchedFrameDetections result;
    for (const auto& pair : detectionPairs) {
        if (auto verdict = matchFunc_(pair.first, pair.second)) {
            result.push_back(MatchedFrameDetection{
                {.frameId = store.frameId(pair.first), .detectionId = pair.first},
                {.frameId = store.frameId(pair.second), .detectionId = pair.second},
                1.,
                verdict.value()
            });
        } else {
            pairsToCheck.insert(pair);
        }
    }
    auto matchedDetections = baseMatcher_->makeMatches(store, pairsToCheck, frameMatcherPtr);
    result.insert(result.end(), matchedDetections.begin(), matchedDetections.end());
    return result;
}

} // namespace maps::mrc::eye
