#include "point_to_bld.h"

#include "maps/wikimap/mapspro/services/editor/src/branch_helpers.h"
#include "maps/wikimap/mapspro/services/editor/src/exception.h"
#include "maps/wikimap/mapspro/services/editor/src/utils.h"

#include <geos/geom/Point.h>
#include <maps/wikimap/mapspro/libs/views/include/query_builder.h>
#include <maps/wikimap/mapspro/libs/misc_point_to_bld/include/point_to_bld.h>
#include <maps/libs/geolib/include/serialization.h>
#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/geolib/include/conversion_geos.h>
#include <yandex/maps/wiki/common/geom.h>
#include <maps/libs/geolib/include/distance.h>

namespace maps {
namespace wiki {

namespace {

const std::string TASK_METHOD_NAME = "PointToBld";

const double SEARCH_RADIUS = 50.0;
const double INTERSECTION_TOLERANCE = 1.0;

bool
areAdjacentBuildings(
    Transaction& work,
    TBranchId branchId,
    TOid bldId1,
    TOid bldId2)
{
    views::QueryBuilder qb(branchId);

    qb.with(
        "bldGeom1", "",
        "SELECT the_geom FROM " + views::TABLE_OBJECTS_A +
        " WHERE id = " + std::to_string(bldId1)
    );
    qb.with(
        "bldGeom2", "",
        "SELECT the_geom FROM " + views::TABLE_OBJECTS_A +
        " WHERE id = " + std::to_string(bldId2)
    );

    std::ostringstream fields;
    fields
        << "ST_Intersects("
        << "ST_Buffer(bldGeom1.the_geom, " << INTERSECTION_TOLERANCE << ")"
        << ", "
        << "ST_Buffer(bldGeom2.the_geom, " << INTERSECTION_TOLERANCE << ")"
        << ")";
    qb.selectFields(fields.str());

    qb.fromWith("bldGeom1");
    qb.fromWith("bldGeom2");
    qb.whereClause("true");

    auto result = work.exec1(qb.query());
    return result[0].as<bool>();
}

controller::ResultType<PointToBld> makeActionResult(const misc::PointToBldResult& pointToBldResult)
{
    return controller::ResultType<PointToBld>{
        Geom(geolib3::internal::geolib2geosGeometry(pointToBldResult.point)),
        pointToBldResult.distance,
        pointToBldResult.azimuth,
        pointToBldResult.isInsideBld,
        pointToBldResult.bldId
    };
}

}

PointToBld::PointToBld(const Request& request)
    : controller::BaseController<PointToBld>(BOOST_CURRENT_FUNCTION)
    , request_(request)
{
}

const std::string&
PointToBld::taskName()
{
    return TASK_METHOD_NAME;
}

std::string
PointToBld::Request::dump() const
{
    std::stringstream ss;
    ss << " point: " << pointGeoJson;
    ss << " bldNumber: " << bldNumber;
    return ss.str();
}

std::string
PointToBld::printRequest() const
{
    return request_.dump();
}

void
PointToBld::control()
{
    WIKI_REQUIRE(!request_.pointGeoJson.empty(), ERR_BAD_DATA, "Empty geoJson");


    auto pointGeoJsonValue = json::Value::fromString(request_.pointGeoJson);

    auto geoPoint = geolib3::readGeojson<geolib3::Point2>(pointGeoJsonValue);
    WIKI_REQUIRE(abs(geoPoint.y()) <= 85.0, ERR_BAD_DATA,
        "Y coordinate must be <= 85.0. Current value = " << geoPoint.y());
    auto mercPoint = geolib3::convertGeodeticToMercator(geoPoint);

    auto work = BranchContextFacade::acquireWorkReadViewOnly(request_.branchId, request_.token);

    auto resInside = misc::pullPointTowardsBld(
        *work,
        request_.branchId,
        mercPoint,
        true, /* findInsideBld */
        SEARCH_RADIUS,
        "" /* bldNumber */
        );

    if (resInside) {
        *result_ = makeActionResult(*resInside);
        return;
    }

    auto resOutside = misc::pullPointTowardsBld(
        *work,
        request_.branchId,
        mercPoint,
        false, /* findInsideBld */
        SEARCH_RADIUS,
        "" /* bldNumber */
        );

    if (!request_.bldNumber.empty()) {
        auto resOutsideWithAddr = misc::pullPointTowardsBld(
            *work,
            request_.branchId,
            mercPoint,
            false, /* findInsideBld */
            SEARCH_RADIUS,
            request_.bldNumber
            );
        WIKI_REQUIRE(resOutsideWithAddr, ERR_NOT_FOUND, "Too far");

        if (resOutside->bldId != resOutsideWithAddr->bldId &&
            areAdjacentBuildings(*work, request_.branchId,
                resOutside->bldId, resOutsideWithAddr->bldId)) {
            *result_ = makeActionResult(*resOutside);
        } else {
            *result_ = makeActionResult(*resOutsideWithAddr);
        }
    } else {
        WIKI_REQUIRE(resOutside, ERR_NOT_FOUND, "Too far");
        *result_ = makeActionResult(*resOutside);
    }
}

} // namespace wiki
} // namespace maps
