#pragma once

#include "../events_data.h"

#include <yandex/maps/wiki/topo/common.h>
#include <yandex/maps/wiki/topo/edge.h>
#include <yandex/maps/wiki/topo/events.h>
#include <yandex/maps/wiki/topo/node.h>

#include <maps/libs/geolib/include/segment.h>
#include <maps/libs/geolib/include/polyline.h>

#include <initializer_list>
#include <functional>
#include <memory>


namespace maps {
namespace wiki {
namespace topo {
namespace geom {


typedef std::unique_ptr<SplitEdgeEventData> SplitEdgeEventDataPtr;


typedef std::function<bool(const NodeID&)> DeleteNodeChecker;


struct SnapPointData;
typedef std::shared_ptr<SnapPointData> SnapPointDataPtr;

class SnapPoint {

    template<class TKey>
    friend struct std::hash;

public:
    explicit SnapPoint(const geolib3::Point2& point);
    explicit SnapPoint(const Node& node);
    SnapPoint(const Edge& edge, size_t vertexIndex);
    SnapPoint(const Edge& edge, size_t segmentIndex, const geolib3::Point2& point);

    const OptionalNodeID& node() const;
    const OptionalEdgeID& edge() const;
    const OptionalIndex& vertexIndex() const;
    const OptionalIndex& segmentIndex() const;
    const geolib3::Point2& geom() const;

    bool onSegment(const Edge& edge, size_t segmentIndex) const;
    bool onEdge(const Edge& edge) const;

    bool operator==(const SnapPoint& other) const;
    bool operator!=(const SnapPoint& other) const;

private:

    SnapPointDataPtr data_;
};

typedef std::vector<SnapPoint> SnapPointVector;


struct SnapPolylineData;
typedef std::shared_ptr<SnapPolylineData> SnapPolylineDataPtr;

class SnapPolyline {

    template<class TKey>
    friend struct std::hash;

public:

    explicit SnapPolyline(const SnapPointVector& points);
    explicit SnapPolyline(std::initializer_list<SnapPoint> points);

    const SnapPoint& operator[](size_t index) const;
    size_t size() const;
    bool empty() const;
    bool operator==(const SnapPolyline& other) const;
    bool isPoint() const;

    const SnapPoint& start() const;
    const SnapPoint& end() const;
    const geolib3::Polyline2& geom() const;
    const SnapPointVector& points() const;

    // Append points of other polyline at the end.
    // Polylines must have common point (end and start point respectively).
    void extend(const SnapPolyline& other);

    // User must ensure that the specified polyline is subpolyline,
    // in other case undefined behavior is possible.
    bool hasSameOrder(const SnapPolyline& subpolyline) const;

private:

    SnapPolylineDataPtr data_;
};


typedef std::vector<SnapPolyline> SnapPolylineVector;


} // namespace maps::wiki::topo::geom
} // namespace maps::wiki::topo
} // namespace maps::wiki
} // namespace maps


namespace std {

template<>
struct hash<maps::wiki::topo::geom::SnapPoint>{
    size_t operator()(const maps::wiki::topo::geom::SnapPoint& point) const;
};

template<>
struct hash<maps::wiki::topo::geom::SnapPolyline>{
    size_t operator()(const maps::wiki::topo::geom::SnapPolyline& polyline) const;
};

} // end of std namespace
