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

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

namespace maps::mrc::common {

ImageBox ImageBox::fromJson(const json::Value& box) {
    return {
        box[0][0].as<size_t>(),
        box[0][1].as<size_t>(),
        box[1][0].as<size_t>(),
        box[1][1].as<size_t>()
    };
}

std::ostream& operator<<(std::ostream& out, const ImageBox& box)
{
    return out << box.minX() << "," << box.minY() << ","
               << box.maxX() << "," << box.maxY();
}

std::ostream& operator<<(std::ostream& out, const ImageBoxes& boxes)
{
    std::string delim = "";
    for (const auto& box: boxes) {
        out << delim << box;
        delim = ";";
    }
    return out;
}

namespace {

inline auto makeJsonPoint(size_t x, size_t y)
{
    // cast to unsigned to avoid the problem of type changing after deserialization
    return json::Value{
        json::Value{static_cast<int64_t>(x)},
        json::Value{static_cast<int64_t>(y)},
    };
}

} // namespace

json::Value toJson(const ImageBox& box)
{
    return json::Value{
        makeJsonPoint(box.minX(), box.minY()),
        makeJsonPoint(box.maxX(), box.maxY())
    };
}

void json(const ImageBox& box, maps::json::ArrayBuilder coords)
{
    coords << [&](json::ArrayBuilder coord) {
        coord << box.minX() << box.minY();
    };

    coords << [&](json::ArrayBuilder coord) {
        coord << box.maxX() << box.maxY();
    };
}

std::optional<ImageBox> intersect(const ImageBox& lhs, const ImageBox& rhs)
{
    const auto minX = std::max(lhs.minX(), rhs.minX());
    const auto maxX = std::min(lhs.maxX(), rhs.maxX());

    const auto minY = std::max(lhs.minY(), rhs.minY());
    const auto maxY = std::min(lhs.maxY(), rhs.maxY());

    if (maxX <= minX or maxY <= minY) {
        return std::nullopt;
    }

    return ImageBox{minX, minY, maxX, maxY};
}

double getIoU(const ImageBox& lhs, const ImageBox& rhs)
{
    const auto intersection = intersect(lhs, rhs);

    if (not intersection) {
        return 0;
    }

    const double intersectionArea = intersection->area();
    const double unionArea = lhs.area() + rhs.area() - intersectionArea;

    return intersectionArea / unionArea;
}

double getIntersectionRatio(const ImageBox& box, const cv::Mat& mask)
{
    ASSERT(mask.dims == 2);

    if (not box.height() or not box.width()) {
        return 0.0;
    }

    const std::size_t area = cv::countNonZero(mask(box));
    return static_cast<double>(area) / box.area();
}

double getBoxToFrameSizeRatio(const ImageBox& box, const Size& frameSize)
{
    const double frameSquare = frameSize.pixels();
    if (frameSquare == 0) {
        return 0;
    }

    return box.area() / frameSquare;
}

} // maps::mrc::common
