#pragma once

#include "segments_graph.h"
#include "segments_processing_common.h"

namespace maps {
namespace wiki {
namespace topology_fixer {
namespace utils {

class SegmentsPairOverlapper {
public:
    SegmentsPairOverlapper(
            double tolerance,
            double maxAngleBetweenSegments)
        : tolerance_(tolerance)
        , maxAngleBetweenSegments_(maxAngleBetweenSegments)
    {}

    SegmentsProcessingResult
    operator () (
        SegmentsGraph& graph, SegmentId segmentId1, SegmentId segmentId2) const;

private:
    struct SnappedPoint {
        PointId id;
        double projFactor;
    };

    typedef std::vector<SnappedPoint> SnappedPointsVector;

    typedef std::function<bool(PointId)> MergeablePointsFilter;

    struct SnappedPointIterators {
        SnappedPointsVector::iterator insertIterator;
        SnappedPointsVector::iterator mergeIterator;
    };

    class Impl {
    public:
        Impl(
                SegmentsGraph& graph,
                double tolerance,
                double maxAngleBetweenSegments)
            : graph_(graph)
            , tolerance_(tolerance)
            , maxAngleBetweenSegments_(maxAngleBetweenSegments)
        {}

        SegmentsProcessingResult
        processSegmentsOverlap(SegmentId segmentId1, SegmentId segmentId2);

    private:
        SnappedPointIterators
        findSnapIterators(
            const SnappedPoint& snappedPoint, SnappedPointsVector& otherPoints,
            double epsFactor);

        void
        snap(SnappedPointsVector& thisPoints,
            SnappedPointsVector& otherPoints, const gl::Segment2& otherSegment,
            MergeablePointsFilter mergeablePointsFilter,
            std::map<PointId, PointId>& mergedPoints);

        boost::optional<SnappedPoint>
        snapPoint(const Point& point,
            SnappedPointsVector& otherPoints, const gl::Segment2& otherSegment,
            MergeablePointsFilter pointIsMergeable);

        bool mergedPointsValid(const std::map<PointId, PointId>& mergedPoints) const;
        void mergePoints(const std::map<PointId, PointId>& mergedPoints);

        bool snappedPointsValid(const SnappedPointsVector& snappedPoints,
            const std::map<PointId, PointId>& mergedPoints) const;

        SegmentIdsList processSnappedPointsVector(
            SegmentId segmentId, const SnappedPointsVector& snappedPoints,
            const std::map<PointId, PointId>& mergedPoints);

        SegmentsGraph& graph_;
        double tolerance_;
        double maxAngleBetweenSegments_;
    };

    double tolerance_;
    double maxAngleBetweenSegments_;
};

class SegmentsOverlapper : public SegmentsProcessor {
public:
    SegmentsOverlapper(
            double tolerance,
            double maxAngleBetweenSegments)
        : SegmentsProcessor(SegmentsPairOverlapper(tolerance, maxAngleBetweenSegments))
        , tolerance_(tolerance)
        , maxAngleBetweenSegments_(maxAngleBetweenSegments)
    {}

protected:

    class Impl : public SegmentsProcessor::Impl {
    public:
        Impl(
                const SegmentsPairProcessor& processor,
                SegmentsGraph& graph,
                const SegmentIdsList& segmentIds,
                const SegmentIdsList& otherSegmentIds,
                double tolerance,
                double maxAngleBetweenSegments)
            : SegmentsProcessor::Impl(processor, graph, segmentIds, otherSegmentIds)
            , tolerance_(tolerance)
            , maxAngleBetweenSegments_(maxAngleBetweenSegments)
        {}

        virtual SegmentsProcessingResult doProcessing()
        {
            return processInteractionsOnce();
        }

        virtual void doBeforeProcessing();

        virtual void doAfterProcessing();

    protected:
        double tolerance_;
        double maxAngleBetweenSegments_;
    };

    virtual std::unique_ptr<SegmentsProcessor::Impl>
    initImpl(
        SegmentsGraph& graph,
        const SegmentIdsList& segmentIds,
        const SegmentIdsList& otherSegmentIds) const
    {
        return std::unique_ptr<Impl>(new Impl(
            processor_, graph, segmentIds, otherSegmentIds,
            tolerance_, maxAngleBetweenSegments_));
    }

    double tolerance_;
    double maxAngleBetweenSegments_;
};

SegmentsProcessingResult
processOverlaps(SegmentsGraph& data,
    const SegmentIdsList& segmentIds, const SegmentIdsList& otherSegmentIds,
    double tolerance, double maxAngleBetweenSegments);

} // namespace utils
} // namespace topology_fixer
} // namespace wiki
} // namespace maps
