#include <maps/wikimap/mapspro/services/mrc/libs/birdview/include/panel_detector.h>

#include <maps/libs/common/include/exception.h>

namespace maps::mrc::birdview {
namespace {

cv::Mat calculateGradientMagnitude(const cv::Mat& img)
{
    static const double GAUSSIAN_SIGMA = 0.9;

    CV_Assert((img.type() == CV_8UC1) || (img.type() == CV_8UC3));

    cv::Mat gray;
    if (img.type() == CV_8UC3) {
        cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
    }
    else {
        img.copyTo(gray);
    }
    cv::GaussianBlur(gray, gray, cv::Size(), GAUSSIAN_SIGMA, GAUSSIAN_SIGMA);

    cv::Mat gradX;
    cv::Sobel(gray, gradX, CV_32F, 1, 0);

    cv::Mat gradY;
    cv::Sobel(gray, gradY, CV_32F, 0, 1);

    cv::Mat result;
    cv::magnitude(gradX, gradY, result);
    return result;
}

int findPanelRow(const cv::Mat& img)
{
    static const float THRESHOLD_FACTOR = 20.;
    const float thresholdSum = THRESHOLD_FACTOR * img.cols;

    auto isPanel = [=](float row) {
        return row < thresholdSum;
    };

    cv::Mat sum;
    cv::reduce(img, sum, 1, cv::REDUCE_SUM, CV_32F);
    CV_Assert(sum.isContinuous());

    const auto rbegin = std::make_reverse_iterator(sum.end<float>() - 1);
    const auto rend = rbegin + img.rows;
    auto it = std::find_if(rbegin, rend, isPanel);

    if (it == rend) {
        return img.rows; // panel is out of view
    }

    it = std::find_if_not(it, rend, isPanel);
    return rend - it;
}

} // anonymous namespace

int PanelDetector::update(const cv::Mat& img)
{
    static const float SMOOTHING_RATIO = 0.1;
    static const int PART_FACTOR = 3;

    if (not size_.empty()) {
        REQUIRE(size_ == img.size(), "Different sizes: " << size_ << " != " << img.size());
    } else {
        size_ = img.size();
    }

    const int minPanelY = img.rows - img.rows / PART_FACTOR;

    const cv::Mat gradientMagnitude = calculateGradientMagnitude(img.rowRange(minPanelY, img.rows));
    if (not gradientMagnitude_.empty()) {
        cv::Mat absDiff;
        cv::absdiff(gradientMagnitude_, gradientMagnitude, absDiff);
        const int row  = findPanelRow(absDiff) + minPanelY;

        panelRow_ = panelRow_ >= 0
            ? (1. - SMOOTHING_RATIO) * panelRow_ + SMOOTHING_RATIO * row
            : row;
    }

    gradientMagnitude_ = gradientMagnitude;

    return panelRow();
}

} // namespace maps::mrc::birdview
