#include <maps/wikimap/mapspro/services/mrc/libs/camera/include/camera.h>
#include <maps/libs/common/include/exception.h>

#include <maps/wikimap/mapspro/services/mrc/libs/birdview/include/vanishing_point.h>

#include <library/cpp/testing/unittest/env.h>
#include <library/cpp/testing/gmock_in_unittest/gmock.h>
#include <library/cpp/testing/unittest/registar.h>

using namespace testing;

namespace maps::mrc {

namespace tests {

namespace {

const std::vector<std::string> IMAGE_NAMES{
    "10161525.jpg",
    "10160871.jpg"
};

cv::Mat loadImage(const std::string& name) {
    static const std::string IMAGES_DIR
        = "maps/wikimap/mapspro/services/mrc/libs/camera/tests/images/";
    auto imagePath = static_cast<std::string>(BinaryPath(IMAGES_DIR + name));
    cv::Mat image = cv::imread(imagePath, CV_LOAD_IMAGE_COLOR);
    REQUIRE(image.data != nullptr, "Can't load image " << name);
    return image;
}

} // namespace

TEST(camera_tests, throw_expection_while_estimate_angle_without_calibration_test)
{
    cv::Mat image(100, 100, CV_8UC3, cv::Scalar::all(0));

    Camera camera;

    EXPECT_THROW(camera.estimateAngle(image), maps::Exception);
}

TEST(camera_tests, check_angle_for_initial_camera_test)
{
    Camera camera;

    EXPECT_DOUBLE_EQ(camera.angle().value(), 0.);
    EXPECT_FALSE(camera.vanishingPoint().has_value());
}

TEST(camera_tests, vanishing_point_estimation_for_one_image_test)
{
    const std::string model = "SM-A320F";
    const common::ImageOrientation orientation(common::Rotation::CW_0);

    for (const std::string& imageName : IMAGE_NAMES) {
        cv::Mat image = loadImage(imageName);

        std::optional<cv::Point> vanishingPoint
            = birdview::findVanishingPoint(image);

        EXPECT_TRUE(vanishingPoint.has_value());

        Camera camera;
        camera.resetParameters(
            getCameraParameters(model, orientation, image.size()),
            image.size()
        );

        camera.estimateAngle(image);

        EXPECT_TRUE(camera.vanishingPoint().has_value());
        EXPECT_EQ(vanishingPoint.value(), camera.vanishingPoint().value());
    }
}

TEST(camera_tests, reset_parameters_reset_vanishing_point_test)
{
    const std::string model1 = "SM-A320F";
    const std::string model2 = "SM-A720F";
    const common::ImageOrientation orientation(common::Rotation::CW_0);

    Camera camera;
    EXPECT_FALSE(camera.vanishingPoint().has_value());
    EXPECT_DOUBLE_EQ(camera.angle().value(), 0.);

    cv::Mat image = loadImage(IMAGE_NAMES.front());

    camera.resetParameters(
        getCameraParameters(model1, orientation, image.size()),
        image.size()
    );

    camera.estimateAngle(image);

    EXPECT_TRUE(camera.vanishingPoint().has_value());

    camera.resetParameters(
        getCameraParameters(model2, orientation, image.size()),
        image.size()
    );

    EXPECT_FALSE(camera.vanishingPoint().has_value());
    EXPECT_DOUBLE_EQ(camera.angle().value(), 0.);
}

TEST(camera_tests, throw_if_images_have_different_size_test)
{
    const std::string model = "model";
    const common::ImageOrientation orientation(common::Rotation::CW_0);

    cv::Mat image1(100, 100, CV_8UC3);
    cv::Mat image2(101, 101, CV_8UC3);

    Camera camera;
    camera.resetParameters(
        getCameraParameters(model, orientation, image1.size()),
        image1.size()
    );

    camera.estimateAngle(image1);

    EXPECT_THROW(camera.estimateAngle(image2), maps::Exception);
}

TEST(camera_tests, return_true_if_camera_is_recalibrated_test)
{
    const std::string sourceId = "sourceId1";
    const std::string model = "model1";
    const common::ImageOrientation orientation(common::Rotation::CW_0);
    const db::CameraDeviation deviation(db::CameraDeviation::Front);
    const cv::Size size(1920, 1080);

    Camera camera;

    EXPECT_TRUE(camera.calibrate(sourceId, model, orientation, deviation, size));

    const cv::Size newSize(800, 720);

    EXPECT_TRUE(camera.calibrate(sourceId, model, orientation, deviation, newSize));
}

TEST(camera_tests, change_size_after_camera_calibration_test)
{
    const std::string sourceId = "sourceId1";
    const std::string model = "model1";
    const common::ImageOrientation orientation(common::Rotation::CW_0);
    const db::CameraDeviation deviation(db::CameraDeviation::Front);
    const cv::Size size(1920, 1080);

    cv::Mat image(size, CV_8UC3, cv::Scalar::all(0));

    Camera camera;
    camera.calibrate(sourceId, model, orientation, deviation, size);

    camera.estimateAngle(image);

    const cv::Size newSize(800, 720);
    camera.calibrate(sourceId, model, orientation, deviation, newSize);

    EXPECT_THROW(camera.estimateAngle(image), maps::Exception);
}

} // namespace test

} // namespace maps::mrc
