#include "config.h"

#include "helper.h"

#include <maps/wikimap/mapspro/services/editor/src/magic_strings.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/categories_strings.h>

#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/geolib/include/distance.h>
#include <yandex/maps/wiki/common/geom_utils.h>

#include <maps/libs/common/include/exception.h>

#include <maps/libs/log8/include/log8.h>

namespace maps {
namespace wiki {

namespace {

constexpr double CACHE_RADIUS_METERS = 500;
constexpr double SEARCH_AREA_MARGIN_METERS = 15000;
constexpr double MAX_AREA_SIZE_METERS = 100000;
constexpr size_t LOAD_ELEMENTS_LIMIT = 50000;

constexpr double FC_8_PENALTY_FACTOR = 3.0;

constexpr double MIN_POSSIBLE_SPEED = 1.0;

double travelTime(const RouteElement& element) {
    return element.lengthMeters() / std::max(
        MIN_POSSIBLE_SPEED,
        element.speedIntervalMetersPerSecond().min
    );
}

} // namespace

RoutingConfig defaultTransportRoutingConfig(
        const std::string& threadCategoryId,
        const routing::Stops& stops)
{
    REQUIRE(!stops.empty(), "List of elements is empty");

    geolib3::BoundingBox bbox = findBoundingBox(stops);
    bbox = resizeByValue(bbox, geolib3::toMercatorUnits(SEARCH_AREA_MARGIN_METERS, bbox.center()));

    WIKI_REQUIRE(
        !isSizeLimitExceeded(bbox, MAX_AREA_SIZE_METERS),
        ERR_ROUTING_SEARCH_AREA_TOO_LARGE,
        "Too large area of search"
    );

    WIKI_REQUIRE(
        threadCategoryId == CATEGORY_TRANSPORT_BUS_THREAD,
        ERR_ROUTING_UNSUPPORTED_ROUTE_CATEGORY,
        "'" << threadCategoryId << "' is unsupported thread category id"
    );

    return RoutingConfig {
        CATEGORY_RD_EL, // support only rd routing
        /* aoi  = */ bbox,
        /* direction = */ [](const RouteElement& el) -> Direction {
            const Direction direction = el[STR_WAY].as<Direction>(Direction::Both);
            if (el[STR_BACK_BUS]) {
                if (direction == Direction::Both) {
                    WARN() << "RoutingConfig: Bidirectional element " << el.id()
                           << " has back_bus attribute";
                    return Direction::Both;
                }

                return isSet(AccessId::Bus, el[STR_ACCEESS_ID])
                    ? Direction::Both
                    : ymapsdf::rd::reverse(direction);
            }

            return direction;
        },
        /* filter = */ [](const RouteElement& el) {
            return el[STR_FC].as<int>() <= 8 && (
                isSet(AccessId::Bus, el[STR_ACCEESS_ID]) || el[STR_BACK_BUS]
            );
        },
        /* weight = */ [](const RouteElement& lhs, const RouteElement& /*rhs*/) {
            if (lhs[STR_FC].as<int>() == 8) {
                return FC_8_PENALTY_FACTOR * travelTime(lhs);
            }
            return travelTime(lhs);
        },
        /* condition = */ RoutingConditionConfig {
            /* filter = */ [](const RouteCondition& cond) {
                return cond.categoryId() == CATEGORY_COND
                    && isSet(AccessId::Bus, cond[STR_ACCEESS_ID]);
            },
            /* type = */ [](const RouteCondition& cond) {
                return cond[STR_COND_TYPE] == common::ConditionType::Uturn
                    ? RouteConditionType::Turnabout
                    : RouteConditionType::Forbidden;
            }
        },
        CACHE_RADIUS_METERS,
        LOAD_ELEMENTS_LIMIT
    };
}

} // namespace wiki
} // namespace maps
