#pragma once
#include "configuration.h"
#include "tools.h"
#include "types.h"

#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/string_utils.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/panorama_utils.h>

#include <maps/libs/log8/include/log8.h>
#include <maps/infra/yacare/include/yacare.h>
#include <util/generic/string.h>
#include <util/string/cast.h>

#include <boost/lexical_cast.hpp>

namespace maps::mrc::browser {

inline std::optional<db::CameraDeviation>
parseOptionalCameraDeviation(const yacare::Request& request)
{
    static constexpr std::string_view CAMERA_DIRECTION_PARAM = "camera-direction";
    const auto& valueStr = request.input()[CAMERA_DIRECTION_PARAM];
    if (valueStr.empty()) {
        return std::nullopt;
    }
    try {
        return FromString<db::CameraDeviation>(to_title(TString(valueStr)));
    }
    catch (const std::exception&) {
        throw yacare::errors::BadRequest() << "invalid " << CAMERA_DIRECTION_PARAM;
    }
}

inline std::optional<chrono::TimePoint>
parseOptionalTimePoint(const yacare::Request& request, std::string_view name)
{
    const auto& valueStr = request.input()[name];
    if (valueStr.empty()) {
        return std::nullopt;
    }
    try {
        return chrono::parseIsoDateTime(valueStr);
    }
    catch (const std::exception&) {
        throw yacare::errors::BadRequest() << "invalid " << name;
    }
}

inline std::optional<chrono::TimePoint>
parseOptionalDate(const yacare::Request& request, std::string_view name)
{
    const auto& valueStr = request.input()[name];
    if (valueStr.empty()) {
        return std::nullopt;
    }
    try {
        return maps::chrono::parseIsoDate(valueStr);
    }
    catch (const std::exception&) {
        throw yacare::errors::BadRequest() << "invalid " << name;
    }
}

} //namespace maps::mrc::browser

template<>
struct yacare::Parser<maps::mrc::common::ImageBoxes> {
    maps::mrc::common::ImageBoxes operator()(const std::string& parameter) const
    {
        maps::mrc::common::ImageBoxes result;

        if (parameter.empty()) {
            return result;
        }

        for (const auto& box: maps::wiki::common::split(parameter, ";")) {
            const std::vector<std::string> coords = maps::wiki::common::split(box, ",");
            if (coords.size() != 4) {
                throw std::bad_cast();
            }

            try {
                result.emplace_back(
                    std::stoull(coords[0]),
                    std::stoull(coords[1]),
                    std::stoull(coords[2]),
                    std::stoull(coords[3])
                );
            } catch (std::invalid_argument& e) {
                throw std::bad_cast();
            }
        }

        return result;
    }
};

YCR_QUERY_PARAM(boxes, maps::mrc::common::ImageBoxes);

template <>
struct yacare::Parser<maps::mrc::common::PanoCutParams> {
    maps::mrc::common::PanoCutParams operator()(
        const std::string& parameter) const
    {
        const std::vector<std::string> strParams
            = maps::wiki::common::split(parameter, ",");
        if (strParams.size() != 5) {
            throw std::bad_cast{};
        }

        try {
            return {boost::lexical_cast<std::uint32_t>(strParams[0]),
                    boost::lexical_cast<std::uint32_t>(strParams[1]),
                    boost::lexical_cast<std::uint32_t>(strParams[2]),
                    boost::lexical_cast<std::uint32_t>(strParams[3]),
                    boost::lexical_cast<std::uint16_t>(strParams[4])};
        } catch (const boost::bad_lexical_cast&) {
            throw std::bad_cast{};
        }
    }
};

YCR_QUERY_PARAM(cut_params, maps::mrc::common::PanoCutParams);

template <>
struct yacare::Parser<maps::mrc::common::PanoCropOut> {
    maps::mrc::common::PanoCropOut operator()(
        const std::string& parameter) const
    {
        const std::vector<std::string> strParams
            = maps::wiki::common::split(parameter, ",");
        if (strParams.size() != 5) {
            throw std::bad_cast{};
        }

        try {
            return {boost::lexical_cast<std::uint32_t>(strParams[0]),
                    boost::lexical_cast<std::uint32_t>(strParams[1]),
                    boost::lexical_cast<std::uint32_t>(strParams[2]),
                    boost::lexical_cast<std::uint32_t>(strParams[3]),
                    boost::lexical_cast<float>(strParams[4])};
        } catch (const boost::bad_lexical_cast&) {
            throw std::bad_cast{};
        }
    }
};

YCR_QUERY_PARAM(crop_out, maps::mrc::common::PanoCropOut);

YCR_QUERY_CUSTOM_PARAM((), orientation, std::optional<maps::mrc::common::ImageOrientation>)
{
    dest = std::nullopt;
    int exifOrientation;
    if (!yacare::impl::parseArg(exifOrientation, request, "orientation")) {
        return false;
    }

    try {
        dest = maps::mrc::common::ImageOrientation::fromExif(exifOrientation);
    } catch (const std::exception& e) {
        throw yacare::errors::BadRequest() << e.what();
    }

    return true;
}

static constexpr std::string_view GRAPH_TYPE_PARAM = "graph_type";

inline maps::mrc::db::GraphType parseGraphType(const std::string& valueStr) {
    try {
        return FromString<maps::mrc::db::GraphType>(to_title(TString(valueStr)));
    }
    catch (const std::exception& e) {
        throw yacare::errors::BadRequest()
            << "invalid " << GRAPH_TYPE_PARAM << ": " << e.what();
    }
}

inline std::optional<maps::mrc::db::GraphType>
parseOptionalGraphType(const yacare::Request& request)
{
    using maps::mrc::db::GraphType;
    const auto& valueStr = request.input()[GRAPH_TYPE_PARAM];
    if (valueStr.empty()) {
        return std::nullopt;
    }

    return parseGraphType(valueStr);
}

YCR_QUERY_CUSTOM_PARAM((), graphType, maps::mrc::db::GraphType)
{
    auto maybeGraphType = parseOptionalGraphType(request);
    if (maybeGraphType.has_value()) {
        dest = maybeGraphType.value();
        return true;
    } else {
        return false;
    }
}

YCR_QUERY_PARAM(style, std::string);

YCR_QUERY_PARAM(with_authors, bool);

YCR_QUERY_PARAM(snap_to_road_graph, bool);

YCR_QUERY_PARAM(debug, bool);

YCR_QUERY_PARAM(dataset, std::string);

YCR_QUERY_PARAM(zmin, unsigned);

YCR_QUERY_PARAM(zmax, unsigned);

YCR_QUERY_PARAM(before_limit, unsigned);
YCR_QUERY_PARAM(after_limit, unsigned);

YCR_QUERY_PARAM(limit, unsigned);

YCR_QUERY_PARAM(id, std::int64_t);
YCR_QUERY_PARAM(ids, std::vector<maps::mrc::db::TId>);

YCR_QUERY_PARAM(vec_protocol, unsigned,
    YCR_CONSTRAINT(yacare::constraints::within(2u, 3u)), YCR_DEFAULT(2u));

YCR_QUERY_CUSTOM_PARAM(("format"), contentType, std::string, YCR_DEFAULT(std::string()))
{
    std::string format;
    if (!yacare::impl::parseArg(format, request, "format")) {
        dest = yacare::request().env("HTTP_ACCEPT");
    } else {
        auto convertedFormat = maps::mrc::browser::formatToContentType(format);
        if (convertedFormat.empty()) {
            throw yacare::errors::BadRequest() << " malformed 'format' parameter";
        }
        dest = convertedFormat;
    }
    return true;
}

YCR_QUERY_PARAM(experimental_mrc_design, std::optional<std::string>)

YCR_QUERY_PARAM(heading, double)
YCR_QUERY_PARAM(tilt, double)
YCR_QUERY_PARAM(hfov, double)
YCR_QUERY_PARAM(size_name, std::string);

YCR_QUERY_PARAM(experimental_equidistant_as_style, int)
