#include <library/cpp/testing/gtest/gtest.h>
#include <maps/libs/geolib/include/units_literals.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_lane_hypothesis/include/generator.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/location/include/rotation.h>
#include <maps/wikimap/mapspro/services/mrc/libs/object/include/mock_loader.h>
#include <maps/libs/geolib/include/test_tools/comparison.h>

using namespace maps::geolib3::literals;

namespace maps::mrc::eye {

namespace tests {

TEST(test_generate_lane_hypothesis, filter_test)
{
    db::eye::Objects objects{
        {
            db::TId(0u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::RoadMarkingLaneDirectionL, false}
        },
        {
            db::TId(0u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::RoadMarkingLaneDirectionL, true}
        }
    };

    objects = filterObjects<LaneHypothesisGeneratorImpl>(std::move(objects));

    EXPECT_EQ(objects.size(), 1u);
}

TEST(test_generate_lane_hypothesis, empty)
{
    object::MockLoader loader;
    {   // lane road marking
        const db::eye::Object object(
            db::TId(0u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::RoadMarkingLaneDirectionL, false}
        );

        const db::eye::ObjectLocation location(
            db::TId(0u),
            geolib3::Point2(0, 0),
            toRotation(geolib3::Heading(180.0), common::ImageOrientation(common::Rotation::CW_0))
        );

        const db::eye::Objects slaveObjects;

        const auto hypotheses = LaneHypothesisGeneratorImpl::validate(object, location, slaveObjects, loader);

        EXPECT_TRUE(hypotheses.empty());
    }

    {   // lane sign
        const db::eye::Object object(
            db::TId(0u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::PrescriptionLaneDirectionF, false}
        );

        const db::eye::ObjectLocation location(
            db::TId(0u),
            geolib3::Point2(0, 0),
            toRotation(geolib3::Heading(180.0), common::ImageOrientation(common::Rotation::CW_0))
        );

        const db::eye::Objects slaveObjects;

        const auto hypotheses = LaneHypothesisGeneratorImpl::validate(object, location, slaveObjects, loader);

        EXPECT_TRUE(hypotheses.empty());
    }
}

TEST(test_generate_lane_hypothesis, right_turn_is_absent)
{
    /*
        (101)<ooooo>(102)<ooooo>(103)
              [201]   ^   [202]
                      o
                      o
                      o   [203]
                      o
                      o
                      v
                    (104)
    */
    object::MockLoader loader;

    loader.add(object::RoadJunctions{
        object::RoadJunction(
            object::RevisionID{101, 1}, geolib3::Point2{-10, 0})
            .elementIds({201}),
        object::RoadJunction(
            object::RevisionID{102, 1}, geolib3::Point2{0, 0})
            .elementIds({201, 202, 203}),
        object::RoadJunction(
            object::RevisionID{103, 1}, geolib3::Point2{10, 0})
            .elementIds({202}),
        object::RoadJunction(
            object::RevisionID{104, 1}, geolib3::Point2{0, -10})
            .elementIds({203})});

    loader.add(object::RoadElements{
        object::RoadElement(object::RevisionID{201, 1},
            geolib3::Polyline2{geolib3::PointsVector{{-10, 0}, {0, 0}}})
            .fc(object::RoadElement::FunctionalClass::MajorLocalRoad)
            .startZLevel(0)
            .startJunctionId(101)
            .endJunctionId(102)
            .direction(object::RoadElement::Direction::Both)
            .accessId(object::RoadElement::AccessId::Car),
        object::RoadElement(object::RevisionID{202, 1},
            geolib3::Polyline2{geolib3::PointsVector{{0, 0}, {10, 0}}})
            .fc(object::RoadElement::FunctionalClass::MajorLocalRoad)
            .startJunctionId(102)
            .endJunctionId(103)
            .direction(object::RoadElement::Direction::Both)
            .accessId(object::RoadElement::AccessId::Car),
        object::RoadElement(object::RevisionID{203, 1},
            geolib3::Polyline2{geolib3::PointsVector{{0, 0}, {0, -10}}})
            .fc(object::RoadElement::FunctionalClass::MajorLocalRoad)
            .startJunctionId(102)
            .endJunctionId(104)
            .direction(object::RoadElement::Direction::Both)
            .accessId(object::RoadElement::AccessId::Car)
            .fLanes({object::RoadElement::Lane{"L90:auto"}})});

    {
        const db::eye::Object object(
            db::TId(0u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::RoadMarkingLaneDirectionL, false}
        );

        const db::eye::ObjectLocation location(
            db::TId(0u),
            geolib3::Point2(-1, -1),
            toRotation(geolib3::Heading(180.0), common::ImageOrientation(common::Rotation::CW_0))
        );

        const db::eye::Objects slaveObjects;

        const auto hypotheses = LaneHypothesisGeneratorImpl::validate(object, location, slaveObjects, loader);

        EXPECT_TRUE(hypotheses.empty());
    }

    {
        const db::eye::Object object(
            db::TId(0u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::RoadMarkingLaneDirectionR, false}
        );

        const db::eye::ObjectLocation location(
            db::TId(0u),
            geolib3::Point2(-1, -1),
            toRotation(geolib3::Heading(180.0), common::ImageOrientation(common::Rotation::CW_0))
        );

        const db::eye::Objects slaveObjects;

        const auto hypotheses = LaneHypothesisGeneratorImpl::validate(object, location, slaveObjects, loader);

        ASSERT_EQ(hypotheses.size(), 1u);
        const auto& hypothesis = hypotheses[0];
        EXPECT_EQ(hypothesis.type(), db::eye::HypothesisType::LaneHypothesis);
        EXPECT_EQ(hypothesis.attrs<db::eye::LaneHypothesisAttrs>().roadElementRevisionId.objectId(), 203u);
        EXPECT_EQ(hypothesis.attrs<db::eye::LaneHypothesisAttrs>().roadElementRevisionId.commitId(), 1u);
    }

    {
        const db::eye::Object object(
            db::TId(0u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::PrescriptionLaneDirectionL, false}
        );

        const db::eye::ObjectLocation location(
            db::TId(0u),
            geolib3::Point2(-1, -1),
            toRotation(geolib3::Heading(180.0), common::ImageOrientation(common::Rotation::CW_0))
        );

        const db::eye::Objects slaveObjects;

        const auto hypotheses = LaneHypothesisGeneratorImpl::validate(object, location, slaveObjects, loader);

        EXPECT_TRUE(hypotheses.empty());
    }

    {
        const db::eye::Object object(
            db::TId(0u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::RoadMarkingLaneDirectionR, false}
        );

        const db::eye::ObjectLocation location(
            db::TId(0u),
            geolib3::Point2(-1, -1),
            toRotation(geolib3::Heading(180.0), common::ImageOrientation(common::Rotation::CW_0))
        );

        const db::eye::Objects slaveObjects;

        const auto hypotheses = LaneHypothesisGeneratorImpl::validate(object, location, slaveObjects, loader);

        ASSERT_EQ(hypotheses.size(), 1u);
        const auto& hypothesis = hypotheses[0];
        EXPECT_EQ(hypothesis.type(), db::eye::HypothesisType::LaneHypothesis);
        EXPECT_EQ(hypothesis.attrs<db::eye::LaneHypothesisAttrs>().roadElementRevisionId.objectId(), 203u);
        EXPECT_EQ(hypothesis.attrs<db::eye::LaneHypothesisAttrs>().roadElementRevisionId.commitId(), 1u);
    }
}

TEST(test_generate_lane_hypothesis, turnabout)
{
    /*
        (101)<ooooo>(102)<ooooo>(103)
              [201]   ^   [202]
                      o
                      o
                      o   [203]
                      o
                      o
                      o
                    (104)
    */
    object::MockLoader loader;

    loader.add(
        object::RoadJunctions{
            object::RoadJunction{
                object::RevisionID{101, 1},
                geolib3::Point2{-10, 0}
            }
            .elementIds({201}),
            object::RoadJunction{
                object::RevisionID{102, 1},
                geolib3::Point2{0, 0}
            }.elementIds({201, 202, 203}),
            object::RoadJunction{
                object::RevisionID{103, 1},
                geolib3::Point2{10, 0}
            }.elementIds({202}),
            object::RoadJunction{
                object::RevisionID{104, 1},
                geolib3::Point2{0, -10}
            }
            .elementIds({203})
        }
    );

    loader.add(
        object::RoadElements{
            object::RoadElement{
                object::RevisionID{201, 1},
                geolib3::Polyline2{geolib3::PointsVector{{-10, 0}, {0, 0}}}
            }
            .fc(object::RoadElement::FunctionalClass::MajorLocalRoad)
            .startZLevel(0)
            .startJunctionId(101)
            .endJunctionId(102)
            .direction(object::RoadElement::Direction::Both)
            .accessId(object::RoadElement::AccessId::Car),
            object::RoadElement{
                object::RevisionID{202, 1},
                geolib3::Polyline2{geolib3::PointsVector{{0, 0}, {10, 0}}}
            }
            .fc(object::RoadElement::FunctionalClass::MajorLocalRoad)
            .startJunctionId(102)
            .endJunctionId(103)
            .direction(object::RoadElement::Direction::Backward)
            .accessId(object::RoadElement::AccessId::Car)
            .fLanes({object::RoadElement::Lane{"L90,L180:auto"}}),
            object::RoadElement{
                object::RevisionID{203, 1},
                geolib3::Polyline2{geolib3::PointsVector{{0, 0}, {0, -10}}}
            }
            .fc(object::RoadElement::FunctionalClass::MajorLocalRoad)
            .startJunctionId(102)
            .endJunctionId(104)
            .fow(object::RoadElement::FormOfWay::TwoWayRoad)
            .direction(object::RoadElement::Direction::Backward)
            .accessId(object::RoadElement::AccessId::Car)
            .fLanes({object::RoadElement::Lane{"L90,L180:auto"}})
        }
    );

    {
        const db::eye::Object object(
            db::TId(0u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::PrescriptionLaneDirectionL, false}
        );

        const db::eye::ObjectLocation location(
            db::TId(0u),
            geolib3::Point2(-1, -1),
            toRotation(geolib3::Heading(180.0), common::ImageOrientation(common::Rotation::CW_0))
        );

        const db::eye::Objects slaveObjects;

        const auto hypotheses = LaneHypothesisGeneratorImpl::validate(object, location, slaveObjects, loader);

        EXPECT_TRUE(hypotheses.empty());
    }

    {
        const db::eye::Object object(
            db::TId(0u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::PrescriptionLaneDirectionL, false}
        );

        const db::eye::ObjectLocation location(
            db::TId(0u),
            geolib3::Point2(1, 1),
            toRotation(geolib3::Heading(90.0), common::ImageOrientation(common::Rotation::CW_0))
        );

        const db::eye::Objects slaveObjects;

        const auto hypotheses = LaneHypothesisGeneratorImpl::validate(object, location, slaveObjects, loader);

        ASSERT_EQ(hypotheses.size(), 1u);
        const auto& hypothesis = hypotheses[0];
        EXPECT_EQ(hypothesis.type(), db::eye::HypothesisType::LaneHypothesis);
        EXPECT_EQ(hypothesis.attrs<db::eye::LaneHypothesisAttrs>().roadElementRevisionId.objectId(), 202u);
        EXPECT_EQ(hypothesis.attrs<db::eye::LaneHypothesisAttrs>().roadElementRevisionId.commitId(), 1u);
    }
}

} //namespace tests

} //namespace maps::mrc::eye
