#include "module.h"
#include "../transport-thread-common/common.h"
#include "../utils/geom.h"
#include "../utils/misc.h"

#include <yandex/maps/wiki/validator/check.h>
#include <yandex/maps/wiki/validator/categories.h>
#include <maps/libs/geolib/include/convex_hull.h>

namespace maps {
namespace wiki {
namespace validator {
namespace checks {

using categories::TRANSPORT_OPERATOR;
using categories::TRANSPORT_METRO_EL;
using categories::TRANSPORT_METRO_THREAD;
using categories::TRANSPORT_THREAD_STOP;
using categories::TRANSPORT_METRO_STATION;
using categories::TRANSPORT_METRO_LINE;

namespace {
const double THREAD_TO_STATION_MAX_DISTANCE = 80.0;//meters

const TransportMetroStation*
stationByStop(CheckContext* context, TId stopId)
{
    auto stop = context->objects<TRANSPORT_THREAD_STOP>().byId(stopId);
    return context->objects<TRANSPORT_METRO_STATION>().byId(stop->station());
}
}//namespace

VALIDATOR_CHECK_PART( transport_thread_station_distance, thread_to_stations,
    TRANSPORT_METRO_THREAD, TRANSPORT_METRO_EL, TRANSPORT_THREAD_STOP,
    TRANSPORT_METRO_STATION, TRANSPORT_METRO_LINE,
    TRANSPORT_OPERATOR)
{
    context->objects<TRANSPORT_METRO_THREAD>().visit([&](const TransportThread* thread) {
        if (!isBoundToOperatorToValidate<TRANSPORT_METRO_LINE>(context, thread)) {
            return;
        }
        for (auto stopId : thread->stops()) {
            auto station = stationByStop(context, stopId);
            bool closeEnough = false;
            for (auto elementId : thread->elements()) {
                auto element = context->objects<TRANSPORT_METRO_EL>().byId(elementId);
                double distance = utils::mercatorDistanceRatio(station->geom()) *
                        geolib3::distance(element->geom(), station->geom());
                if (distance < THREAD_TO_STATION_MAX_DISTANCE) {
                    closeEnough = true;
                    break;
                }
            }
            if (!closeEnough) {
                context->warning(
                    "station-too-far-from-thread",
                    station->geom(),
                    {thread->id(), station->id()});
            }
        }
    });//visit
}

VALIDATOR_CHECK_PART( transport_thread_station_distance, stations_to_stations,
    TRANSPORT_METRO_THREAD, TRANSPORT_THREAD_STOP,
    TRANSPORT_METRO_STATION, TRANSPORT_METRO_LINE,
    TRANSPORT_OPERATOR)
{
    context->objects<TRANSPORT_METRO_THREAD>().visit([&](const TransportThread* thread){
        if (!isBoundToOperatorToValidate<TRANSPORT_METRO_LINE>(context, thread)) {
            return;
        }
        if (thread->stops().size() < 3) {
            return;
        }
        std::map<TId, TId> stopIdToNextId;
        for (auto stopId : thread->stops()) {
            auto stop = context->objects<TRANSPORT_THREAD_STOP>().byId(stopId);
            if (!stopIdToNextId.insert({stop->previous(), stop->id()}).second) {
                return;
            }
        }
        auto curIt = stopIdToNextId.find(0);
        while (curIt != stopIdToNextId.end()) {
            auto nextIt = stopIdToNextId.find(curIt->second);
            if (nextIt == stopIdToNextId.end()) {
                break;
            }
            auto nextNextIt = stopIdToNextId.find(nextIt->second);
            if (nextNextIt == stopIdToNextId.end()) {
                break;
            }
            auto curStation = stationByStop(context, curIt->second);
            auto nextStation = stationByStop(context, nextIt->second);
            auto nextNextStation = stationByStop(context, nextNextIt->second);
            if (geolib3::distance(curStation->geom(), nextStation->geom()) >
                geolib3::distance(curStation->geom(), nextNextStation->geom())) {
                    context->warning(
                        "thread-stations-spatial-alignment-suspicious",
                        geolib3::bufferedConvexHull(
                            {curStation->geom(), nextStation->geom(), nextNextStation->geom()},
                            utils::BUFFER_DISTANCE),
                        {thread->id(), curStation->id(), nextStation->id(), nextNextStation->id()});
            }
            curIt = nextIt;
        }
    });//visit
}



} // namespace checks
} // namespace validator
} // namespace wiki
} // namespace maps
