#include "osrm/match_parameters.hpp"
#include "osrm/nearest_parameters.hpp"
#include "osrm/route_parameters.hpp"
#include "osrm/table_parameters.hpp"
#include "osrm/trip_parameters.hpp"

#include "osrm/coordinate.hpp"
#include "osrm/engine_config.hpp"
#include "osrm/json_container.hpp"

#include "osrm/osrm.hpp"
#include "osrm/status.hpp"

#include <exception>
#include <iostream>
#include <string>
#include <utility>

#include <cstdlib>

int main(int argc, const char *argv[])
{
    if (argc < 2)
    {
        std::cerr << "Usage: " << argv[0] << " data.osrm\n";
        return EXIT_FAILURE;
    }

    using namespace osrm;

    // Configure based on a .osrm base path, and no datasets in shared mem from osrm-datastore
    EngineConfig config;

    config.storage_config = {argv[1]};
    config.use_shared_memory = false;

    // We support two routing speed up techniques:
    // - Contraction Hierarchies (CH): requires extract+contract pre-processing
    // - Multi-Level Dijkstra (MLD): requires extract+partition+customize pre-processing
    //
    // config.algorithm = EngineConfig::Algorithm::CH;
    config.algorithm = EngineConfig::Algorithm::MLD;

    // Routing machine with several services (such as Route, Table, Nearest, Trip, Match)
    const OSRM osrm{config};

    // The following shows how to use the Route service; configure this service
    RouteParameters params;

    // Route in monaco
    params.coordinates.push_back({util::FloatLongitude{7.419758}, util::FloatLatitude{43.731142}});
    params.coordinates.push_back({util::FloatLongitude{7.419505}, util::FloatLatitude{43.736825}});

    // Response is in JSON format
    engine::api::ResultT result = json::Object();

    // Execute routing request, this does the heavy lifting
    const auto status = osrm.Route(params, result);

    auto &json_result = std::get<json::Object>(result);
    if (status == Status::Ok)
    {
        auto &routes = std::get<json::Array>(json_result.values["routes"]);

        // Let's just use the first route
        auto &route = std::get<json::Object>(routes.values.at(0));
        const auto distance = std::get<json::Number>(route.values["distance"]).value;
        const auto duration = std::get<json::Number>(route.values["duration"]).value;

        // Warn users if extract does not contain the default coordinates from above
        if (distance == 0 || duration == 0)
        {
            std::cout << "Note: distance or duration is zero. ";
            std::cout << "You are probably doing a query outside of the OSM extract.\n\n";
        }

        std::cout << "Distance: " << distance << " meter\n";
        std::cout << "Duration: " << duration << " seconds\n";
        return EXIT_SUCCESS;
    }
    else if (status == Status::Error)
    {
        const auto code = std::get<json::String>(json_result.values["code"]).value;
        const auto message = std::get<json::String>(json_result.values["message"]).value;

        std::cout << "Code: " << code << "\n";
        std::cout << "Message: " << message << "\n";
        return EXIT_FAILURE;
    }
    else
    {
        std::cout << "Unexpected status returned from OSRM::Route.\n";
        return EXIT_FAILURE;
    }
}
