#include "geometry_filter.h"
#include "utils/geom.h"

#include <maps/libs/geolib/include/variant.h>

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

namespace maps {
namespace wiki {
namespace groupedit {

GeometryFilter::GeometryFilter()
    : predicate_(GeomPredicate::True)
{ }

GeometryFilter::GeometryFilter(GeomPredicate predicate, std::string wkb)
    : predicate_(predicate)
{
    if (predicate_ == GeomPredicate::True) {
        REQUIRE(wkb.empty(), "Geometry provided for true predicate");
        return;
    }

    wkb_ = std::move(wkb);

    auto geometryVariant = geolib3::WKB::read<geolib3::SimpleGeometryVariant>(wkb_);
    switch (geometryVariant.geometryType()) {
        case (geolib3::GeometryType::Polygon):
            polygonGeom_ = geometryVariant.get<geolib3::Polygon2>();
            break;
        case (geolib3::GeometryType::LineString):
            if (predicate_ == GeomPredicate::Within) {
                throw maps::LogicError(
                    "Within relationship for polylines is not supported");
            }
            polylineGeom_ = geometryVariant.get<geolib3::Polyline2>();
            break;
        default:
            throw maps::LogicError("Unexpected aoi geometry type");
    }
}

GeomPredicate GeometryFilter::predicate() const
{ return predicate_; }

const std::string& GeometryFilter::wkb() const
{ return wkb_; }

std::optional<geolib3::BoundingBox> GeometryFilter::boundingBox() const
{
    if (predicate_ == GeomPredicate::True) {
        return std::nullopt;
    }
    if (polygonGeom_) {
        return polygonGeom_->boundingBox();
    }
    if (polylineGeom_) {
        return polylineGeom_->boundingBox();
    }
    throw LogicError("Geometry not set for non-trivial predicate");
}

bool GeometryFilter::apply(
        const std::optional<std::string>& optionalWkb) const
{
    if (predicate_ == GeomPredicate::True) {
        return true;
    }
    ASSERT(polygonGeom_ || polylineGeom_);
    if (!optionalWkb) {
        return false;
    }
    switch (predicate_) {
        case (GeomPredicate::Within):
            ASSERT(polygonGeom_);
            return utils::contains(*polygonGeom_, *optionalWkb);
        case (GeomPredicate::Intersects):
            return polygonGeom_
                ? utils::intersects(*polygonGeom_, *optionalWkb)
                : utils::intersects(*polylineGeom_, *optionalWkb);
        case (GeomPredicate::True):
            throw LogicError("Unexpected predicate");
    }
    throw LogicError("Unreachable code");
}

} // namespace groupedit
} // namespace wiki
} // namespace maps
