#include <mapreduce/yt/interface/client.h>
#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/geo_helpers.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/graph_load.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/hyp_generator.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/hyp_serialize.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/consts.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/yt_ops.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/geolib/include/point.h>
#include <maps/libs/geolib/include/bounding_box.h>
#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/wiki/common/geom_utils.h>

#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

namespace bg = boost::gregorian;
namespace mwc = maps::wiki::common;
namespace mwt = maps::wiki::traffic_analyzer;
namespace mg = maps::geolib3;

const int averageWindowDefault = 1;

/*
    Input:
      -- tracks matching statistics (at YT) for chosen signals day
      -- static road graph, existing at localmachine or at YT
    Output:
      -- to stdout: oneway hypotheses in terms of static road graph, serialized in json
*/
int main(int argc, char* argv[]) try
{
    NYT::Initialize(argc, const_cast<const char**>(argv));

    maps::cmdline::Parser parser;

    auto lastSignalsDay = parser.string("last-signals-day")
        .help("Last signals day")
        .required();

    auto averagingWindow = parser.num("average-window")
        .defaultValue(averageWindowDefault)
        .help("Averaging window size. For example. If equal to 1 - "
              "only last day signals will be analyzed."
              " If equal to 2 - last day and the day before last. And so on.");

    auto bboxGeoString = parser.string("bbox-geo")
        .defaultValue(mwt::WORLD_BBOX_GEO_STRING)
        .help("Comma separated string with bounding box (Geo) "
              "of hypotheses area. String format: lon1,lat1,lon2,lat2");

    auto alarmMatchRatio = parser.real("alarm-match-ratio")
        .help("Hypothesis parameter. "
              "If matched ratio of tracks for particular road is greater than "
              "this value, a hypothesis will be produced")
        .required();

    auto minBothEdgeTracks = parser.num("min-both-edge-tracks")
        .help("Hypothesis parameter. "
              "If number of matched tracks for particular road (on both "
              "directions) is less than this value, no hypothesis "
              "will be produced")
        .required();

    auto maxFunClass = parser.num("max-funclass")
        .help("Hypothesis parameter. "
              "If fun-class of particular road is greater than this value, "
              "no hypothesis will be produced")
        .required();

    parser.parse(argc, argv);

    // Take matching statistics from YT
    INFO() << "Mining matched tracks statistics for graph roads";

    bg::date_period signalsDays(
        bg::from_string(lastSignalsDay) - bg::date_duration(averagingWindow - 1),
        bg::from_string(lastSignalsDay) + bg::date_duration(1)
    );
    auto persIdsToTracks = mwt::getYTRoadsStat(signalsDays);

    // Choosing graph version
    auto graphVersion = mwt::oldestGraphVersionOfMatchedTracks(signalsDays);

    // Choose roads from bbox
    INFO() << "Loading static road graph";
    mwt::GraphMmapped graph = mwt::loadGraph(graphVersion);

    INFO() << "Loading graph roads from bounding box";
    auto bboxGeo = mwc::bboxFromCommaSeparatedCoords(bboxGeoString);
    auto twoWayRoads = graph.getTwoWayRoads(bboxGeo);

    // Produce hypotheses
    INFO() << "Generating one-way hypotheses for static road graph";
    mwt::OnewayGenParams genParams(
        alarmMatchRatio, maxFunClass, minBothEdgeTracks);
    mwt::OnewayHypothesisGenerator gen(
        graph, std::move(persIdsToTracks), genParams);

    auto hypos = gen.generate(twoWayRoads);
    for (const auto& hyp : hypos) {
        std::cout << toJson(hyp) << "\n";
    }

    return 0;

} catch (const maps::Exception& e) {
    INFO() << e;
    return 0;
} catch (const std::exception& e) {
    INFO() << e.what();
    return 0;
}
