#include "module.h"
#include "../transport-thread-common/common.h"
#include <yandex/maps/wiki/validator/check.h>
#include <yandex/maps/wiki/validator/categories.h>

#include <maps/libs/geolib/include/convex_hull.h>

#include <boost/none.hpp>

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

using categories::TRANSPORT_OPERATOR;
using categories::TRANSPORT_STOP;

using categories::TRANSPORT_BUS_THREAD;
using categories::TRANSPORT_BUS_ROUTE;
using categories::TRANSPORT_TRAM_THREAD;
using categories::TRANSPORT_TRAM_ROUTE;

using categories::TRANSPORT_WATERWAY_THREAD;
using categories::TRANSPORT_WATERWAY_ROUTE;
using categories::TRANSPORT_WATERWAY_STOP;

using categories::FREQ_DT;

const TSeconds MIN_TRAVEL_TIME = 10;
const TSeconds MAX_TRAVEL_TIME = 3600;
const TSeconds MIN_WAIT_TIME = 0;
const TSeconds MAX_WAIT_TIME = 1800;

const size_t MIN_FREQUENCY = 1;
const size_t MAX_FREQUENCY = 600;

#define NOT_METRO_THREAD_CATEGORIES \
    TRANSPORT_BUS_THREAD, TRANSPORT_BUS_ROUTE, \
    TRANSPORT_TRAM_THREAD, TRANSPORT_TRAM_ROUTE, \
    TRANSPORT_WATERWAY_THREAD, TRANSPORT_WATERWAY_ROUTE


bool
threadHasStopWaitTimes(const TransportThread* thread, CheckContext* context)
{
    for (const auto& stopId : thread->stops()) {
        auto stop  = context->objects<TRANSPORT_THREAD_STOP>().byId(stopId);
        if (stop->waitTime()) {
            return true;
        }
    }
    return false;
}

bool
threadHasStopTravelTimes(const TransportThread* thread, CheckContext* context)
{
    for (const auto& stopId : thread->stops()) {
        auto stop  = context->objects<TRANSPORT_THREAD_STOP>().byId(stopId);
        if (stop->travelTime()) {
            return true;
        }
    }
    return false;
}

template <typename RouteCategory, typename ThreadCategory, typename StopCategory>
void checkThreadStopTiming(CheckContext* context)
{
    context->objects<ThreadCategory>().visit(
        [&](const TransportThread* thread)
        {
            if (!isBoundToOperatorToValidate<RouteCategory>(context, thread)) {
                return;
            }
            auto travelTimeIsMandatory =
                threadHasStopTravelTimes(thread, context)
                ? TravelTimeIsMandatory::Yes
                : TravelTimeIsMandatory::No;
            auto waitTimeIsMandatory =
                threadHasStopWaitTimes(thread, context)
                ? WaitTimeIsMandatory::Yes
                : WaitTimeIsMandatory::No;
            for (const auto& stopId : thread->stops()) {
                auto threadStop = context->objects<TRANSPORT_THREAD_STOP>().byId(stopId);
                checkThreadStopsTiming<StopCategory,
                    RouteCategory,
                    ThreadCategory>(
                        context,
                        travelTimeIsMandatory,
                        waitTimeIsMandatory,
                        threadStop,
                        MIN_TRAVEL_TIME, MAX_TRAVEL_TIME,
                        MIN_WAIT_TIME, MAX_WAIT_TIME,
                        Severity::Warning);
            }
        });
}
} // anonymous namespace

VALIDATOR_CHECK_PART(transport_thread_time, waterway_thread_stop,
    TRANSPORT_WATERWAY_STOP, TRANSPORT_THREAD_STOP,
    TRANSPORT_WATERWAY_THREAD, TRANSPORT_WATERWAY_ROUTE,
    TRANSPORT_OPERATOR)
{
    checkThreadStopTiming<
        TRANSPORT_WATERWAY_ROUTE,
        TRANSPORT_WATERWAY_THREAD,
        TRANSPORT_WATERWAY_STOP>(context);
}

VALIDATOR_CHECK_PART(transport_thread_time, tram_thread_stop,
    TRANSPORT_STOP, TRANSPORT_THREAD_STOP,
    TRANSPORT_TRAM_THREAD, TRANSPORT_TRAM_ROUTE,
    TRANSPORT_OPERATOR)
{
    checkThreadStopTiming<
        TRANSPORT_TRAM_ROUTE,
        TRANSPORT_TRAM_THREAD,
        TRANSPORT_STOP>(context);
}


VALIDATOR_CHECK_PART(transport_thread_time, bus_thread_stop,
    TRANSPORT_STOP, TRANSPORT_THREAD_STOP,
    TRANSPORT_BUS_THREAD, TRANSPORT_BUS_ROUTE,
    TRANSPORT_OPERATOR)
{
    checkThreadStopTiming<
        TRANSPORT_BUS_ROUTE,
        TRANSPORT_BUS_THREAD,
        TRANSPORT_STOP>(context);
}

template<typename... Categories>
struct Performer;
template<>
struct Performer<>
{
    Performer(CheckContext*){};
};

template<typename ThreadCategory, typename RouteCategory, typename... Categories>
struct Performer<ThreadCategory, RouteCategory, Categories...> : public Performer<Categories...>
{
    Performer(CheckContext* context)
        : Performer<Categories...>(context)
    {
        performSchedulesCheck<ThreadCategory, RouteCategory>(context, MIN_FREQUENCY, MAX_FREQUENCY);
    }

};

VALIDATOR_CHECK_PART(transport_thread_time, thread_schedules,
    NOT_METRO_THREAD_CATEGORIES, FREQ_DT, TRANSPORT_OPERATOR)
{
    Performer<NOT_METRO_THREAD_CATEGORIES> check(context);
}


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