#pragma once

#include <maps/wikimap/mapspro/services/mrc/libs/common/include/panorama_utils.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/panorama.h>

#include <maps/libs/http/include/url.h>
#include <maps/libs/geolib/include/heading.h>
#include <maps/libs/geolib/include/units.h>

#include <opencv2/opencv.hpp>

#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>

namespace maps::mrc::common {

constexpr geolib3::Degrees CROP_OUT_DEFAULT_HORIZONTAL_FOV{70};
constexpr geolib3::Heading CROP_OUT_DEFAULT_STRIDE{60};

// For the object features to be distuguished bbox in FHD image should be at
// least 30x30 pixels. So bbox area ratio to image size should be about
// 0.00043402777777777778 = (30x30) / (1920*1080).
constexpr float MIN_VISIBLE_BBOX_AREA_RATIO = (30.f * 30.f) / (1920.f * 1080.f);

using PanoTilesDownloaderFactory =
    std::function<std::unique_ptr<PanoTilesDownloader>()>;

// This class provides a mean to acquire crop-outs from a panorama with
// azimuth (aka Heading).
class PanoramaCropper {
public:
    PanoramaCropper(
        PanoTilesDownloaderFactory panoDownloaderFactory,
        const PanoCutParams& cutParams,
        geolib3::Heading horizontalAngle,
        geolib3::Degrees verticalAngle,
        geolib3::Degrees cropOutHorizontalFOV =
            CROP_OUT_DEFAULT_HORIZONTAL_FOV);

    PanoramaCropper(const PanoramaCropper&) = delete;
    PanoramaCropper(PanoramaCropper&&) = delete;
    PanoramaCropper& operator=(const PanoramaCropper&) = delete;
    PanoramaCropper& operator=(PanoramaCropper&&) = delete;

    // Compute crop-out parameters with a given cropOutCenter.
    PanoCropOut getCropOut(geolib3::Heading cropOutCenter) const;

    // Central horizon point is given in crop-out image coordinates.
    cv::Point getCentralHorizonPoint(geolib3::Heading cropOutCenter) const;

    // Get a decoded image oriented by a given cropOutCenter.
    cv::Mat getImage(geolib3::Heading cropOutCenter);

    // Return a range of decoded images there the first image is centerd on a
    // given startingCropOutCenter.
    std::vector<cv::Mat> getImagesRange(
        geolib3::Heading startingCropOutCenter,
        geolib3::Heading cropOutStride = CROP_OUT_DEFAULT_STRIDE);

private:

    // Load tiles with the tiles donwnloader passed to this class ctor.
    PanoTiles getTiles(const PanoCropOut& cropOut);

    PanoTilesDownloaderFactory panoDownloaderFactory_;
    PanoCutParams cutParams_;
    geolib3::Heading horizontalAngle_;
    geolib3::Degrees verticalAngle_;
    geolib3::Degrees cropOutHorizontalFOV_;
};

// Pixels to degrees conversion
template <typename T>
inline T convertPixelsTo(std::size_t panoramaTotalWidth, float pixels)
{
    return T{pixels * 360.0 / panoramaTotalWidth};
}

// Degrees to pixels conversion
template <typename T>
inline float convertToPixels(std::size_t panoramaTotalWidth, T arg)
{
    return arg.value() * panoramaTotalWidth / 360.0f;
}

// Compute a panorama crop-out parameters using an object's bounding box with
// this bounding box placed in the center. Notice what this crop-out is
// oriented to the true horizon vertically. The objects must have minX and
// maxY returning values in the panorama projection image coordinates.
template<typename Object>
std::tuple<geolib3::Heading, PanoCropOut> getObjectCropOutWithHeading(
    const db::Panorama& panorama, const Object& object);

} // namespace maps::mrc::common
