#include "../lib/column_names.h"
#include "../lib/common.h"
#include "../lib/lost_mapper.h"
#include "../lib/lost_reducer.h"

#include <library/cpp/testing/unittest/registar.h>
#include <mapreduce/yt/interface/client.h>
#include <maps/libs/geolib/include/polygon.h>
#include <maps/wikimap/mapspro/libs/yt_stubs/include/execute_operation.h>
#include <util/generic/xrange.h>

namespace mjs = maps::jams::static_graph2;
namespace mwy = maps::wiki::yt_stubs;

namespace maps::wiki::route_lost_feedback::tests {

Y_UNIT_TEST_SUITE(suite) {

Y_UNIT_TEST(lost_reducer)
{
    std::vector<mjs::PersistentSegmentId> inputSegments {
        {mjs::LongId(2), 1},
        {mjs::LongId(2), 2},
        {mjs::LongId(2), 2},
        {mjs::LongId(1), 0},
    };

    std::vector<NYT::TNode> inputRows;
    for (const auto& segment : inputSegments) {
        NYT::TNode row;
        row(column_names::AFTER_AFTER_LOST_TRACK_SEGMENT, toTNode(segment));
        inputRows.emplace_back(row);
    }

    LostReducer reducer;
    auto reducedRows = mwy::executeOperation(reducer, inputRows);

    UNIT_ASSERT_EQUAL(reducedRows.size(), 1);
    const auto& resRow = reducedRows.front();

    auto resSegment = asPersistentSegment(
        resRow[column_names::AFTER_AFTER_LOST_TRACK_SEGMENT]
    );
    UNIT_ASSERT_EQUAL(resSegment.edgeId().value(), 2);
    UNIT_ASSERT_EQUAL(resSegment.segmentIndex(), 2);

    UNIT_ASSERT_EQUAL(resRow[column_names::LOSTS_NUMBER].AsUint64(), 4);
}

Y_UNIT_TEST(lost_mapper)
{
    /*
     *
     *
     *     route - *               * -  track
     * (31 points   \             /  (9 points after loss)
     *  after loss)  .           .
     *                .         .
     *                 .       .
     *                  \     /
     *                   *   *
     *                    \ /
     *                     *  - lost point
     *                     |
     *                     *  - before lost point
     *
     * Route will be cut due to max points limitation.
     * Track will be cut due to max distance to loss point limitation.
     * Common path before won't be cut.
     */

    mjs::PersistentSegmentId beforeLostSegment(mjs::LongId(1), 2);
    double lostPointX = 60., lostPointY = 50.;
    geolib3::Point2 beforeLostPoint(lostPointX, lostPointY - 0.001);
    geolib3::Point2 lostPoint(lostPointX, lostPointY);

    // Construct route
    //
    std::vector<geolib3::Point2> routePoints;
    routePoints.push_back(beforeLostPoint);
    double routeStep = 0.00001;
    size_t routeSizeAfterLoss = 31;
    for (size_t i : xrange(0ul, routeSizeAfterLoss)) {
        routePoints.push_back({
            lostPointX - i * routeStep,
            lostPointY + i * routeStep
        });
    }
    auto routePointBeforeLast = *std::next(routePoints.rbegin());

    std::vector<mjs::PersistentSegmentId> routeSegments;
    routeSegments.push_back(beforeLostSegment);
    for (size_t i : xrange(0ul + 10, routeSizeAfterLoss + 10)) {
        routeSegments.push_back(mjs::PersistentSegmentId(mjs::LongId(i), 0));
    }

    // Construct track
    //
    std::vector<geolib3::Point2> trackPoints;
    trackPoints.push_back(beforeLostPoint);
    double trackStep = 0.001;
    size_t trackSizeAfterLoss = 9;
    for (size_t i : xrange(0ul, trackSizeAfterLoss)) {
        trackPoints.push_back({
            lostPointX + i * trackStep,
            lostPointY + i * trackStep
        });
    }
    auto trackPointBeforeLast = *std::next(trackPoints.rbegin());

    std::vector<mjs::PersistentSegmentId> trackSegments;
    trackSegments.push_back(beforeLostSegment);
    for (size_t i : xrange(0ul + 100, trackSizeAfterLoss + 100)) {
        trackSegments.push_back(mjs::PersistentSegmentId(mjs::LongId(i), 0));
    }

    // Construct Input Row
    //
    NYT::TNode row;

    NYT::TNode positionsRow;
    positionsRow(column_names::ROUTE, 0);
    positionsRow(column_names::TRACK, 0);

    row(column_names::ROUTE_LOST_POSITION, positionsRow);

    auto createListNode = [](const auto& objectsVec) {
        auto node = NYT::TNode::CreateList();
        for (const auto& obj : objectsVec) {
            node.Add(toTNode(obj));
        }
        return node;
    };

    row(column_names::ROUTE_PERSISTENT_SEGMENTS, createListNode(routeSegments));
    row(column_names::TRACK_PERSISTENT_SEGMENTS, createListNode(trackSegments));

    row(column_names::ROUTE_GEOMETRY, createListNode(routePoints));
    row(column_names::TRACK_GEOMETRY, createListNode(trackPoints));

    row(column_names::ROUTE_LOST_TYPE, "true");
    row(column_names::TYPE, "route");
    row(column_names::ROUTE_ID, "4edff11a-2bc7-4ba5-b72c-edad6a657561");

    // Run operation
    //
    geolib3::Polygon2 polygon(
        geolib3::PointsVector({
            {lostPointX - 1, lostPointY - 1},
            {lostPointX - 1, lostPointY + 1},
            {lostPointX + 1, lostPointY + 1},
            {lostPointX + 1, lostPointY - 1}
        })
    );

    LostMapper mapper(polygon);

    auto resVec = mwy::executeOperation(
        mapper,
        std::vector<NYT::TNode>({row})
    );

    // Check result
    //
    UNIT_ASSERT_EQUAL(resVec.size(), 1);
    const auto& res = resVec.front();

    UNIT_ASSERT_STRINGS_EQUAL(
        res[column_names::KEY].AsString(),
        "(1, 2),(10, 0),(100, 0)"
    );

    UNIT_ASSERT_EQUAL(res[column_names::PERSISTENT_ID].AsUint64(), 1);
    UNIT_ASSERT_EQUAL(res[column_names::SEGMENT_INDEX].AsUint64(), 2);

    UNIT_ASSERT_EQUAL(asPoint(res[column_names::LOST_POINT]), lostPoint);

    UNIT_ASSERT_EQUAL(
        asPersistentSegment(res[column_names::BEFORE_LOST_SEGMENT]),
        mjs::PersistentSegmentId(mjs::LongId(1), 2)
    );

    UNIT_ASSERT_EQUAL(
        asPersistentSegment(res[column_names::AFTER_LOST_ROUTE_SEGMENT]),
        mjs::PersistentSegmentId(mjs::LongId(10), 0)
    );

    UNIT_ASSERT_EQUAL(
        asPersistentSegment(res[column_names::AFTER_LOST_TRACK_SEGMENT]),
        mjs::PersistentSegmentId(mjs::LongId(100), 0)
    );

    UNIT_ASSERT_EQUAL(
        asPersistentSegment(res[column_names::AFTER_AFTER_LOST_TRACK_SEGMENT]),
        mjs::PersistentSegmentId(mjs::LongId(101), 0)
    );

    // Check result segments paths
    //
    auto createPositionsFromNodes = [](const NYT::TNode::TListType& nodes) {
        std::vector<geolib3::Point2> positions;
        for (const auto& node : nodes) {
            positions.emplace_back(asPoint(node));
        }
        return positions;
    };

    auto beforeLostPositions =
        createPositionsFromNodes(res[column_names::POINTS_BEFORE_LOST].AsList());

    UNIT_ASSERT_EQUAL(beforeLostPositions.size(), 2);
    UNIT_ASSERT_EQUAL(beforeLostPositions.front(), lostPoint);
    UNIT_ASSERT_EQUAL(beforeLostPositions.back(), beforeLostPoint);

    auto afterLostRoutePositions = createPositionsFromNodes(
        res[column_names::POINTS_ROUTE_AFTER_LOST].AsList()
    );

    UNIT_ASSERT_EQUAL(afterLostRoutePositions.size(), routeSizeAfterLoss - 1);
    UNIT_ASSERT_EQUAL(afterLostRoutePositions.front(), lostPoint);
    UNIT_ASSERT_EQUAL(afterLostRoutePositions.back(), routePointBeforeLast);

    auto afterLostTrackPositions = createPositionsFromNodes(
        res[column_names::POINTS_TRACK_AFTER_LOST].AsList()
    );

    UNIT_ASSERT_EQUAL(afterLostTrackPositions.size(), trackSizeAfterLoss - 1);
    UNIT_ASSERT_EQUAL(afterLostTrackPositions.front(), lostPoint);
    UNIT_ASSERT_EQUAL(afterLostTrackPositions.back(), trackPointBeforeLast);
}

}

} // namespace maps::wiki::route_lost_feedback::tests
