#pragma once

#include <maps/libs/geolib/include/bounding_box.h>
#include <maps/libs/geolib/include/serialization.h>

#include <maps/libs/json/include/builder.h>

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

#include <vector>

namespace maps::wiki::geom_tools {

// NOTE: Expected that geometry has boundingBox() method.
template<class Geometry>
class ObjectDiffBuilder
{
public:
    ObjectDiffBuilder& setBefore(std::vector<Geometry> before);
    ObjectDiffBuilder& setAfter(std::vector<Geometry> after);

    // Construct JSON object according to schema
    // social.common.schema.json#/definitions/objectDiffType
    void json(json::ObjectBuilder& builder) const;

private:
    geolib3::BoundingBox boundingBox() const;

    std::vector<Geometry> before_;
    std::vector<Geometry> after_;
};

template<class Geometry>
ObjectDiffBuilder<Geometry>&
ObjectDiffBuilder<Geometry>::setAfter(std::vector<Geometry> after)
{
    after_ = std::move(after);
    return *this;
}

template<class Geometry>
ObjectDiffBuilder<Geometry>&
ObjectDiffBuilder<Geometry>::setBefore(std::vector<Geometry> before)
{
    before_ = std::move(before);
    return *this;
}

template<class Geometry>
geolib3::BoundingBox ObjectDiffBuilder<Geometry>::boundingBox() const
{
    REQUIRE(!before_.empty() || !after_.empty(),
            "ObjectDiffBuilder geometry is empty");

    auto bbox = !before_.empty()
        ? before_.front().boundingBox()
        : after_.front().boundingBox();

    auto expandAll = [&bbox] (const auto& geoms) {
        for (const auto& geom : geoms) {
            bbox = geolib3::expand(bbox, geom);
        }
    };

    expandAll(before_);
    expandAll(after_);
    return bbox;
}


template<class Geometry>
void ObjectDiffBuilder<Geometry>::json(json::ObjectBuilder& builder) const
{
    const auto bounds = boundingBox();
    builder["bounds"] << [&](json::ArrayBuilder arrayBuilder) {
        arrayBuilder << bounds.minX();
        arrayBuilder << bounds.minY();
        arrayBuilder << bounds.maxX();
        arrayBuilder << bounds.maxY();
    };

    builder["modified"] << [&](json::ObjectBuilder builder) {
        builder["geometry"] << [&](json::ObjectBuilder builder) {
            builder["before"] << [&](json::ArrayBuilder builder) {
                for (const auto& geometry: before_) {
                    builder << geolib3::geojson(geometry);
                }
            };
            builder["after"] << [&](json::ArrayBuilder builder) {
                for (const auto& geometry: after_) {
                    builder << geolib3::geojson(geometry);
                }
            };
        };
    };
}

} // namespace maps::wiki::geom_tools
