#include <maps/wikimap/mapspro/services/autocart/libs/satellite/include/load_sat_image.h>

#include <maps/libs/common/include/exception.h>
#include <maps/libs/tile/include/utils.h>
#include <maps/libs/http/include/http.h>
#include <maps/libs/log8/include/log8.h>

#include <opencv2/opencv.hpp>

#include <thread>
#include <chrono>

namespace maps::wiki::autocart {

namespace {

cv::Rect getDisplayRect(const geolib3::BoundingBox& mercatorBbox, size_t zoom) {
    tile::DisplayCoord pt1 = tile::mercatorToDisplay(mercatorBbox.lowerCorner(), zoom);
    tile::DisplayCoord pt2 = tile::mercatorToDisplay(mercatorBbox.upperCorner(), zoom);

    cv::Rect rcDisplay(cv::Point(pt1.x(), pt1.y()), cv::Point(pt2.x(), pt2.y()));

    // add cv::Size(1, 1) - for picking right and bootom borders of rectangle
    return rcDisplay + cv::Size(1, 1);
}

void loadTileImage(const std::string& tileSourceUrl, http::Client &client, const tile::Tile& tile, cv::Mat &image) {
    constexpr int RETRY_NUMBER = 10;
    constexpr std::chrono::seconds RETRY_TIMEOUT(3);

    http::URL url(tileSourceUrl);
    url.addParam("l", "sat");
    url.addParam("x", tile.x());
    url.addParam("y", tile.y());
    url.addParam("z", tile.z());

    http::Request request(client, http::GET, url);

    for (int i = 0; i <= RETRY_NUMBER; i++) {
        try {
            auto response = request.perform();
            if (response.status() == 200) {
                std::string data = response.readBody();
                image = cv::imdecode(cv::Mat(1, data.size(), CV_8UC1,
                                             const_cast<char*>(data.data())),
                                     cv::IMREAD_COLOR);
                return;
            } else {
                INFO() << "Got unexpected response status " << response.status();
            }
        } catch (const maps::Exception& e) {
            WARN() << e;
        }
        if (i != RETRY_NUMBER) {
            INFO() << "Retry";
            std::this_thread::sleep_for(RETRY_TIMEOUT);
        }
    }
    throw maps::RuntimeError("Failed to download tile image. Tile is unavailable");
}

void calcTileRect(const tile::Tile& tile, cv::Rect &rc) {
    tile::DisplayBox tileDB = tile.dispBox();
    rc = cv::Rect(cv::Point(tileDB.lt().x(), tileDB.lt().y()),
                  cv::Point(tileDB.rb().x(), tileDB.rb().y()));
    rc.width++;
    rc.height++;
    // +1 - for picking right and bootom borders of rectangle
}

} // anonymous namespace

cv::Mat loadSatImage(const geolib3::BoundingBox& mercatorBbox,
                     size_t zoom,
                     const std::string& tileSourceUrl)
{
    cv::Mat result;

    http::Client client;

    const cv::Rect rcDisplay = getDisplayRect(mercatorBbox, zoom);
    result.create(rcDisplay.size(), CV_8UC3);

    tile::TileRange tiles({tile::mercatorToTile(mercatorBbox.upperCorner(), zoom),
                           tile::mercatorToTile(mercatorBbox.lowerCorner(), zoom)});

    cv::Mat imgTile;
    cv::Rect rcTile;
    for (auto tile : tiles)
    {
        loadTileImage(tileSourceUrl, client, tile, imgTile);
        calcTileRect(tile, rcTile);
        REQUIRE(rcTile.size() == imgTile.size(),
                "Failed to download tile image. Get tile with invalid size");

        const cv::Rect intersect = (rcTile & rcDisplay);
        if (0 == intersect.area())
            continue;

        const cv::Point offset(std::max(0, intersect.x - rcTile.x),
                               std::max(0, intersect.y - rcTile.y));
        const cv::Rect rc = intersect - rcDisplay.tl();
        imgTile(cv::Rect(offset, rc.size())).copyTo(result(rc));
    }

    return result;
}

} // namespace maps::wiki::autocart
