#pragma once

#include <maps/libs/introspection/include/comparison.h>
#include <maps/libs/introspection/include/stream_output.h>

#include <maps/libs/common/include/exception.h>
#include <maps/libs/geolib/include/polyline.h>

#include <optional>
#include <ostream>
#include <string>
#include <unordered_set>
#include <vector>

namespace maps::mrc::common::geometry {

using maps::introspection::operator<;
using maps::introspection::operator>;
using maps::introspection::operator==;
using maps::introspection::operator<<;

constexpr double MIN_REL_POS = 0.;
constexpr double MAX_REL_POS = 1.;

/// Defines half-closed interval [begin, end)
class SubSegment {
public:
    SubSegment() = default;

    SubSegment(double begin, double end);

    double begin() const { return begin_; }
    double end() const { return end_; }
    double length() const { return end_ - begin_; }
    bool isDegenerate() const;

    SubSegment& setBegin(double begin);
    SubSegment& setEnd(double end);

private:
    void validate(double begin, double end) const;

    double begin_ = MIN_REL_POS;
    double end_ = MAX_REL_POS;
};

bool operator==(const SubSegment& one, const SubSegment& other);
std::ostream& operator<<(std::ostream& out, const SubSegment& segment);

class PolylinePosition {
public:
    PolylinePosition(size_t segmentIdx, double segmentRelPosition);

    size_t segmentIdx() const { return segmentIdx_; }
    double segmentRelPosition() const { return segmentRelPosition_; }

private:
    size_t segmentIdx_;
    double segmentRelPosition_;
};

std::ostream& operator<<(std::ostream& out, const PolylinePosition& pos);

bool operator==(const PolylinePosition& one, const PolylinePosition& other);
bool operator!=(const PolylinePosition& one, const PolylinePosition& other);
bool operator<(const PolylinePosition& one, const PolylinePosition& other);
bool operator>(const PolylinePosition& one, const PolylinePosition& other);
bool operator<=(const PolylinePosition& one, const PolylinePosition& other);
bool operator>=(const PolylinePosition& one, const PolylinePosition& other);

/// Defines half-closed interval [begin, end)
class SubPolyline {
public:
    SubPolyline(PolylinePosition begin, PolylinePosition end);

    const PolylinePosition& begin() const { return begin_; }
    const PolylinePosition& end() const { return end_; }

    SubPolyline& setBegin(PolylinePosition begin);
    SubPolyline& setEnd(PolylinePosition end);

    bool empty() const { return begin_ == end_; }

    template<typename T>
    static auto introspect(T& o) {
        return std::tie(o.begin(), o.end());
    }

private:
    void validate(PolylinePosition begin, PolylinePosition end);

    PolylinePosition begin_;
    PolylinePosition end_;
};

using SubPolylines = std::vector<SubPolyline>;

std::ostream& operator<<(std::ostream& out, const SubPolyline& subpolyline);

bool precedes(const SubPolyline& one, const SubPolyline& other);

bool intersects(const SubPolyline& one, const SubPolyline& other);

bool contains(const SubPolyline& one, const SubPolyline& other);

SubPolyline intersection(const SubPolyline& one, const SubPolyline& other);

SubPolylines merge(SubPolylines subpolylines);

template<typename Value>
struct SubPolylineWithValue {

    SubPolylineWithValue(SubPolyline s, Value v)
        : subPolyline(std::move(s))
        , value(std::move(v))
    {}

    SubPolyline subPolyline;
    Value value;

    template<typename T>
    static auto introspect(T& o) {
        return std::tie(o.subPolyline, o.value);
    }
};

/// Merges given @param items subpoolylines and @return non-intersecting subpolylines.
/// For each intersecting pair of subpolylines it chooses the greatest according to
/// @param cmp. Value type must have operator== defined.
template<typename Value,
         typename Cmp = std::function<bool(const Value&, const Value&)>>
std::vector<SubPolylineWithValue<Value>>
merge(const std::vector<SubPolylineWithValue<Value>>& items, Cmp cmp);

} // maps::mrc::common::geometry


#define MAPS_MRC_COMMON_GEOEMTRY_INL
#include "geometry-inl.h"
#undef MAPS_MRC_COMMON_GEOEMTRY_INL
