#include "objects_creator.h"

#include <maps/libs/json/include/std.h>
#include <maps/libs/json/include/value.h>

#include <util/generic/overloaded.h>


namespace maps::wiki::tests {

namespace {
const CoordinatesVec DEFAULT_COORDS_AOI{{51.0, 51.0}, {53.0, 51.0}, {53.0, 53.0}, {51.0, 53.0}};
const CoordinatesVec DEFAULT_COORDS_BLD{{52.0, 52.0}, {52.01, 52.0}, {52.01, 52.01}, {52.0, 52.01}};
const Coordinates    DEFAULT_COORDS_POI_FOOD{52.0, 52.0};
const CoordinatesVec DEFAULT_COORDS_RD_EL{{52.0, 52.0}, {52.1, 52.0}};
const Coordinates    DEFAULT_COORDS_RD_JC{52.0, 52.0};
} // namespace


Coordinates::Coordinates(double x, double y)
    : x(x)
    , y(y)
{}


Coordinates::Coordinates(const json::Value& value)
    : Coordinates(value[0].as<double>(), value[1].as<double>())
{}


void Coordinates::json(json::ArrayBuilder builder) const
{
    builder << x << y;
}


Point::Point(Coordinates coordinates)
    : Coordinates(std::move(coordinates))
{}


Point::Point(const json::Value& value)
    : Point(Coordinates(value["coordinates"]))
{}


void Point::json(json::ObjectBuilder builder) const
{
    builder["coordinates"] = static_cast<Coordinates>(*this);
    builder["type"] = "Point";
}


LineString::LineString(CoordinatesVec coordinates)
    : coordinates(std::move(coordinates))
{}


LineString::LineString(const json::Value& value)
{
    const auto& coordVec = value["coordinates"];
    for (const auto& coords: coordVec) {
        coordinates.emplace_back(coords);
    }
}


void LineString::json(json::ObjectBuilder builder) const
{
    builder["coordinates"] = coordinates;
    builder["type"] = "LineString";
}


Polygon::Polygon(std::vector<CoordinatesVec> coordinates)
    : coordinates(std::move(coordinates))
{}


Polygon::Polygon(const json::Value value)
{
    const auto& vecOfCoordVec = value["coordinates"];
    for (const auto& coordVec: vecOfCoordVec) {
        CoordinatesVec path;
        for (const auto& coords: coordVec) {
            path.emplace_back(coords);
        }
        coordinates.emplace_back(std::move(path));
    }
}


void Polygon::json(json::ObjectBuilder builder) const
{
    builder["coordinates"] = coordinates;
    builder["type"] = "Polygon";
}


Geometry::Geometry(const json::Value& value)
{
    const auto& type = value["type"].as<std::string>();

    if (type == "Point") {
        this->emplace<Point>(value);
    } else if (type == "LineString") {
        this->emplace<LineString>(value);
    } else if (type == "Polygon") {
        this->emplace<Polygon>(value);
    } else {
        throw LogicError() << "Unsupported type: '" << type << "'.";
    }
}


void Geometry::json(json::ObjectBuilder builder) const
{
    std::visit(
        TOverloaded {
            [&](const Point&      point)      {      point.json(builder); },
            [&](const LineString& lineString) { lineString.json(builder); },
            [&](const Polygon&    polygon)    {    polygon.json(builder); },
        },
        *this
    );
}


void EditContext::json(json::ObjectBuilder builder) const
{
    builder["zoom"] = zoom;
    builder["center"] = center;
}


ObjectsCreator::operator std::string() const
{
    json::Builder builder;
    builder << *this;
    return builder.str();
}


void ObjectsCreator::json(json::ObjectBuilder builder) const
{
    builder["uuid"] = "00000000-0000-0000-0000-000000000000";
    builder["categoryId"] = categoryId_;
    builder["editContext"] = editContext_;
    builder["geometry"] = geometry_;

    if (revisionId_) {
        builder["revisionId"] = (*revisionId_);
    }

    if (!attributes_.empty()) {
        builder["attrs"] = attributes_;
    }
}


ObjectsCreator aoi()
{
    Geometry geometry{Polygon{std::vector<CoordinatesVec>{DEFAULT_COORDS_AOI}}};
    return ObjectsCreator().categoryId("aoi").geometry(geometry);
}

ObjectsCreator poiFood()
{
    Geometry geometry{Point{DEFAULT_COORDS_POI_FOOD}};
    return ObjectsCreator().categoryId("poi_food").geometry(geometry);
}

ObjectsCreator building()
{
    Geometry geometry{Polygon{std::vector<CoordinatesVec>{DEFAULT_COORDS_BLD}}};
    return ObjectsCreator().categoryId("bld").geometry(geometry);
}

ObjectsCreator rdEl()
{
    Geometry geometry{LineString{DEFAULT_COORDS_RD_EL}};
    return ObjectsCreator().categoryId("rd_el").geometry(geometry);
}

ObjectsCreator rdJc()
{
    Geometry geometry{Point{DEFAULT_COORDS_RD_JC}};
    return ObjectsCreator().categoryId("rd_jc").geometry(geometry);
}

} // namespace maps::wiki::tests
