#pragma once

#include <maps/wikimap/mapspro/services/mrc/eye/lib/detection/include/store.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/location/include/location.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/common/include/id.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/common.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/verified_detection_pair_match.h>

#include <opencv2/opencv.hpp>

#include <map>
#include <mutex>

namespace maps::mrc::eye {

struct FramesMatchData {
    int goodPtsCnt;  // кол-во "хороших" точек, оставшихся после поиска фундаментальной матрицы
    int ptsCnt0;  // общее кол-во точек на фрейме 0
    int ptsCnt1;  // общее кол-во точек на фрейме 1
    cv::Mat fundMatrix;
    std::vector<cv::Point2f> hull0; // выпуклая оболочка хороших точек на нулевом фрейме
    std::vector<cv::Point2f> hull1; // выпуклая оболочка хороших точек на первом фрейме
};

struct MatchedFramesPair {
    db::TId id0{};
    db::TId id1{};
    FramesMatchData match;
};
using MatchedFramesPairs = std::vector<MatchedFramesPair>;


class FrameMatcher {
public:
    virtual ~FrameMatcher() = default;

    virtual MatchedFramesPairs makeMatches(
        const DetectionStore& store,
        const std::vector<std::pair<db::TId, db::TId>>& frameIdPairs) const = 0;
};

class CachingFrameMatcher : public FrameMatcher{
public:
    CachingFrameMatcher(const FrameMatcher& frameMatcher)
        : frameMatcher_(frameMatcher)
    {}

    MatchedFramesPairs makeMatches(
        const DetectionStore& store,
        const std::vector<std::pair<db::TId, db::TId>>& frameIdPairs) const override;

private:
    void insertMatches(MatchedFramesPairs framesMatches) const;

    const FrameMatcher& frameMatcher_;
    mutable std::mutex mutex_;
    mutable std::map<std::pair<db::TId, db::TId>, MatchedFramesPair> frameIdsPairToMatch_;
};


struct MatchedData {
    int goodPtsCnt; // кол-во хороших точек оставшихся после поиска фундаментальной матрицы
    double sampsonDistance;
    cv::Mat fundMatrix;
    double hullDistance0;
    double hullDistance1;
};

class MatchedFrameDetection {
public:
    enum Verdict {
        No,
        Yes
    };

    // @param relevance must not be negative
    MatchedFrameDetection(
        FrameDetectionId id0,
        FrameDetectionId id1,
        float relevance,
        std::optional<Verdict> verdict = std::nullopt,
        std::optional<MatchedData> data = std::nullopt
    );

    const FrameDetectionId& id0() const { return id0_; }
    const FrameDetectionId& id1() const { return id1_; }

    // The greater relevance is the more probable that detections match
    // must not be negative
    float relevance() const { return relevance_; }

    // Contains reliable verdict about whether detections match or not
    const std::optional<Verdict>& verdict() const { return verdict_; }
    const std::optional<MatchedData>& data() const { return data_; }

private:
    FrameDetectionId id0_;
    FrameDetectionId id1_;
    float relevance_;
    std::optional<Verdict> verdict_;
    std::optional<MatchedData> data_;
};

using MatchedFrameDetections = std::vector<MatchedFrameDetection>;

MatchedFrameDetection reverseMatch(const MatchedFrameDetection& match);

struct RelevanceWithVerdict {
    float relevance;
    std::optional<MatchedFrameDetection::Verdict> verdict;
};

bool operator<(const RelevanceWithVerdict& lhs, const RelevanceWithVerdict& rhs);

class DetectionMatcher {
public:

    virtual MatchedFrameDetections makeMatches(
        const DetectionStore& store,
        const DetectionIdPairSet& detectionPairs,
        const FrameMatcher* frameMatcherPtr) const = 0;

    virtual ~DetectionMatcher() = default;
};


using MatchDetectionsFunc =
        std::function<std::optional<MatchedFrameDetection::Verdict> (db::TId, db::TId)>;

// Wraps given matcher, overrides its match results with
// given verified results
class PatchedMatcher : public DetectionMatcher {
public:

    PatchedMatcher(const DetectionMatcher* baseMatcher, MatchDetectionsFunc matchFunc);

    MatchedFrameDetections makeMatches(
        const DetectionStore& store,
        const DetectionIdPairSet& detectionPairs,
        const FrameMatcher* frameMatcherPtr) const override;
private:
    const DetectionMatcher* baseMatcher_;
    MatchDetectionsFunc matchFunc_;
};

} // namespace maps::mrc::eye
