#include <library/cpp/testing/common/env.h>
#include <library/cpp/testing/gtest/gtest.h>
#include <maps/libs/common/include/file_utils.h>
#include <maps/libs/img/include/algorithm.h>
#include <maps/libs/img/include/raster.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/algorithm/for_each_batch.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/exif.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/opencv.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/suncalc.h>
#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>
#include <yandex/maps/mrc/unittest/database_fixture.h>
#include <yandex/maps/mrc/unittest/utils.h>

#include <opencv2/opencv.hpp>

namespace maps::mrc::common::tests {

TEST(exif, exif_attributes)
{
    for (int exifOrientation = 1; exifOrientation <= 8; ++exifOrientation) {
        const std::string imagePath = SRC_(
            "images/exif_orientation_" + std::to_string(exifOrientation) + ".jpg"
        );

        auto imageData = maps::common::readFileToVector(imagePath);
        auto orientation = parseImageOrientationFromExif(imageData);

        EXPECT_TRUE(orientation);

        auto model = parseModelFromExif(imageData);

        ASSERT_TRUE(model);
        EXPECT_EQ(*model, "Model");
    }
}

TEST(exif, exif_rotation_operators)
{
    EXPECT_EQ(Rotation::CW_0 + Rotation::CW_0, Rotation::CW_0);
    EXPECT_EQ(Rotation::CW_0 + Rotation::CW_90, Rotation::CW_90);
    EXPECT_EQ(Rotation::CW_0 + Rotation::CW_180, Rotation::CW_180);
    EXPECT_EQ(Rotation::CW_0 + Rotation::CW_270, Rotation::CW_270);

    EXPECT_EQ(Rotation::CW_90 + Rotation::CW_0, Rotation::CW_90);
    EXPECT_EQ(Rotation::CW_90 + Rotation::CW_90, Rotation::CW_180);
    EXPECT_EQ(Rotation::CW_90 + Rotation::CW_180, Rotation::CW_270);
    EXPECT_EQ(Rotation::CW_90 + Rotation::CW_270, Rotation::CW_0);

    EXPECT_EQ(Rotation::CW_180 + Rotation::CW_0, Rotation::CW_180);
    EXPECT_EQ(Rotation::CW_180 + Rotation::CW_90, Rotation::CW_270);
    EXPECT_EQ(Rotation::CW_180 + Rotation::CW_180, Rotation::CW_0);
    EXPECT_EQ(Rotation::CW_180 + Rotation::CW_270, Rotation::CW_90);

    EXPECT_EQ(Rotation::CW_270 + Rotation::CW_0, Rotation::CW_270);
    EXPECT_EQ(Rotation::CW_270 + Rotation::CW_90, Rotation::CW_0);
    EXPECT_EQ(Rotation::CW_270 + Rotation::CW_180, Rotation::CW_90);
    EXPECT_EQ(Rotation::CW_270 + Rotation::CW_270, Rotation::CW_180);
}

TEST(exif, test_forward_image_transform)
{
    for (int exifOrientation = 1; exifOrientation <= 8; ++exifOrientation) {
        const std::string imagePath = SRC_(
            "images/exif_orientation_" + std::to_string(exifOrientation) + ".jpg"
        );

        auto encodedImage = maps::common::readFileToVector(imagePath);
        const auto orientation = parseImageOrientationFromExif(encodedImage);

        EXPECT_TRUE(orientation);

        const cv::Mat image = maps::mrc::common::decodeImage(encodedImage);
        const cv::Mat transImage = transformByImageOrientation(image, *orientation);

        const std::string expectedPath
            = SRC_("images/transformed_exif_orientation_"
                   + std::to_string(exifOrientation) + ".png");
        const cv::Mat refImage = mrc::common::decodeImage(
            maps::common::readFileToVector(expectedPath));

        EXPECT_TRUE(unittest::areImagesEq(refImage, transImage));
    }
}

TEST(exif, bad_blob)
{
    const Blob junk{"bla-bla-bla"};
    EXPECT_FALSE(parseImageOrientationFromExif(junk));
}

TEST(exif, size_forward_transform_by_image_orientation) // upstanding tests
{
    constexpr Size size{16, 12};

    { // 1. Top Left
        constexpr Size expected {16, 12};
        const auto orientation = ImageOrientation::fromExif(1);

        EXPECT_EQ(expected, transformByImageOrientation(size, orientation));
    }

    { // 2. Top Right
        constexpr Size expected {16, 12};
        const auto orientation = ImageOrientation::fromExif(2);

        EXPECT_EQ(expected, transformByImageOrientation(size, orientation));
    }

    { // 3. Bottom Right
        constexpr Size expected {16, 12};
        const auto orientation = ImageOrientation::fromExif(3);

        EXPECT_EQ(expected, transformByImageOrientation(size, orientation));
    }

    { // 4. Bottom Left
        constexpr Size expected {16, 12};
        const auto orientation = ImageOrientation::fromExif(4);

        EXPECT_EQ(expected, transformByImageOrientation(size, orientation));
    }

    { // 5. Left Top
        constexpr Size expected {12, 16};
        const auto orientation = ImageOrientation::fromExif(5);

        EXPECT_EQ(expected, transformByImageOrientation(size, orientation));
    }

    { // 6. Right Top
        constexpr Size expected {12, 16};
        const auto orientation = ImageOrientation::fromExif(6);

        EXPECT_EQ(expected, transformByImageOrientation(size, orientation));
    }

    { // 7. Right Bottom
        constexpr Size expected {12, 16};
        const auto orientation = ImageOrientation::fromExif(7);

        EXPECT_EQ(expected, transformByImageOrientation(size, orientation));
    }

    { // 8. Left Bottom
        constexpr Size expected {12, 16};
        const auto orientation = ImageOrientation::fromExif(8);

        EXPECT_EQ(expected, transformByImageOrientation(size, orientation));
    }
}

TEST(exif, size_revert_by_image_orientation) // upstanding tests
{
    constexpr Size size{16, 12};

    { // 1. Top Left
        constexpr Size expected {16, 12};
        const auto orientation = ImageOrientation::fromExif(1);

        EXPECT_EQ(expected, revertByImageOrientation(size, orientation));
    }

    { // 2. Top Right
        constexpr Size expected {16, 12};
        const auto orientation = ImageOrientation::fromExif(2);

        EXPECT_EQ(expected, revertByImageOrientation(size, orientation));
    }

    { // 3. Bottom Right
        constexpr Size expected {16, 12};
        const auto orientation = ImageOrientation::fromExif(3);

        EXPECT_EQ(expected, revertByImageOrientation(size, orientation));
    }

    { // 4. Bottom Left
        constexpr Size expected {16, 12};
        const auto orientation = ImageOrientation::fromExif(4);

        EXPECT_EQ(expected, revertByImageOrientation(size, orientation));
    }

    { // 5. Left Top
        constexpr Size expected {12, 16};
        const auto orientation = ImageOrientation::fromExif(5);

        EXPECT_EQ(expected, revertByImageOrientation(size, orientation));
    }

    { // 6. Right Top
        constexpr Size expected {12, 16};
        const auto orientation = ImageOrientation::fromExif(6);

        EXPECT_EQ(expected, revertByImageOrientation(size, orientation));
    }

    { // 7. Right Bottom
        constexpr Size expected {12, 16};
        const auto orientation = ImageOrientation::fromExif(7);

        EXPECT_EQ(expected, revertByImageOrientation(size, orientation));
    }

    { // 8. Left Bottom
        constexpr Size expected {12, 16};
        const auto orientation = ImageOrientation::fromExif(8);

        EXPECT_EQ(expected, revertByImageOrientation(size, orientation));
    }
}

TEST(exif, image_box_forward_transform_by_image_orientation) // upstanding tests
{
    constexpr Size size{16, 12};
    constexpr ImageBox box{3, 2, 7, 7};

    { // 1. Top Left
        constexpr ImageBox expected = box;
        const auto orientation = ImageOrientation::fromExif(1);

        EXPECT_EQ(expected, transformByImageOrientation(box, size, orientation));
    }

    { // 2. Top Right
        constexpr ImageBox expected{9, 2, 13, 7};
        const auto orientation = ImageOrientation::fromExif(2);

        EXPECT_EQ(expected, transformByImageOrientation(box, size, orientation));
    }

    { // 3. Bottom Right
        constexpr ImageBox expected{9, 5, 13, 10};
        const auto orientation = ImageOrientation::fromExif(3);

        EXPECT_EQ(expected, transformByImageOrientation(box, size, orientation));
    }

    { // 4. Bottom Left
        constexpr ImageBox expected{3, 5, 7, 10};
        const auto orientation = ImageOrientation::fromExif(4);

        EXPECT_EQ(expected, transformByImageOrientation(box, size, orientation));

    }

    { // 5. Left Top
        constexpr ImageBox expected{2, 3, 7, 7};
        const auto orientation = ImageOrientation::fromExif(5);

        EXPECT_EQ(expected, transformByImageOrientation(box, size, orientation));
    }

    { // 6. Right Top
        constexpr ImageBox expected{5, 3, 10, 7};
        const auto orientation = ImageOrientation::fromExif(6);

        EXPECT_EQ(expected, transformByImageOrientation(box, size, orientation));
    }

    { // 7. Right Bottom
        constexpr ImageBox expected{5, 9, 10, 13};
        const auto orientation = ImageOrientation::fromExif(7);

        EXPECT_EQ(expected, transformByImageOrientation(box, size, orientation));
    }

    { // 8. Left Bottom
        constexpr ImageBox expected{2, 9, 7, 13};
        const auto orientation = ImageOrientation::fromExif(8);

        EXPECT_EQ(expected, transformByImageOrientation(box, size, orientation));
    }
}

TEST(exif, image_box_forward_backward_transform_by_image_orientation) // lazy tests
{
    constexpr Size size{16, 12};
    constexpr ImageBox box{3, 2, 7, 7};

    for (int exifOrientation = 1; exifOrientation <= 8; ++exifOrientation) {
        const auto orientation = ImageOrientation::fromExif(exifOrientation);

        const ImageBox transformed = transformByImageOrientation(box, size, orientation);
        const ImageBox result = revertByImageOrientation(transformed, size, orientation);

        EXPECT_EQ(box, result);
    }
}

} // namespace maps::mrc::common::tests
