#include <maps/wikimap/mapspro/services/mrc/tools/sign-positioning-dataset/common/constants.h>

#include <maps/wikimap/mapspro/services/mrc/libs/common/include/exif.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/types.h>
#include <yandex/maps/proto/offline-mrc/results.sproto.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/json/include/value.h>
#include <maps/libs/log8/include/log8.h>

#include <boost/filesystem.hpp>

#include <cstdio>
#include <fstream>

using namespace maps::mrc;
namespace spresults = yandex::maps::sproto::offline::mrc::results;

namespace {

void visualize(const maps::json::Value& imagesMeta,
               const spresults::Results& mrcResults,
               std::optional<std::uint64_t> startingFeatureId)
{
    for (std::size_t idx = 0; idx < imagesMeta.size(); ++idx) {
        REQUIRE(mrcResults.images()[idx].created()
                    == imagesMeta[idx]["feature_ts"].as<std::uint64_t>(),
                "The data must be aligned!");

        const auto featureId
            = imagesMeta[idx]["feature_id"].as<std::uint64_t>();
        if (startingFeatureId && *startingFeatureId == featureId) {
            startingFeatureId = std::nullopt;
        }
        else if (startingFeatureId) {
            continue;
        }

        const auto& loc = mrcResults.images()[idx].estimatedPosition();
        std::cout << "\nsigns for feature_id = " << featureId
                  << " at \"lat, lon\": " << loc->point().lat() << ", "
                  << loc->point().lon() << " and \"heading\": "
                  << (loc->heading() ? *loc->heading() : -666.f) << "\n";

        cv::Mat decodedImage
            = common::decodeImage(mrcResults.images()[idx].image());

        const auto orientation = common::ImageOrientation::fromExif(
            imagesMeta[idx]["exif_orientation"].as<int>());
        decodedImage = transformByImageOrientation(decodedImage, orientation);

        cv::putText(decodedImage, std::to_string(featureId),
                    {0, decodedImage.rows}, cv::FONT_HERSHEY_SIMPLEX, 1.5,
                    {0, 0, 255}, 3);
        for (const auto& sign : imagesMeta[idx]["signs"]) {
            const auto signType = sign["sign_type"].as<std::string>();
            const auto minX = sign["bbox"][0].as<std::size_t>();
            const auto minY = sign["bbox"][1].as<std::size_t>();
            const auto maxX = sign["bbox"][2].as<std::size_t>();
            const auto maxY = sign["bbox"][3].as<std::size_t>();

            const auto imageBox = revertByImageOrientation(
                {minX, minY, maxX, maxY}, {(std::size_t)decodedImage.cols,
                                           (std::size_t)decodedImage.rows},
                orientation);
            cv::rectangle(decodedImage, imageBox, {0, 255, 0}); // BGRA
            cv::putText(decodedImage, signType,
                        {(int)imageBox.minX(), (int)imageBox.maxY()},
                        cv::FONT_HERSHEY_SIMPLEX, .8, {0, 0, 255}, 3);

            std::cout << "[" << minX << ", " << minY << ", " << maxX << ", "
                      << maxY << "]\n";
            std::cout << imageBox << "\n";
            std::cout << "{ \"sign_type\" : \"" << signType
                      << "\", \"sign_id\" : , \"wgs84_pos\" : [], "
                         "\"azimuth\" : }\n";
        }

        auto encodedImage = common::encodeImage(decodedImage);
        std::ofstream{"image.jpeg", std::ios::binary}.write(
            reinterpret_cast<char*>(encodedImage.data()),
            encodedImage.size());

        std::string cmd;
        while (true) {
            (std::cout << "\ntype 'n' or 'b' enter (n - next, b - back)\n")
                .flush();
            std::cin >> cmd;
            if (idx > 0 && !cmd.empty() && cmd[0] == 'b') {
                idx -= 2;
                break;
            }
            else if (!cmd.empty() && cmd[0] == 'n') {
                break;
            }
        }
    }
}

} // namespace

int main(int argc, char** argv)
{
    maps::cmdline::Parser parser("A tool to visualize MRC datasets");
    auto datasetDir
        = parser.dir("dir").help("dataset directory").defaultValue(".");
    auto datasetPrefix
        = parser.string("prefix").help("dataset prefix").required();
    auto featureId
        = parser.num("feature-id").help("feature id to start with");
    parser.parse(argc, argv);

    const auto metaPath
        = boost::filesystem::path{datasetDir}
          / (datasetPrefix + dataset::IMAGES_META_FILENAME_POSTFIX);
    const auto resultsPath
        = boost::filesystem::path{datasetDir}
          / (datasetPrefix + dataset::RESULTS_FILENAME_POSTFIX);

    spresults::Results mrcResults;
    std::ifstream{resultsPath.string(), std::ios::binary} >> mrcResults;
    const maps::json::Value imagesMeta
        = maps::json::Value::fromFile(metaPath.string());

    REQUIRE(imagesMeta.size() == mrcResults.images().size(),
            "Images metadata must be aligned with MRC results!");

    std::optional<std::uint64_t> featureIdOpt;
    if (featureId.defined()) {
        featureIdOpt = static_cast<std::uint64_t>(featureId);
    }
    visualize(imagesMeta, mrcResults, featureIdOpt);

    return EXIT_SUCCESS;
}
