#include "feedback_description_links.h"
#include "column_names.h"
#include "common.h"

#include <maps/libs/geolib/include/distance.h>
#include <maps/libs/geolib/include/polyline.h>
#include <maps/libs/common/include/exception.h>
#include <yandex/maps/wiki/common/string_utils.h>

#include <boost/range/join.hpp>

namespace maps::wiki::route_lost_feedback {

namespace {

geolib3::PointsVector asPoints(const TVector<NYT::TNode>& pointNodes)
{
    geolib3::PointsVector points;
    for (const auto& pointNode : pointNodes) {
        points.push_back(asPoint(pointNode));
    }
    return points;
}

geolib3::BoundingBox boundingBoxOfTracks(
    const geolib3::PointsVector& pointsBeforeLost,
    const geolib3::PointsVector& pointsRouteAfterLost,
    const geolib3::PointsVector& pointsTrackAfterLost)
{
    return geolib3::expand(
        geolib3::boundingBox(pointsBeforeLost),
        geolib3::expand(
            geolib3::boundingBox(pointsRouteAfterLost),
            geolib3::boundingBox(pointsTrackAfterLost)
        )
    );
}

geolib3::PointsVector sparsePoints(const geolib3::PointsVector& points)
{
    ASSERT(!points.empty());
    geolib3::PointsVector sparsed {points.front()};
    for (const auto& point : points) {
        if (geolib3::geoDistance(point, sparsed.back()) > 2) {
            sparsed.push_back(point);
        }
    }
    return sparsed;
}

geolib3::Polyline2 buildEquidistantPolyline(
    const geolib3::PointsVector& points,
    geolib3::Orientation orientation)
{
    return geolib3::equidistant(
        geolib3::Polyline2(points),
        0.00003,
        orientation);
}

geolib3::PointsVector concatPointsVectors(
    const geolib3::PointsVector& lhs,
    const geolib3::PointsVector& rhs)
{
    return boost::copy_range<geolib3::PointsVector>(
        boost::join(lhs, rhs)
    );
}

std::string pointToString(const geolib3::Point2& point)
{
    return std::to_string(point.x()) + "," + std::to_string(point.y());
}

std::string polylineToString(const geolib3::Polyline2& polyline)
{
    return common::join(polyline.points(), pointToString, ",");
}

std::string asStaticApiPolyline(
    const geolib3::PointsVector& points,
    geolib3::Orientation orientation,
    const std::string& color,
    const std::string& width)
{
    // Here we sparse polyline, because geolib3 equidistant
    // doesn't like short segments
    //
    auto sparsedPoints = sparsePoints(points);
    auto equidistant = buildEquidistantPolyline(sparsedPoints, orientation);
    return "c:" + color + ",w:" + width + "," + polylineToString(equidistant);
}

} // unnamed namespace

FeedbackDescriptionLinksGenerator::FeedbackDescriptionLinksGenerator(const NYT::TNode& ytRow)
{
    pointsBeforeLost_ =
        asPoints(ytRow[column_names::POINTS_BEFORE_LOST].AsList());

    std::reverse(pointsBeforeLost_.begin(), pointsBeforeLost_.end());

    pointsRouteAfterLost_ =
        asPoints(ytRow[column_names::POINTS_ROUTE_AFTER_LOST].AsList());

    pointsTrackAfterLost_ =
        asPoints(ytRow[column_names::POINTS_TRACK_AFTER_LOST].AsList());

    ASSERT(!pointsBeforeLost_.empty());
    ASSERT(!pointsRouteAfterLost_.empty());
    ASSERT(!pointsTrackAfterLost_.empty());

    bboxOfTracks_ = boundingBoxOfTracks(
        pointsBeforeLost_,
        pointsRouteAfterLost_,
        pointsTrackAfterLost_
    );

    routeStartPos_ = asPoint(ytRow[column_names::ROUTE_START]);
    routeEndPos_ = asPoint(ytRow[column_names::ROUTE_END]);
}

std::string FeedbackDescriptionLinksGenerator::localLink() const
{
    std::string res = staticApiUrlPlusBbox();

    // draw start, end and lost points
    //
    res += "&pt=";
    res += pointToString(pointsBeforeLost_.front()) + ",pm2am~";
    res += pointToString(pointsBeforeLost_.back()) + ",flag~";
    res += pointToString(pointsRouteAfterLost_.back()) + ",pm2bm~";
    res += pointToString(pointsTrackAfterLost_.back()) + ",pm2bm";

    // draw lines
    //
    res += "&pl=";

    // route after lost, blue
    //
    res += asStaticApiPolyline(
        pointsRouteAfterLost_,
        geolib3::Orientation::Counterclockwise,
        blueColor_,
        lineWidth_
    );

    res += "~";

    // route and track before lost, green
    //
    res += asStaticApiPolyline(
        pointsBeforeLost_,
        geolib3::Orientation::Clockwise,
        greenColor_,
        lineWidth_
    );

    res += "~";

    // track after lost, green
    //
    res += asStaticApiPolyline(
        pointsTrackAfterLost_,
        geolib3::Orientation::Clockwise,
        greenColor_,
        lineWidth_
    );

    return res;
}

std::string FeedbackDescriptionLinksGenerator::localUserLink() const
{
    return fullTrackWithLostPointLink(
        concatPointsVectors(pointsBeforeLost_, pointsTrackAfterLost_),
        pointsBeforeLost_.back(),
        greenColor_
    );
}

std::string FeedbackDescriptionLinksGenerator::localNaviLink() const
{
    return fullTrackWithLostPointLink(
        concatPointsVectors(pointsBeforeLost_, pointsRouteAfterLost_),
        pointsBeforeLost_.back(),
        blueColor_
    );
}

std::string FeedbackDescriptionLinksGenerator::globalNaviLink() const
{
    const auto& lostPoint = pointsBeforeLost_.back();

    std::string res = "https://yandex.ru/maps/?mode=routes";

    res += "&rtext=";

    res += pointToString({routeStartPos_.y(), routeStartPos_.x()}) + "~";
    res += pointToString({lostPoint.y(), lostPoint.x()}) + "~";
    res += pointToString({routeEndPos_.y(), routeEndPos_.x()});

    res += "&rtt=auto";

    return res;
}

std::string FeedbackDescriptionLinksGenerator::staticApiUrlPlusBbox() const
{
    std::string res = staticApiPrefix_;

    res += "&bbox=";
    res += pointToString(bboxOfTracks_.lowerCorner()) + "~";
    res += pointToString(bboxOfTracks_.upperCorner());

    return res;
}

std::string
FeedbackDescriptionLinksGenerator::fullTrackWithLostPointLink(
    const geolib3::PointsVector& fullTrack,
    const geolib3::Point2& lostPoint,
    const std::string& color) const
{
    std::string res = staticApiUrlPlusBbox();

    // draw start, end and lost points
    //
    res += "&pt=";
    res += pointToString(fullTrack.front()) + ",pm2am~";
    res += pointToString(lostPoint) + ",flag~";
    res += pointToString(fullTrack.back()) + ",pm2bm";

    // draw full user track
    //
    res += "&pl=";

    res += asStaticApiPolyline(
        fullTrack,
        geolib3::Orientation::Clockwise,
        color,
        lineWidth_
    );

    return res;
}

} // namespace maps::wiki::route_lost_feedback
