#pragma once

#include <maps/wikimap/mapspro/services/mrc/libs/common/include/types.h>

#include <maps/libs/introspection/include/comparison.h>
#include <maps/libs/introspection/include/stream_output.h>
#include <maps/libs/json/include/builder.h>
#include <maps/libs/json/include/value.h>

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

#include <opencv2/opencv.hpp>

#include <cstddef>
#include <ostream>
#include <vector>

namespace maps::mrc::common {

class ImageBox {

public:
    ImageBox() = default;

    constexpr ImageBox(size_t minX, size_t minY, size_t maxX, size_t maxY)
        : minX_(minX)
        , minY_(minY)
        , maxX_(maxX)
        , maxY_(maxY)
    {
        REQUIRE(minX <= maxX && minY <= maxY,  "Invalid image box: "
                << "[[" << minX << "," << minY << "],[" << maxX << "," << maxY << "]]");
    }

    ImageBox(const cv::Rect& rect)
        : ImageBox(rect.tl().x, rect.tl().y, rect.br().x, rect.br().y)
    {}

    // Schema [[min_x, min_y], [max_x, max_y]]
    static ImageBox fromJson(const json::Value& value);

    static constexpr ImageBox createBySize(size_t minX, size_t minY, size_t width, size_t height)
    {
        return ImageBox{minX, minY, minX + width, minY + height};
    }

    operator cv::Rect() const { return cv::Rect(minX(), minY(), width(), height()); }

    constexpr size_t minX() const { return minX_; }
    constexpr size_t minY() const { return minY_; }
    constexpr size_t maxX() const { return maxX_; }
    constexpr size_t maxY() const { return maxY_; }

    constexpr size_t height() const { return maxY() - minY(); }
    constexpr size_t width() const { return maxX() - minX(); }

    constexpr size_t area() const { return height() * width(); }

    auto introspect() const { return std::tie(minX_, minY_, maxY_, maxY_); }

private:
    size_t minX_;
    size_t minY_;
    size_t maxX_;
    size_t maxY_;
};

using ImageBoxes = std::vector<ImageBox>;

inline bool operator==(const ImageBox& lhs, const ImageBox& rhs) { return lhs.introspect() == rhs.introspect(); }

// WARN: These output operators produce values in format acceptable by MRC browser.
std::ostream& operator<<(std::ostream& out, const ImageBox& box);

// Schema [[min_x, min_y], [max_x, max_y]]
json::Value toJson(const ImageBox& box);
void json(const ImageBox& box, maps::json::ArrayBuilder coords);

std::optional<ImageBox> intersect(const ImageBox& lhs, const ImageBox& rhs);

double getIoU(const ImageBox& lhs, const ImageBox& rhs);

double getIntersectionRatio(const ImageBox& box, const cv::Mat& boolMask);

// Return square ratio or 0 if size is degenerate
double getBoxToFrameSizeRatio(const ImageBox& box, const Size& frameSize);

} // namespace maps::mrc::common
