#include <library/cpp/testing/gtest/gtest.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_wrong_speed_limit/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>

namespace maps::mrc::eye {

namespace tests {

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

    objects = filterObjects<SpeedLimitGeneratorImpl>(objects);

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

TEST(test_generate_wrong_speed_limit, test_sign_without_road_elements)
{
    object::MockLoader loader;

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

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

    const db::eye::Objects slaveObjects;

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

    ASSERT_TRUE(hypotheses.empty());
}

TEST(test_generate_wrong_speed_limit, test_generating_a_hypothesis)
{
    object::MockLoader loader;
    loader.add(
        object::RoadElements{
            object::RoadElement(
                object::RevisionID{1, 1},
                geolib3::Polyline2{
                    geolib3::PointsVector{{0, 0},{0, -100}}
                }
            )
                .speedLimit(50)
                .direction(object::RoadElement::Direction::Forward)
                .accessId(object::RoadElement::AccessId::Car | object::RoadElement::AccessId::Pedestrian)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
        }
    );

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

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

    const db::eye::Objects slaveObjects;

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

    ASSERT_EQ(hypotheses.size(), 1u);
    EXPECT_EQ(hypotheses[0].attrs<db::eye::WrongSpeedLimitAttrs>().roadElementRevisionId.objectId(), 1u);
    EXPECT_EQ(hypotheses[0].attrs<db::eye::WrongSpeedLimitAttrs>().roadElementRevisionId.commitId(), 1u);
    EXPECT_EQ(hypotheses[0].attrs<db::eye::WrongSpeedLimitAttrs>().direction.value(), object::RoadElement::Direction::Forward);
    EXPECT_EQ(hypotheses[0].attrs<db::eye::WrongSpeedLimitAttrs>().currentSpeedLimit.value(), 50);
    EXPECT_EQ(hypotheses[0].attrs<db::eye::WrongSpeedLimitAttrs>().correctSpeedLimit, 60);
    EXPECT_TRUE(
        geolib3::test_tools::approximateEqual(
            hypotheses[0].mercatorPos(),
            location.mercatorPos(),
            geolib3::EPS
        )
    );
}


TEST(test_generate_wrong_speed_limit, test_access_id_check)
{
    object::MockLoader loader;
    loader.add(
        object::RoadElements{
            object::RoadElement(
                object::RevisionID{1, 1},
                geolib3::Polyline2{
                    geolib3::PointsVector{{0, 0}, {0, -100}}
                }
            )
                .speedLimit(50)
                .direction(object::RoadElement::Direction::Forward)
                .accessId(object::RoadElement::AccessId::Bicycle | object::RoadElement::AccessId::Pedestrian)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
        }
    );

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

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

    const db::eye::Objects slaveObjects;

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

    ASSERT_TRUE(hypotheses.empty());
}

TEST(test_generate_wrong_speed_limit, test_codirectional_check)
{
    object::MockLoader loader;
    loader.add(
        object::RoadElements{
            object::RoadElement(
                object::RevisionID{1, 1},
                geolib3::Polyline2{
                    geolib3::PointsVector{{0, 0},{100, 0}}
                }
            )
                .speedLimit(50)
                .direction(object::RoadElement::Direction::Both)
                .accessId(object::RoadElement::AccessId::Car)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
        }
    );

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

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

    const db::eye::Objects slaveObjects;

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

    ASSERT_TRUE(hypotheses.empty());
}

TEST(test_generate_wrong_speed_limit, test_direction_check_no_hyp)
{
    object::MockLoader loader;
    loader.add(
        object::RoadElements{
            object::RoadElement(
                object::RevisionID{1, 1},
                geolib3::Polyline2{
                    geolib3::PointsVector{{0, 0},{0, 100}}
                }
            )
                .speedLimit(50)
                .speedLimitF(50)
                .speedLimitT(object::RoadElement::NO_SPEED_LIMIT)
                .direction(object::RoadElement::Direction::Forward)
                .accessId(object::RoadElement::AccessId::Car)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
        }
    );

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

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

    const db::eye::Objects slaveObjects;

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

    ASSERT_TRUE(hypotheses.empty());
}

TEST(test_generate_wrong_speed_limit, test_direction_check_hyp)
{
    object::MockLoader loader;
    loader.add(
        object::RoadElements{
            object::RoadElement(
                object::RevisionID{1, 1},
                geolib3::Polyline2{
                    geolib3::PointsVector{{0, 0},{0, 100}}
                }
            )
                .speedLimit(50)
                .speedLimitF(object::RoadElement::NO_SPEED_LIMIT)
                .speedLimitT(50)
                .direction(object::RoadElement::Direction::Backward)
                .accessId(object::RoadElement::AccessId::Car)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
        }
    );

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

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

    const db::eye::Objects slaveObjects;

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

    ASSERT_TRUE(1 == hypotheses.size());
}

TEST(test_generate_wrong_speed_limit, test_truck)
{
    object::MockLoader loader;
    loader.add(
        object::RoadElements{
            object::RoadElement(
                object::RevisionID{1, 1},
                geolib3::Polyline2{
                    geolib3::PointsVector{{0, 0},{0, 100}}
                }
            )
                .speedLimit(60)
                .speedLimitF(object::RoadElement::NO_SPEED_LIMIT)
                .speedLimitT(60)
                .speedLimitTruckF(object::RoadElement::NO_SPEED_LIMIT)
                .speedLimitTruckT(60)
                .direction(object::RoadElement::Direction::Backward)
                .accessId(object::RoadElement::AccessId::Vehicles)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
        }
    );

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

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

    const db::eye::Objects slaveObjects{
        {
            db::TId(1u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::InformationHeavyVehicle, false}
        },
    };
    const auto hypotheses = SpeedLimitGeneratorImpl::validate(object, location, slaveObjects, loader);

    ASSERT_EQ(hypotheses.size(), 1u);
    EXPECT_EQ(hypotheses[0].attrs<db::eye::WrongSpeedLimitAttrs>().correctSpeedLimit, 40);
    EXPECT_EQ(hypotheses[0].attrs<db::eye::WrongSpeedLimitAttrs>().truck.value(), true);
}

TEST(test_generate_wrong_speed_limit, test_heuristic_good_element_too_close)
{
    object::MockLoader loader;
    loader.add(
        object::RoadElements{
            //first element, with incorrect speed limit
            object::RoadElement(
                object::RevisionID{1, 1},
                geolib3::Polyline2{
                    geolib3::PointsVector{{0, 0}, {0, -100}}
                }
            )
                .speedLimit(50)
                .direction(object::RoadElement::Direction::Forward)
                .accessId(object::RoadElement::AccessId::Car)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
            //second element, with correct speed limit, lying within magical radius
            object::RoadElement(
                object::RevisionID{2, 2},
                geolib3::Polyline2{
                    geolib3::PointsVector{{10, 0},{10, -100}}
                }
            )
                .speedLimit(60)
                .direction(object::RoadElement::Direction::Forward)
                .accessId(object::RoadElement::AccessId::Car)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
        }
    );

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

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

    const db::eye::Objects slaveObjects;

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

    ASSERT_TRUE(hypotheses.empty());
}

TEST(test_generate_wrong_speed_limit, test_heuristic_good_element_too_far)
{
    object::MockLoader loader;
    loader.add(
        object::RoadElements{
            //first element, with incorrect speed limit
            object::RoadElement(
                object::RevisionID{1, 1},
                geolib3::Polyline2{
                    geolib3::PointsVector{{0, 0},{0, -100}}
                }
            )
                .speedLimit(50)
                .direction(object::RoadElement::Direction::Forward)
                .accessId(object::RoadElement::AccessId::Car)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
            //second element, with correct speed limit, lying outside the magical radius
            object::RoadElement(
                object::RevisionID{2, 2},
                geolib3::Polyline2{
                    geolib3::PointsVector{{30, 0},{30, -100}}
                }
            )
                .speedLimit(50)
                .direction(object::RoadElement::Direction::Forward)
                .accessId(object::RoadElement::AccessId::Car)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
        }
    );

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

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

    const db::eye::Objects slaveObjects;

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

    ASSERT_EQ(hypotheses.size(), 1u);
    EXPECT_EQ(hypotheses[0].attrs<db::eye::WrongSpeedLimitAttrs>().roadElementRevisionId.objectId(), 1u);
    EXPECT_EQ(hypotheses[0].attrs<db::eye::WrongSpeedLimitAttrs>().roadElementRevisionId.commitId(), 1u);
    EXPECT_EQ(hypotheses[0].attrs<db::eye::WrongSpeedLimitAttrs>().currentSpeedLimit.value(), 50);
    EXPECT_EQ(hypotheses[0].attrs<db::eye::WrongSpeedLimitAttrs>().correctSpeedLimit, 60);
    EXPECT_TRUE(
        geolib3::test_tools::approximateEqual(
            hypotheses[0].mercatorPos(),
            location.mercatorPos(),
            geolib3::EPS
        )
    );

}

TEST(test_generate_wrong_speed_limit, test_heuristic_good_element_too_far_with_trucks_table)
{
    object::MockLoader loader;
    loader.add(
        object::RoadElements{
            //first element, with incorrect speed limit
            object::RoadElement(
                object::RevisionID{1, 1},
                geolib3::Polyline2{
                    geolib3::PointsVector{{0, 0},{0, -100}}
                }
            )
                .speedLimit(50)
                .direction(object::RoadElement::Direction::Forward)
                .accessId(object::RoadElement::AccessId::Car)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
            //second element, with correct speed limit, lying outside the magical radius
            object::RoadElement(
                object::RevisionID{2, 2},
                geolib3::Polyline2{
                    geolib3::PointsVector{{30, 0},{30, -100}}
                }
            )
                .speedLimit(50)
                .direction(object::RoadElement::Direction::Forward)
                .accessId(object::RoadElement::AccessId::Car)
                .fc(object::RoadElement::FunctionalClass::MainRoad),
        }
    );

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

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

    const db::eye::Objects slaveObjects{
        {
            db::TId(1u),
            db::eye::SignAttrs{traffic_signs::TrafficSign::InformationHeavyVehicle, false}
        },
    };

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

    ASSERT_EQ(hypotheses.size(), 0u);
}

} //namespace tests

} //namespace maps::mrc::eye
