#pragma once

#include <yandex/maps/wiki/graph/common.h>
#include <yandex/maps/wiki/graph/graph.h>

#include "common.h"
#include "config.h"

#include "junction_geom_index.h"
#include "route_condition.h"
#include "route_element.h"

#include <maps/wikimap/mapspro/services/editor/src/exception.h>

#include <maps/libs/geolib/include/bounding_box.h>
#include <maps/libs/geolib/include/point.h>
#include <yandex/maps/wiki/revision/snapshot.h>
#include <yandex/maps/wiki/routing/auto_id_map.h>

#include <boost/optional.hpp>

#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>


namespace maps {
namespace wiki {


typedef std::map<LevelJunctionID, std::vector<DirectedElementID>> IncidenceMap;

struct Incidence {
    IncidenceMap in;
    IncidenceMap out;
};

typedef std::map<std::pair<RouteConditionType, TOid>, ConditionPositions> ConditionPositionMap;

enum class IsStartOfForbiddenCondition { No, Yes, YesAndMoveIsForbidden };

// Directed, dynamicly loaded route graph:
// * Route element - edges of some graph (e.g. rd_el, tram_el and etc)
// * Node - tuple (route element, direction)
// * Edge - (A, D1) -> (B, D2) means, that you may go from element A to element B via some junction,
//      using the appropriate directions D1 and D2 and with condition consideration
class DynamicRouteGraph {

public:
    DynamicRouteGraph(revision::Snapshot snapshot, Transaction& viewTxn, RoutingConfig config);

    std::vector<graph::NodeID> nodesAtDistance(const geolib3::Point2& point, double radiusMeters);

    boost::optional<graph::NodeID> node(
        const DirectedElementID& directedElementID,
        boost::optional<geolib3::BoundingBox> cacheHint = boost::none);

    const graph::Edges edges(graph::NodeID nodeId);

    const CompoundNodeID& getCompoundNodeId(graph::NodeID nodeId) const;

    const RouteElement& getElement(TOid elementId) const;

private:
    const RouteCondition& getCondition(TOid conditionId) const;

    TOIds cacheJunctionIds(const geolib3::BoundingBox& bbox);

    TOIds cacheFilteredElementsAndReturnIds(const TOIds& elementIds);

    revision::Revisions loadRelationsWithElements(const TOIds& junctionIds) const;

    void cacheFilteredCondtionsForElements(const TOIds& elementIds);

    Incidence computeIncidence(const revision::Revisions& junctionRelationsWithElements) const;

    const ConditionPositions& getConditionPositions(RouteConditionType type, TOid elementId) const;

    bool isTurnaboutPossible(TOid elementId, TOid junctionId) const;

    IsStartOfForbiddenCondition isStartOfForbiddenCondition(
        TOid fromElId,
        TOid viaJcId,
        TOid toElId) const;

    bool anyConditionForbidPath(const ConditionPath& path) const;

    ConditionPath getLongestPathSuffixForbiddenConditionPrefix(const ConditionPath& path) const;

    void addNodes(const Incidence& incidence);

    void cacheByBBox(const geolib3::BoundingBox& box);

    void cacheByNodeId(graph::NodeID nodeId);

    void cacheByPoint(const geolib3::Point2& point);

    revision::Snapshot snapshot_;
    JunctionGeomIndex junctionIndex_;

    routing::AutoIdMap<CompoundNodeID> nodeIdMap_;
    TOIds cachedJunctionIds_;
    TOIds discardedElementIds_;
    std::map<TOid, RouteElement> idToElement_;
    TOIds discardedConditionIds_;
    std::map<TOid, RouteCondition> idToCondition_;
    ConditionPositionMap conditionPositionsMap_;
    std::map<graph::NodeID, graph::Edges> nodeIdToEdges_;

    RoutingConfig config_;
};

} // namespace wiki
} // namepspace maps
